1package flags
2
3import (
4	"reflect"
5	"sort"
6	"strconv"
7	"strings"
8)
9
10// Command represents an application command. Commands can be added to the
11// parser (which itself is a command) and are selected/executed when its name
12// is specified on the command line. The Command type embeds a Group and
13// therefore also carries a set of command specific options.
14type Command struct {
15	// Embedded, see Group for more information
16	*Group
17
18	// The name by which the command can be invoked
19	Name string
20
21	// The active sub command (set by parsing) or nil
22	Active *Command
23
24	// Whether subcommands are optional
25	SubcommandsOptional bool
26
27	// Aliases for the command
28	Aliases []string
29
30	// Whether positional arguments are required
31	ArgsRequired bool
32
33	commands            []*Command
34	hasBuiltinHelpGroup bool
35	args                []*Arg
36}
37
38// Commander is an interface which can be implemented by any command added in
39// the options. When implemented, the Execute method will be called for the last
40// specified (sub)command providing the remaining command line arguments.
41type Commander interface {
42	// Execute will be called for the last active (sub)command. The
43	// args argument contains the remaining command line arguments. The
44	// error that Execute returns will be eventually passed out of the
45	// Parse method of the Parser.
46	Execute(args []string) error
47}
48
49// Usage is an interface which can be implemented to show a custom usage string
50// in the help message shown for a command.
51type Usage interface {
52	// Usage is called for commands to allow customized printing of command
53	// usage in the generated help message.
54	Usage() string
55}
56
57type lookup struct {
58	shortNames map[string]*Option
59	longNames  map[string]*Option
60
61	commands map[string]*Command
62}
63
64// AddCommand adds a new command to the parser with the given name and data. The
65// data needs to be a pointer to a struct from which the fields indicate which
66// options are in the command. The provided data can implement the Command and
67// Usage interfaces.
68func (c *Command) AddCommand(command string, shortDescription string, longDescription string, data interface{}) (*Command, error) {
69	cmd := newCommand(command, shortDescription, longDescription, data)
70
71	cmd.parent = c
72
73	if err := cmd.scan(); err != nil {
74		return nil, err
75	}
76
77	c.commands = append(c.commands, cmd)
78	return cmd, nil
79}
80
81// AddGroup adds a new group to the command with the given name and data. The
82// data needs to be a pointer to a struct from which the fields indicate which
83// options are in the group.
84func (c *Command) AddGroup(shortDescription string, longDescription string, data interface{}) (*Group, error) {
85	group := newGroup(shortDescription, longDescription, data)
86
87	group.parent = c
88
89	if err := group.scanType(c.scanSubcommandHandler(group)); err != nil {
90		return nil, err
91	}
92
93	c.groups = append(c.groups, group)
94	return group, nil
95}
96
97// Commands returns a list of subcommands of this command.
98func (c *Command) Commands() []*Command {
99	return c.commands
100}
101
102// Find locates the subcommand with the given name and returns it. If no such
103// command can be found Find will return nil.
104func (c *Command) Find(name string) *Command {
105	for _, cc := range c.commands {
106		if cc.match(name) {
107			return cc
108		}
109	}
110
111	return nil
112}
113
114// FindOptionByLongName finds an option that is part of the command, or any of
115// its parent commands, by matching its long name (including the option
116// namespace).
117func (c *Command) FindOptionByLongName(longName string) (option *Option) {
118	for option == nil && c != nil {
119		option = c.Group.FindOptionByLongName(longName)
120
121		c, _ = c.parent.(*Command)
122	}
123
124	return option
125}
126
127// FindOptionByShortName finds an option that is part of the command, or any of
128// its parent commands, by matching its long name (including the option
129// namespace).
130func (c *Command) FindOptionByShortName(shortName rune) (option *Option) {
131	for option == nil && c != nil {
132		option = c.Group.FindOptionByShortName(shortName)
133
134		c, _ = c.parent.(*Command)
135	}
136
137	return option
138}
139
140// Args returns a list of positional arguments associated with this command.
141func (c *Command) Args() []*Arg {
142	ret := make([]*Arg, len(c.args))
143	copy(ret, c.args)
144
145	return ret
146}
147
148func newCommand(name string, shortDescription string, longDescription string, data interface{}) *Command {
149	return &Command{
150		Group: newGroup(shortDescription, longDescription, data),
151		Name:  name,
152	}
153}
154
155func (c *Command) scanSubcommandHandler(parentg *Group) scanHandler {
156	f := func(realval reflect.Value, sfield *reflect.StructField) (bool, error) {
157		mtag := newMultiTag(string(sfield.Tag))
158
159		if err := mtag.Parse(); err != nil {
160			return true, err
161		}
162
163		positional := mtag.Get("positional-args")
164
165		if len(positional) != 0 {
166			stype := realval.Type()
167
168			for i := 0; i < stype.NumField(); i++ {
169				field := stype.Field(i)
170
171				m := newMultiTag((string(field.Tag)))
172
173				if err := m.Parse(); err != nil {
174					return true, err
175				}
176
177				name := m.Get("positional-arg-name")
178
179				if len(name) == 0 {
180					name = field.Name
181				}
182
183				required := -1
184				requiredMaximum := -1
185
186				sreq := m.Get("required")
187
188				if sreq != "" {
189					required = 1
190
191					rng := strings.SplitN(sreq, "-", 2)
192
193					if len(rng) > 1 {
194						if preq, err := strconv.ParseInt(rng[0], 10, 32); err == nil {
195							required = int(preq)
196						}
197
198						if preq, err := strconv.ParseInt(rng[1], 10, 32); err == nil {
199							requiredMaximum = int(preq)
200						}
201					} else {
202						if preq, err := strconv.ParseInt(sreq, 10, 32); err == nil {
203							required = int(preq)
204						}
205					}
206				}
207
208				arg := &Arg{
209					Name:            name,
210					Description:     m.Get("description"),
211					Required:        required,
212					RequiredMaximum: requiredMaximum,
213
214					value: realval.Field(i),
215					tag:   m,
216				}
217
218				c.args = append(c.args, arg)
219
220				if len(mtag.Get("required")) != 0 {
221					c.ArgsRequired = true
222				}
223			}
224
225			return true, nil
226		}
227
228		subcommand := mtag.Get("command")
229
230		if len(subcommand) != 0 {
231			var ptrval reflect.Value
232
233			if realval.Kind() == reflect.Ptr {
234				ptrval = realval
235
236				if ptrval.IsNil() {
237					ptrval.Set(reflect.New(ptrval.Type().Elem()))
238				}
239			} else {
240				ptrval = realval.Addr()
241			}
242
243			shortDescription := mtag.Get("description")
244			longDescription := mtag.Get("long-description")
245			subcommandsOptional := mtag.Get("subcommands-optional")
246			aliases := mtag.GetMany("alias")
247
248			subc, err := c.AddCommand(subcommand, shortDescription, longDescription, ptrval.Interface())
249
250			if err != nil {
251				return true, err
252			}
253
254			subc.Hidden = mtag.Get("hidden") != ""
255
256			if len(subcommandsOptional) > 0 {
257				subc.SubcommandsOptional = true
258			}
259
260			if len(aliases) > 0 {
261				subc.Aliases = aliases
262			}
263
264			return true, nil
265		}
266
267		return parentg.scanSubGroupHandler(realval, sfield)
268	}
269
270	return f
271}
272
273func (c *Command) scan() error {
274	return c.scanType(c.scanSubcommandHandler(c.Group))
275}
276
277func (c *Command) eachOption(f func(*Command, *Group, *Option)) {
278	c.eachCommand(func(c *Command) {
279		c.eachGroup(func(g *Group) {
280			for _, option := range g.options {
281				f(c, g, option)
282			}
283		})
284	}, true)
285}
286
287func (c *Command) eachCommand(f func(*Command), recurse bool) {
288	f(c)
289
290	for _, cc := range c.commands {
291		if recurse {
292			cc.eachCommand(f, true)
293		} else {
294			f(cc)
295		}
296	}
297}
298
299func (c *Command) eachActiveGroup(f func(cc *Command, g *Group)) {
300	c.eachGroup(func(g *Group) {
301		f(c, g)
302	})
303
304	if c.Active != nil {
305		c.Active.eachActiveGroup(f)
306	}
307}
308
309func (c *Command) addHelpGroups(showHelp func() error) {
310	if !c.hasBuiltinHelpGroup {
311		c.addHelpGroup(showHelp)
312		c.hasBuiltinHelpGroup = true
313	}
314
315	for _, cc := range c.commands {
316		cc.addHelpGroups(showHelp)
317	}
318}
319
320func (c *Command) makeLookup() lookup {
321	ret := lookup{
322		shortNames: make(map[string]*Option),
323		longNames:  make(map[string]*Option),
324		commands:   make(map[string]*Command),
325	}
326
327	parent := c.parent
328
329	var parents []*Command
330
331	for parent != nil {
332		if cmd, ok := parent.(*Command); ok {
333			parents = append(parents, cmd)
334			parent = cmd.parent
335		} else {
336			parent = nil
337		}
338	}
339
340	for i := len(parents) - 1; i >= 0; i-- {
341		parents[i].fillLookup(&ret, true)
342	}
343
344	c.fillLookup(&ret, false)
345	return ret
346}
347
348func (c *Command) fillLookup(ret *lookup, onlyOptions bool) {
349	c.eachGroup(func(g *Group) {
350		for _, option := range g.options {
351			if option.ShortName != 0 {
352				ret.shortNames[string(option.ShortName)] = option
353			}
354
355			if len(option.LongName) > 0 {
356				ret.longNames[option.LongNameWithNamespace()] = option
357			}
358		}
359	})
360
361	if onlyOptions {
362		return
363	}
364
365	for _, subcommand := range c.commands {
366		ret.commands[subcommand.Name] = subcommand
367
368		for _, a := range subcommand.Aliases {
369			ret.commands[a] = subcommand
370		}
371	}
372}
373
374func (c *Command) groupByName(name string) *Group {
375	if grp := c.Group.groupByName(name); grp != nil {
376		return grp
377	}
378
379	for _, subc := range c.commands {
380		prefix := subc.Name + "."
381
382		if strings.HasPrefix(name, prefix) {
383			if grp := subc.groupByName(name[len(prefix):]); grp != nil {
384				return grp
385			}
386		} else if name == subc.Name {
387			return subc.Group
388		}
389	}
390
391	return nil
392}
393
394type commandList []*Command
395
396func (c commandList) Less(i, j int) bool {
397	return c[i].Name < c[j].Name
398}
399
400func (c commandList) Len() int {
401	return len(c)
402}
403
404func (c commandList) Swap(i, j int) {
405	c[i], c[j] = c[j], c[i]
406}
407
408func (c *Command) sortedVisibleCommands() []*Command {
409	ret := commandList(c.visibleCommands())
410	sort.Sort(ret)
411
412	return []*Command(ret)
413}
414
415func (c *Command) visibleCommands() []*Command {
416	ret := make([]*Command, 0, len(c.commands))
417
418	for _, cmd := range c.commands {
419		if !cmd.Hidden {
420			ret = append(ret, cmd)
421		}
422	}
423
424	return ret
425}
426
427func (c *Command) match(name string) bool {
428	if c.Name == name {
429		return true
430	}
431
432	for _, v := range c.Aliases {
433		if v == name {
434			return true
435		}
436	}
437
438	return false
439}
440
441func (c *Command) hasCliOptions() bool {
442	ret := false
443
444	c.eachGroup(func(g *Group) {
445		if g.isBuiltinHelp {
446			return
447		}
448
449		for _, opt := range g.options {
450			if opt.canCli() {
451				ret = true
452			}
453		}
454	})
455
456	return ret
457}
458
459func (c *Command) fillParseState(s *parseState) {
460	s.positional = make([]*Arg, len(c.args))
461	copy(s.positional, c.args)
462
463	s.lookup = c.makeLookup()
464	s.command = c
465}
466