1// Copyright 2015 The etcd Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// copied from https://github.com/rkt/rkt/blob/master/rkt/help.go
16
17package ctlv3
18
19import (
20	"bytes"
21	"fmt"
22	"io"
23	"os"
24	"strings"
25	"text/tabwriter"
26	"text/template"
27
28	"go.etcd.io/etcd/api/v3/version"
29
30	"github.com/spf13/cobra"
31	"github.com/spf13/pflag"
32)
33
34var (
35	commandUsageTemplate *template.Template
36	templFuncs           = template.FuncMap{
37		"descToLines": func(s string) []string {
38			// trim leading/trailing whitespace and split into slice of lines
39			return strings.Split(strings.Trim(s, "\n\t "), "\n")
40		},
41		"cmdName": func(cmd *cobra.Command, startCmd *cobra.Command) string {
42			parts := []string{cmd.Name()}
43			for cmd.HasParent() && cmd.Parent().Name() != startCmd.Name() {
44				cmd = cmd.Parent()
45				parts = append([]string{cmd.Name()}, parts...)
46			}
47			return strings.Join(parts, " ")
48		},
49	}
50)
51
52func init() {
53	commandUsage := `
54{{ $cmd := .Cmd }}\
55{{ $cmdname := cmdName .Cmd .Cmd.Root }}\
56NAME:
57{{ if not .Cmd.HasParent }}\
58{{printf "\t%s - %s" .Cmd.Name .Cmd.Short}}
59{{else}}\
60{{printf "\t%s - %s" $cmdname .Cmd.Short}}
61{{end}}\
62
63USAGE:
64{{printf "\t%s" .Cmd.UseLine}}
65{{ if not .Cmd.HasParent }}\
66
67VERSION:
68{{printf "\t%s" .Version}}
69{{end}}\
70{{if .Cmd.HasSubCommands}}\
71
72API VERSION:
73{{printf "\t%s" .APIVersion}}
74{{end}}\
75{{if .Cmd.HasSubCommands}}\
76
77
78COMMANDS:
79{{range .SubCommands}}\
80{{ $cmdname := cmdName . $cmd }}\
81{{ if .Runnable }}\
82{{printf "\t%s\t%s" $cmdname .Short}}
83{{end}}\
84{{end}}\
85{{end}}\
86{{ if .Cmd.Long }}\
87
88DESCRIPTION:
89{{range $line := descToLines .Cmd.Long}}{{printf "\t%s" $line}}
90{{end}}\
91{{end}}\
92{{if .Cmd.HasLocalFlags}}\
93
94OPTIONS:
95{{.LocalFlags}}\
96{{end}}\
97{{if .Cmd.HasInheritedFlags}}\
98
99GLOBAL OPTIONS:
100{{.GlobalFlags}}\
101{{end}}
102`[1:]
103
104	commandUsageTemplate = template.Must(template.New("command_usage").Funcs(templFuncs).Parse(strings.Replace(commandUsage, "\\\n", "", -1)))
105}
106
107func etcdFlagUsages(flagSet *pflag.FlagSet) string {
108	x := new(bytes.Buffer)
109
110	flagSet.VisitAll(func(flag *pflag.Flag) {
111		if len(flag.Deprecated) > 0 {
112			return
113		}
114		var format string
115		if len(flag.Shorthand) > 0 {
116			format = "  -%s, --%s"
117		} else {
118			format = "   %s   --%s"
119		}
120		if len(flag.NoOptDefVal) > 0 {
121			format = format + "["
122		}
123		if flag.Value.Type() == "string" {
124			// put quotes on the value
125			format = format + "=%q"
126		} else {
127			format = format + "=%s"
128		}
129		if len(flag.NoOptDefVal) > 0 {
130			format = format + "]"
131		}
132		format = format + "\t%s\n"
133		shorthand := flag.Shorthand
134		fmt.Fprintf(x, format, shorthand, flag.Name, flag.DefValue, flag.Usage)
135	})
136
137	return x.String()
138}
139
140func getSubCommands(cmd *cobra.Command) []*cobra.Command {
141	var subCommands []*cobra.Command
142	for _, subCmd := range cmd.Commands() {
143		subCommands = append(subCommands, subCmd)
144		subCommands = append(subCommands, getSubCommands(subCmd)...)
145	}
146	return subCommands
147}
148
149func usageFunc(cmd *cobra.Command) error {
150	subCommands := getSubCommands(cmd)
151	tabOut := getTabOutWithWriter(os.Stdout)
152	commandUsageTemplate.Execute(tabOut, struct {
153		Cmd         *cobra.Command
154		LocalFlags  string
155		GlobalFlags string
156		SubCommands []*cobra.Command
157		Version     string
158		APIVersion  string
159	}{
160		cmd,
161		etcdFlagUsages(cmd.LocalFlags()),
162		etcdFlagUsages(cmd.InheritedFlags()),
163		subCommands,
164		version.Version,
165		version.APIVersion,
166	})
167	tabOut.Flush()
168	return nil
169}
170
171func getTabOutWithWriter(writer io.Writer) *tabwriter.Writer {
172	aTabOut := new(tabwriter.Writer)
173	aTabOut.Init(writer, 0, 8, 1, '\t', 0)
174	return aTabOut
175}
176