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