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// FindOptionByLongName finds an option that is part of the command, or any of
116// its parent commands, by matching its long name (including the option
117// 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// FindOptionByShortName finds an option that is part of the command, or any of
129// its parent commands, by matching its long name (including the option
130// 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				required := -1
185				requiredMaximum := -1
186
187				sreq := m.Get("required")
188
189				if sreq != "" {
190					required = 1
191
192					rng := strings.SplitN(sreq, "-", 2)
193
194					if len(rng) > 1 {
195						if preq, err := strconv.ParseInt(rng[0], 10, 32); err == nil {
196							required = int(preq)
197						}
198
199						if preq, err := strconv.ParseInt(rng[1], 10, 32); err == nil {
200							requiredMaximum = int(preq)
201						}
202					} else {
203						if preq, err := strconv.ParseInt(sreq, 10, 32); err == nil {
204							required = int(preq)
205						}
206					}
207				}
208
209				arg := &Arg{
210					Name:            name,
211					Description:     m.Get("description"),
212					Required:        required,
213					RequiredMaximum: requiredMaximum,
214
215					value: realval.Field(i),
216					tag:   m,
217				}
218
219				c.args = append(c.args, arg)
220
221				if len(mtag.Get("required")) != 0 {
222					c.ArgsRequired = true
223				}
224			}
225
226			return true, nil
227		}
228
229		subcommand := mtag.Get("command")
230
231		if len(subcommand) != 0 {
232			ptrval := reflect.NewAt(realval.Type(), unsafe.Pointer(realval.UnsafeAddr()))
233
234			shortDescription := mtag.Get("description")
235			longDescription := mtag.Get("long-description")
236			subcommandsOptional := mtag.Get("subcommands-optional")
237			aliases := mtag.GetMany("alias")
238
239			subc, err := c.AddCommand(subcommand, shortDescription, longDescription, ptrval.Interface())
240			if err != nil {
241				return true, err
242			}
243
244			subc.Hidden = mtag.Get("hidden") != ""
245
246			if len(subcommandsOptional) > 0 {
247				subc.SubcommandsOptional = true
248			}
249
250			if len(aliases) > 0 {
251				subc.Aliases = aliases
252			}
253
254			return true, nil
255		}
256
257		return parentg.scanSubGroupHandler(realval, sfield)
258	}
259
260	return f
261}
262
263func (c *Command) scan() error {
264	return c.scanType(c.scanSubcommandHandler(c.Group))
265}
266
267func (c *Command) eachOption(f func(*Command, *Group, *Option)) {
268	c.eachCommand(func(c *Command) {
269		c.eachGroup(func(g *Group) {
270			for _, option := range g.options {
271				f(c, g, option)
272			}
273		})
274	}, true)
275}
276
277func (c *Command) eachCommand(f func(*Command), recurse bool) {
278	f(c)
279
280	for _, cc := range c.commands {
281		if recurse {
282			cc.eachCommand(f, true)
283		} else {
284			f(cc)
285		}
286	}
287}
288
289func (c *Command) eachActiveGroup(f func(cc *Command, g *Group)) {
290	c.eachGroup(func(g *Group) {
291		f(c, g)
292	})
293
294	if c.Active != nil {
295		c.Active.eachActiveGroup(f)
296	}
297}
298
299func (c *Command) addHelpGroups(showHelp func() error) {
300	if !c.hasBuiltinHelpGroup {
301		c.addHelpGroup(showHelp)
302		c.hasBuiltinHelpGroup = true
303	}
304
305	for _, cc := range c.commands {
306		cc.addHelpGroups(showHelp)
307	}
308}
309
310func (c *Command) makeLookup() lookup {
311	ret := lookup{
312		shortNames: make(map[string]*Option),
313		longNames:  make(map[string]*Option),
314		commands:   make(map[string]*Command),
315	}
316
317	parent := c.parent
318
319	var parents []*Command
320
321	for parent != nil {
322		if cmd, ok := parent.(*Command); ok {
323			parents = append(parents, cmd)
324			parent = cmd.parent
325		} else {
326			parent = nil
327		}
328	}
329
330	for i := len(parents) - 1; i >= 0; i-- {
331		parents[i].fillLookup(&ret, true)
332	}
333
334	c.fillLookup(&ret, false)
335	return ret
336}
337
338func (c *Command) fillLookup(ret *lookup, onlyOptions bool) {
339	c.eachGroup(func(g *Group) {
340		for _, option := range g.options {
341			if option.ShortName != 0 {
342				ret.shortNames[string(option.ShortName)] = option
343			}
344
345			if len(option.LongName) > 0 {
346				ret.longNames[option.LongNameWithNamespace()] = option
347			}
348		}
349	})
350
351	if onlyOptions {
352		return
353	}
354
355	for _, subcommand := range c.commands {
356		ret.commands[subcommand.Name] = subcommand
357
358		for _, a := range subcommand.Aliases {
359			ret.commands[a] = subcommand
360		}
361	}
362}
363
364func (c *Command) groupByName(name string) *Group {
365	if grp := c.Group.groupByName(name); grp != nil {
366		return grp
367	}
368
369	for _, subc := range c.commands {
370		prefix := subc.Name + "."
371
372		if strings.HasPrefix(name, prefix) {
373			if grp := subc.groupByName(name[len(prefix):]); grp != nil {
374				return grp
375			}
376		} else if name == subc.Name {
377			return subc.Group
378		}
379	}
380
381	return nil
382}
383
384type commandList []*Command
385
386func (c commandList) Less(i, j int) bool {
387	return c[i].Name < c[j].Name
388}
389
390func (c commandList) Len() int {
391	return len(c)
392}
393
394func (c commandList) Swap(i, j int) {
395	c[i], c[j] = c[j], c[i]
396}
397
398func (c *Command) sortedVisibleCommands() []*Command {
399	ret := commandList(c.visibleCommands())
400	sort.Sort(ret)
401
402	return []*Command(ret)
403}
404
405func (c *Command) visibleCommands() []*Command {
406	ret := make([]*Command, 0, len(c.commands))
407
408	for _, cmd := range c.commands {
409		if !cmd.Hidden {
410			ret = append(ret, cmd)
411		}
412	}
413
414	return ret
415}
416
417func (c *Command) match(name string) bool {
418	if c.Name == name {
419		return true
420	}
421
422	for _, v := range c.Aliases {
423		if v == name {
424			return true
425		}
426	}
427
428	return false
429}
430
431func (c *Command) hasCliOptions() bool {
432	ret := false
433
434	c.eachGroup(func(g *Group) {
435		if g.isBuiltinHelp {
436			return
437		}
438
439		for _, opt := range g.options {
440			if opt.canCli() {
441				ret = true
442			}
443		}
444	})
445
446	return ret
447}
448
449func (c *Command) fillParseState(s *parseState) {
450	s.positional = make([]*Arg, len(c.args))
451	copy(s.positional, c.args)
452
453	s.lookup = c.makeLookup()
454	s.command = c
455}
456