1
16
17 package templates
18
19 import (
20 "bytes"
21 "fmt"
22 "strings"
23 "text/template"
24 "unicode"
25
26 "github.com/spf13/cobra"
27 flag "github.com/spf13/pflag"
28
29 "k8s.io/kubectl/pkg/util/term"
30 )
31
32 type FlagExposer interface {
33 ExposeFlags(cmd *cobra.Command, flags ...string) FlagExposer
34 }
35
36 func ActsAsRootCommand(cmd *cobra.Command, filters []string, groups ...CommandGroup) FlagExposer {
37 if cmd == nil {
38 panic("nil root command")
39 }
40 templater := &templater{
41 RootCmd: cmd,
42 UsageTemplate: MainUsageTemplate(),
43 HelpTemplate: MainHelpTemplate(),
44 CommandGroups: groups,
45 Filtered: filters,
46 }
47 cmd.SetFlagErrorFunc(templater.FlagErrorFunc())
48 cmd.SilenceUsage = true
49 cmd.SetUsageFunc(templater.UsageFunc())
50 cmd.SetHelpFunc(templater.HelpFunc())
51 return templater
52 }
53
54 func UseOptionsTemplates(cmd *cobra.Command) {
55 templater := &templater{
56 UsageTemplate: OptionsUsageTemplate(),
57 HelpTemplate: OptionsHelpTemplate(),
58 }
59 cmd.SetUsageFunc(templater.UsageFunc())
60 cmd.SetHelpFunc(templater.HelpFunc())
61 }
62
63 type templater struct {
64 UsageTemplate string
65 HelpTemplate string
66 RootCmd *cobra.Command
67 CommandGroups
68 Filtered []string
69 }
70
71 func (templater *templater) FlagErrorFunc(exposedFlags ...string) func(*cobra.Command, error) error {
72 return func(c *cobra.Command, err error) error {
73 c.SilenceUsage = true
74 switch c.CalledAs() {
75 case "options":
76 return fmt.Errorf("%s\nRun '%s' without flags.", err, c.CommandPath())
77 default:
78 return fmt.Errorf("%s\nSee '%s --help' for usage.", err, c.CommandPath())
79 }
80 }
81 }
82
83 func (templater *templater) ExposeFlags(cmd *cobra.Command, flags ...string) FlagExposer {
84 cmd.SetUsageFunc(templater.UsageFunc(flags...))
85 return templater
86 }
87
88 func (templater *templater) HelpFunc() func(*cobra.Command, []string) {
89 return func(c *cobra.Command, s []string) {
90 t := template.New("help")
91 t.Funcs(templater.templateFuncs())
92 template.Must(t.Parse(templater.HelpTemplate))
93 out := term.NewResponsiveWriter(c.OutOrStdout())
94 err := t.Execute(out, c)
95 if err != nil {
96 c.Println(err)
97 }
98 }
99 }
100
101 func (templater *templater) UsageFunc(exposedFlags ...string) func(*cobra.Command) error {
102 return func(c *cobra.Command) error {
103 t := template.New("usage")
104 t.Funcs(templater.templateFuncs(exposedFlags...))
105 template.Must(t.Parse(templater.UsageTemplate))
106 out := term.NewResponsiveWriter(c.OutOrStderr())
107 return t.Execute(out, c)
108 }
109 }
110
111 func (templater *templater) templateFuncs(exposedFlags ...string) template.FuncMap {
112 return template.FuncMap{
113 "trim": strings.TrimSpace,
114 "trimRight": func(s string) string { return strings.TrimRightFunc(s, unicode.IsSpace) },
115 "trimLeft": func(s string) string { return strings.TrimLeftFunc(s, unicode.IsSpace) },
116 "gt": cobra.Gt,
117 "eq": cobra.Eq,
118 "rpad": rpad,
119 "appendIfNotPresent": appendIfNotPresent,
120 "flagsNotIntersected": flagsNotIntersected,
121 "visibleFlags": visibleFlags,
122 "flagsUsages": flagsUsages,
123 "cmdGroups": templater.cmdGroups,
124 "cmdGroupsString": templater.cmdGroupsString,
125 "rootCmd": templater.rootCmdName,
126 "isRootCmd": templater.isRootCmd,
127 "optionsCmdFor": templater.optionsCmdFor,
128 "usageLine": templater.usageLine,
129 "reverseParentsNames": templater.reverseParentsNames,
130 "exposed": func(c *cobra.Command) *flag.FlagSet {
131 exposed := flag.NewFlagSet("exposed", flag.ContinueOnError)
132 if len(exposedFlags) > 0 {
133 for _, name := range exposedFlags {
134 if flag := c.Flags().Lookup(name); flag != nil {
135 exposed.AddFlag(flag)
136 }
137 }
138 }
139 return exposed
140 },
141 }
142 }
143
144 func (templater *templater) cmdGroups(c *cobra.Command, all []*cobra.Command) []CommandGroup {
145 if len(templater.CommandGroups) > 0 && c == templater.RootCmd {
146 all = filter(all, templater.Filtered...)
147 return AddAdditionalCommands(templater.CommandGroups, "Other Commands:", all)
148 }
149 all = filter(all, "options")
150 return []CommandGroup{
151 {
152 Message: "Available Commands:",
153 Commands: all,
154 },
155 }
156 }
157
158 func (t *templater) cmdGroupsString(c *cobra.Command) string {
159 groups := []string{}
160 for _, cmdGroup := range t.cmdGroups(c, c.Commands()) {
161 cmds := []string{cmdGroup.Message}
162 for _, cmd := range cmdGroup.Commands {
163 if cmd.IsAvailableCommand() {
164 cmds = append(cmds, " "+rpad(cmd.Name(), cmd.NamePadding())+" "+cmd.Short)
165 }
166 }
167 groups = append(groups, strings.Join(cmds, "\n"))
168 }
169 return strings.Join(groups, "\n\n")
170 }
171
172 func (t *templater) rootCmdName(c *cobra.Command) string {
173 return t.rootCmd(c).CommandPath()
174 }
175
176 func (t *templater) reverseParentsNames(c *cobra.Command) []string {
177 reverseParentsNames := []string{}
178 parents := t.parents(c)
179 for i := len(parents) - 1; i >= 0; i-- {
180 reverseParentsNames = append(reverseParentsNames, parents[i].Name())
181 }
182 return reverseParentsNames
183 }
184
185 func (t *templater) isRootCmd(c *cobra.Command) bool {
186 return t.rootCmd(c) == c
187 }
188
189 func (t *templater) parents(c *cobra.Command) []*cobra.Command {
190 parents := []*cobra.Command{c}
191 for current := c; !t.isRootCmd(current) && current.HasParent(); {
192 current = current.Parent()
193 parents = append(parents, current)
194 }
195 return parents
196 }
197
198 func (t *templater) rootCmd(c *cobra.Command) *cobra.Command {
199 if c != nil && !c.HasParent() {
200 return c
201 }
202 if t.RootCmd == nil {
203 panic("nil root cmd")
204 }
205 return t.RootCmd
206 }
207
208 func (t *templater) optionsCmdFor(c *cobra.Command) string {
209 if !c.Runnable() {
210 return ""
211 }
212 rootCmdStructure := t.parents(c)
213 for i := len(rootCmdStructure) - 1; i >= 0; i-- {
214 cmd := rootCmdStructure[i]
215 if _, _, err := cmd.Find([]string{"options"}); err == nil {
216 return cmd.CommandPath() + " options"
217 }
218 }
219 return ""
220 }
221
222 func (t *templater) usageLine(c *cobra.Command) string {
223 usage := c.UseLine()
224 suffix := "[options]"
225 if c.HasFlags() && !strings.Contains(usage, suffix) {
226 usage += " " + suffix
227 }
228 return usage
229 }
230
231
232 func flagsUsages(f *flag.FlagSet) (string, error) {
233 flagBuf := new(bytes.Buffer)
234 wrapLimit, err := term.GetWordWrapperLimit()
235 if err != nil {
236 wrapLimit = 0
237 }
238 printer := NewHelpFlagPrinter(flagBuf, wrapLimit)
239
240 f.VisitAll(func(flag *flag.Flag) {
241 if flag.Hidden {
242 return
243 }
244 printer.PrintHelpFlag(flag)
245 })
246
247 return flagBuf.String(), nil
248 }
249
250
251 func getFlagFormat(f *flag.Flag) string {
252 var format string
253 format = "--%s=%s:\n%s%s"
254 if f.Value.Type() == "string" {
255 format = "--%s='%s':\n%s%s"
256 }
257
258 if len(f.Shorthand) > 0 {
259 format = " -%s, " + format
260 } else {
261 format = " %s" + format
262 }
263
264 return format
265 }
266
267 func rpad(s string, padding int) string {
268 template := fmt.Sprintf("%%-%ds", padding)
269 return fmt.Sprintf(template, s)
270 }
271
272 func appendIfNotPresent(s, stringToAppend string) string {
273 if strings.Contains(s, stringToAppend) {
274 return s
275 }
276 return s + " " + stringToAppend
277 }
278
279 func flagsNotIntersected(l *flag.FlagSet, r *flag.FlagSet) *flag.FlagSet {
280 f := flag.NewFlagSet("notIntersected", flag.ContinueOnError)
281 l.VisitAll(func(flag *flag.Flag) {
282 if r.Lookup(flag.Name) == nil {
283 f.AddFlag(flag)
284 }
285 })
286 return f
287 }
288
289 func visibleFlags(l *flag.FlagSet) *flag.FlagSet {
290 hidden := "help"
291 f := flag.NewFlagSet("visible", flag.ContinueOnError)
292 l.VisitAll(func(flag *flag.Flag) {
293 if flag.Name != hidden {
294 f.AddFlag(flag)
295 }
296 })
297 return f
298 }
299
300 func filter(cmds []*cobra.Command, names ...string) []*cobra.Command {
301 out := []*cobra.Command{}
302 for _, c := range cmds {
303 if c.Hidden {
304 continue
305 }
306 skip := false
307 for _, name := range names {
308 if name == c.Name() {
309 skip = true
310 break
311 }
312 }
313 if skip {
314 continue
315 }
316 out = append(out, c)
317 }
318 return out
319 }
320
View as plain text