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	"github.com/coreos/etcd/version"
29	"github.com/spf13/cobra"
30	"github.com/spf13/pflag"
31)
32
33var (
34	commandUsageTemplate *template.Template
35	templFuncs           = template.FuncMap{
36		"descToLines": func(s string) []string {
37			// trim leading/trailing whitespace and split into slice of lines
38			return strings.Split(strings.Trim(s, "\n\t "), "\n")
39		},
40		"cmdName": func(cmd *cobra.Command, startCmd *cobra.Command) string {
41			parts := []string{cmd.Name()}
42			for cmd.HasParent() && cmd.Parent().Name() != startCmd.Name() {
43				cmd = cmd.Parent()
44				parts = append([]string{cmd.Name()}, parts...)
45			}
46			return strings.Join(parts, " ")
47		},
48	}
49)
50
51func init() {
52	commandUsage := `
53{{ $cmd := .Cmd }}\
54{{ $cmdname := cmdName .Cmd .Cmd.Root }}\
55NAME:
56{{ if not .Cmd.HasParent }}\
57{{printf "\t%s - %s" .Cmd.Name .Cmd.Short}}
58{{else}}\
59{{printf "\t%s - %s" $cmdname .Cmd.Short}}
60{{end}}\
61
62USAGE:
63{{printf "\t%s" .Cmd.UseLine}}
64{{ if not .Cmd.HasParent }}\
65
66VERSION:
67{{printf "\t%s" .Version}}
68{{end}}\
69{{if .Cmd.HasSubCommands}}\
70
71API VERSION:
72{{printf "\t%s" .APIVersion}}
73{{end}}\
74{{if .Cmd.HasSubCommands}}\
75
76
77COMMANDS:
78{{range .SubCommands}}\
79{{ $cmdname := cmdName . $cmd }}\
80{{ if .Runnable }}\
81{{printf "\t%s\t%s" $cmdname .Short}}
82{{end}}\
83{{end}}\
84{{end}}\
85{{ if .Cmd.Long }}\
86
87DESCRIPTION:
88{{range $line := descToLines .Cmd.Long}}{{printf "\t%s" $line}}
89{{end}}\
90{{end}}\
91{{if .Cmd.HasLocalFlags}}\
92
93OPTIONS:
94{{.LocalFlags}}\
95{{end}}\
96{{if .Cmd.HasInheritedFlags}}\
97
98GLOBAL OPTIONS:
99{{.GlobalFlags}}\
100{{end}}
101`[1:]
102
103	commandUsageTemplate = template.Must(template.New("command_usage").Funcs(templFuncs).Parse(strings.Replace(commandUsage, "\\\n", "", -1)))
104}
105
106func etcdFlagUsages(flagSet *pflag.FlagSet) string {
107	x := new(bytes.Buffer)
108
109	flagSet.VisitAll(func(flag *pflag.Flag) {
110		if len(flag.Deprecated) > 0 {
111			return
112		}
113		var format string
114		if len(flag.Shorthand) > 0 {
115			format = "  -%s, --%s"
116		} else {
117			format = "   %s   --%s"
118		}
119		if len(flag.NoOptDefVal) > 0 {
120			format = format + "["
121		}
122		if flag.Value.Type() == "string" {
123			// put quotes on the value
124			format = format + "=%q"
125		} else {
126			format = format + "=%s"
127		}
128		if len(flag.NoOptDefVal) > 0 {
129			format = format + "]"
130		}
131		format = format + "\t%s\n"
132		shorthand := flag.Shorthand
133		fmt.Fprintf(x, format, shorthand, flag.Name, flag.DefValue, flag.Usage)
134	})
135
136	return x.String()
137}
138
139func getSubCommands(cmd *cobra.Command) []*cobra.Command {
140	var subCommands []*cobra.Command
141	for _, subCmd := range cmd.Commands() {
142		subCommands = append(subCommands, subCmd)
143		subCommands = append(subCommands, getSubCommands(subCmd)...)
144	}
145	return subCommands
146}
147
148func usageFunc(cmd *cobra.Command) error {
149	subCommands := getSubCommands(cmd)
150	tabOut := getTabOutWithWriter(os.Stdout)
151	commandUsageTemplate.Execute(tabOut, struct {
152		Cmd         *cobra.Command
153		LocalFlags  string
154		GlobalFlags string
155		SubCommands []*cobra.Command
156		Version     string
157		APIVersion  string
158	}{
159		cmd,
160		etcdFlagUsages(cmd.LocalFlags()),
161		etcdFlagUsages(cmd.InheritedFlags()),
162		subCommands,
163		version.Version,
164		version.APIVersion,
165	})
166	tabOut.Flush()
167	return nil
168}
169
170func getTabOutWithWriter(writer io.Writer) *tabwriter.Writer {
171	aTabOut := new(tabwriter.Writer)
172	aTabOut.Init(writer, 0, 8, 1, '\t', 0)
173	return aTabOut
174}
175