1 package cli
2
3 import (
4 "flag"
5 "fmt"
6 "reflect"
7 "sort"
8 "strings"
9 )
10
11
12 type Command struct {
13
14 Name string
15
16 Aliases []string
17
18 Usage string
19
20 UsageText string
21
22 Description string
23
24 Args bool
25
26 ArgsUsage string
27
28 Category string
29
30 BashComplete BashCompleteFunc
31
32
33 Before BeforeFunc
34
35
36 After AfterFunc
37
38 Action ActionFunc
39
40 OnUsageError OnUsageErrorFunc
41
42 Subcommands []*Command
43
44 Flags []Flag
45 flagCategories FlagCategories
46
47 SkipFlagParsing bool
48
49 HideHelp bool
50
51
52 HideHelpCommand bool
53
54 Hidden bool
55
56
57
58 UseShortOptionHandling bool
59
60
61 HelpName string
62 commandNamePath []string
63
64
65
66
67 CustomHelpTemplate string
68
69
70 categories CommandCategories
71
72
73 isRoot bool
74
75 separator separatorSpec
76 }
77
78 type Commands []*Command
79
80 type CommandsByName []*Command
81
82 func (c CommandsByName) Len() int {
83 return len(c)
84 }
85
86 func (c CommandsByName) Less(i, j int) bool {
87 return lexicographicLess(c[i].Name, c[j].Name)
88 }
89
90 func (c CommandsByName) Swap(i, j int) {
91 c[i], c[j] = c[j], c[i]
92 }
93
94
95
96 func (c *Command) FullName() string {
97 if c.commandNamePath == nil {
98 return c.Name
99 }
100 return strings.Join(c.commandNamePath, " ")
101 }
102
103 func (cmd *Command) Command(name string) *Command {
104 for _, c := range cmd.Subcommands {
105 if c.HasName(name) {
106 return c
107 }
108 }
109
110 return nil
111 }
112
113 func (c *Command) setup(ctx *Context) {
114 if c.Command(helpCommand.Name) == nil && !c.HideHelp {
115 if !c.HideHelpCommand {
116 c.Subcommands = append(c.Subcommands, helpCommand)
117 }
118 }
119
120 if !c.HideHelp && HelpFlag != nil {
121
122 c.appendFlag(HelpFlag)
123 }
124
125 if ctx.App.UseShortOptionHandling {
126 c.UseShortOptionHandling = true
127 }
128
129 c.categories = newCommandCategories()
130 for _, command := range c.Subcommands {
131 c.categories.AddCommand(command.Category, command)
132 }
133 sort.Sort(c.categories.(*commandCategories))
134
135 var newCmds []*Command
136 for _, scmd := range c.Subcommands {
137 if scmd.HelpName == "" {
138 scmd.HelpName = fmt.Sprintf("%s %s", c.HelpName, scmd.Name)
139 }
140 scmd.separator = c.separator
141 newCmds = append(newCmds, scmd)
142 }
143 c.Subcommands = newCmds
144
145 if c.BashComplete == nil {
146 c.BashComplete = DefaultCompleteWithFlags(c)
147 }
148 }
149
150 func (c *Command) Run(cCtx *Context, arguments ...string) (err error) {
151
152 if !c.isRoot {
153 c.setup(cCtx)
154 if err := checkDuplicatedCmds(c); err != nil {
155 return err
156 }
157 }
158
159 a := args(arguments)
160 set, err := c.parseFlags(&a, cCtx.shellComplete)
161 cCtx.flagSet = set
162
163 if checkCompletions(cCtx) {
164 return nil
165 }
166
167 if err != nil {
168 if c.OnUsageError != nil {
169 err = c.OnUsageError(cCtx, err, !c.isRoot)
170 cCtx.App.handleExitCoder(cCtx, err)
171 return err
172 }
173 _, _ = fmt.Fprintf(cCtx.App.Writer, "%s %s\n\n", "Incorrect Usage:", err.Error())
174 if cCtx.App.Suggest {
175 if suggestion, err := c.suggestFlagFromError(err, ""); err == nil {
176 fmt.Fprintf(cCtx.App.Writer, "%s", suggestion)
177 }
178 }
179 if !c.HideHelp {
180 if c.isRoot {
181 _ = ShowAppHelp(cCtx)
182 } else {
183 _ = ShowCommandHelp(cCtx.parentContext, c.Name)
184 }
185 }
186 return err
187 }
188
189 if checkHelp(cCtx) {
190 return helpCommand.Action(cCtx)
191 }
192
193 if c.isRoot && !cCtx.App.HideVersion && checkVersion(cCtx) {
194 ShowVersion(cCtx)
195 return nil
196 }
197
198 if c.After != nil && !cCtx.shellComplete {
199 defer func() {
200 afterErr := c.After(cCtx)
201 if afterErr != nil {
202 cCtx.App.handleExitCoder(cCtx, err)
203 if err != nil {
204 err = newMultiError(err, afterErr)
205 } else {
206 err = afterErr
207 }
208 }
209 }()
210 }
211
212 cerr := cCtx.checkRequiredFlags(c.Flags)
213 if cerr != nil {
214 _ = helpCommand.Action(cCtx)
215 return cerr
216 }
217
218 if c.Before != nil && !cCtx.shellComplete {
219 beforeErr := c.Before(cCtx)
220 if beforeErr != nil {
221 cCtx.App.handleExitCoder(cCtx, beforeErr)
222 err = beforeErr
223 return err
224 }
225 }
226
227 if err = runFlagActions(cCtx, c.Flags); err != nil {
228 return err
229 }
230
231 var cmd *Command
232 args := cCtx.Args()
233 if args.Present() {
234 name := args.First()
235 cmd = c.Command(name)
236 if cmd == nil {
237 hasDefault := cCtx.App.DefaultCommand != ""
238 isFlagName := checkStringSliceIncludes(name, cCtx.FlagNames())
239
240 var (
241 isDefaultSubcommand = false
242 defaultHasSubcommands = false
243 )
244
245 if hasDefault {
246 dc := cCtx.App.Command(cCtx.App.DefaultCommand)
247 defaultHasSubcommands = len(dc.Subcommands) > 0
248 for _, dcSub := range dc.Subcommands {
249 if checkStringSliceIncludes(name, dcSub.Names()) {
250 isDefaultSubcommand = true
251 break
252 }
253 }
254 }
255
256 if isFlagName || (hasDefault && (defaultHasSubcommands && isDefaultSubcommand)) {
257 argsWithDefault := cCtx.App.argsWithDefaultCommand(args)
258 if !reflect.DeepEqual(args, argsWithDefault) {
259 cmd = cCtx.App.rootCommand.Command(argsWithDefault.First())
260 }
261 }
262 }
263 } else if c.isRoot && cCtx.App.DefaultCommand != "" {
264 if dc := cCtx.App.Command(cCtx.App.DefaultCommand); dc != c {
265 cmd = dc
266 }
267 }
268
269 if cmd != nil {
270 newcCtx := NewContext(cCtx.App, nil, cCtx)
271 newcCtx.Command = cmd
272 return cmd.Run(newcCtx, cCtx.Args().Slice()...)
273 }
274
275 if c.Action == nil {
276 c.Action = helpCommand.Action
277 }
278
279 err = c.Action(cCtx)
280
281 cCtx.App.handleExitCoder(cCtx, err)
282 return err
283 }
284
285 func (c *Command) newFlagSet() (*flag.FlagSet, error) {
286 return flagSet(c.Name, c.Flags, c.separator)
287 }
288
289 func (c *Command) useShortOptionHandling() bool {
290 return c.UseShortOptionHandling
291 }
292
293 func (c *Command) suggestFlagFromError(err error, command string) (string, error) {
294 flag, parseErr := flagFromError(err)
295 if parseErr != nil {
296 return "", err
297 }
298
299 flags := c.Flags
300 hideHelp := c.HideHelp
301 if command != "" {
302 cmd := c.Command(command)
303 if cmd == nil {
304 return "", err
305 }
306 flags = cmd.Flags
307 hideHelp = hideHelp || cmd.HideHelp
308 }
309
310 suggestion := SuggestFlag(flags, flag, hideHelp)
311 if len(suggestion) == 0 {
312 return "", err
313 }
314
315 return fmt.Sprintf(SuggestDidYouMeanTemplate, suggestion) + "\n\n", nil
316 }
317
318 func (c *Command) parseFlags(args Args, shellComplete bool) (*flag.FlagSet, error) {
319 set, err := c.newFlagSet()
320 if err != nil {
321 return nil, err
322 }
323
324 if c.SkipFlagParsing {
325 return set, set.Parse(append([]string{"--"}, args.Tail()...))
326 }
327
328 err = parseIter(set, c, args.Tail(), shellComplete)
329 if err != nil {
330 return nil, err
331 }
332
333 err = normalizeFlags(c.Flags, set)
334 if err != nil {
335 return nil, err
336 }
337
338 return set, nil
339 }
340
341
342 func (c *Command) Names() []string {
343 return append([]string{c.Name}, c.Aliases...)
344 }
345
346
347 func (c *Command) HasName(name string) bool {
348 for _, n := range c.Names() {
349 if n == name {
350 return true
351 }
352 }
353 return false
354 }
355
356
357
358 func (c *Command) VisibleCategories() []CommandCategory {
359 ret := []CommandCategory{}
360 for _, category := range c.categories.Categories() {
361 if visible := func() CommandCategory {
362 if len(category.VisibleCommands()) > 0 {
363 return category
364 }
365 return nil
366 }(); visible != nil {
367 ret = append(ret, visible)
368 }
369 }
370 return ret
371 }
372
373
374 func (c *Command) VisibleCommands() []*Command {
375 var ret []*Command
376 for _, command := range c.Subcommands {
377 if !command.Hidden {
378 ret = append(ret, command)
379 }
380 }
381 return ret
382 }
383
384
385 func (c *Command) VisibleFlagCategories() []VisibleFlagCategory {
386 if c.flagCategories == nil {
387 c.flagCategories = newFlagCategoriesFromFlags(c.Flags)
388 }
389 return c.flagCategories.VisibleCategories()
390 }
391
392
393 func (c *Command) VisibleFlags() []Flag {
394 return visibleFlags(c.Flags)
395 }
396
397 func (c *Command) appendFlag(fl Flag) {
398 if !hasFlag(c.Flags, fl) {
399 c.Flags = append(c.Flags, fl)
400 }
401 }
402
403 func hasCommand(commands []*Command, command *Command) bool {
404 for _, existing := range commands {
405 if command == existing {
406 return true
407 }
408 }
409
410 return false
411 }
412
413 func checkDuplicatedCmds(parent *Command) error {
414 seen := make(map[string]struct{})
415 for _, c := range parent.Subcommands {
416 for _, name := range c.Names() {
417 if _, exists := seen[name]; exists {
418 return fmt.Errorf("parent command [%s] has duplicated subcommand name or alias: %s", parent.Name, name)
419 }
420 seen[name] = struct{}{}
421 }
422 }
423 return nil
424 }
425
View as plain text