1package cli
2
3import (
4	"flag"
5	"fmt"
6	"sort"
7	"strings"
8)
9
10// Command is a subcommand for a cli.App.
11type Command struct {
12	// The name of the command
13	Name string
14	// A list of aliases for the command
15	Aliases []string
16	// A short description of the usage of this command
17	Usage string
18	// Custom text to show on USAGE section of help
19	UsageText string
20	// A longer explanation of how the command works
21	Description string
22	// A short description of the arguments of this command
23	ArgsUsage string
24	// The category the command is part of
25	Category string
26	// The function to call when checking for bash command completions
27	BashComplete BashCompleteFunc
28	// An action to execute before any sub-subcommands are run, but after the context is ready
29	// If a non-nil error is returned, no sub-subcommands are run
30	Before BeforeFunc
31	// An action to execute after any subcommands are run, but after the subcommand has finished
32	// It is run even if Action() panics
33	After AfterFunc
34	// The function to call when this command is invoked
35	Action ActionFunc
36	// Execute this function if a usage error occurs.
37	OnUsageError OnUsageErrorFunc
38	// List of child commands
39	Subcommands []*Command
40	// List of flags to parse
41	Flags []Flag
42	// Treat all flags as normal arguments if true
43	SkipFlagParsing bool
44	// Boolean to hide built-in help command and help flag
45	HideHelp bool
46	// Boolean to hide built-in help command but keep help flag
47	// Ignored if HideHelp is true.
48	HideHelpCommand bool
49	// Boolean to hide this command from help or completion
50	Hidden bool
51	// Boolean to enable short-option handling so user can combine several
52	// single-character bool arguments into one
53	// i.e. foobar -o -v -> foobar -ov
54	UseShortOptionHandling bool
55
56	// Full name of command for help, defaults to full command name, including parent commands.
57	HelpName        string
58	commandNamePath []string
59
60	// CustomHelpTemplate the text template for the command help topic.
61	// cli.go uses text/template to render templates. You can
62	// render custom help text by setting this variable.
63	CustomHelpTemplate string
64}
65
66type Commands []*Command
67
68type CommandsByName []*Command
69
70func (c CommandsByName) Len() int {
71	return len(c)
72}
73
74func (c CommandsByName) Less(i, j int) bool {
75	return lexicographicLess(c[i].Name, c[j].Name)
76}
77
78func (c CommandsByName) Swap(i, j int) {
79	c[i], c[j] = c[j], c[i]
80}
81
82// FullName returns the full name of the command.
83// For subcommands this ensures that parent commands are part of the command path
84func (c *Command) FullName() string {
85	if c.commandNamePath == nil {
86		return c.Name
87	}
88	return strings.Join(c.commandNamePath, " ")
89}
90
91// Run invokes the command given the context, parses ctx.Args() to generate command-specific flags
92func (c *Command) Run(ctx *Context) (err error) {
93	if len(c.Subcommands) > 0 {
94		return c.startApp(ctx)
95	}
96
97	if !c.HideHelp && HelpFlag != nil {
98		// append help to flags
99		c.appendFlag(HelpFlag)
100	}
101
102	if ctx.App.UseShortOptionHandling {
103		c.UseShortOptionHandling = true
104	}
105
106	set, err := c.parseFlags(ctx.Args(), ctx.shellComplete)
107
108	context := NewContext(ctx.App, set, ctx)
109	context.Command = c
110	if checkCommandCompletions(context, c.Name) {
111		return nil
112	}
113
114	if err != nil {
115		if c.OnUsageError != nil {
116			err = c.OnUsageError(context, err, false)
117			context.App.handleExitCoder(context, err)
118			return err
119		}
120		_, _ = fmt.Fprintln(context.App.Writer, "Incorrect Usage:", err.Error())
121		_, _ = fmt.Fprintln(context.App.Writer)
122		_ = ShowCommandHelp(context, c.Name)
123		return err
124	}
125
126	if checkCommandHelp(context, c.Name) {
127		return nil
128	}
129
130	cerr := checkRequiredFlags(c.Flags, context)
131	if cerr != nil {
132		_ = ShowCommandHelp(context, c.Name)
133		return cerr
134	}
135
136	if c.After != nil {
137		defer func() {
138			afterErr := c.After(context)
139			if afterErr != nil {
140				context.App.handleExitCoder(context, err)
141				if err != nil {
142					err = newMultiError(err, afterErr)
143				} else {
144					err = afterErr
145				}
146			}
147		}()
148	}
149
150	if c.Before != nil {
151		err = c.Before(context)
152		if err != nil {
153			context.App.handleExitCoder(context, err)
154			return err
155		}
156	}
157
158	if c.Action == nil {
159		c.Action = helpSubcommand.Action
160	}
161
162	context.Command = c
163	err = c.Action(context)
164
165	if err != nil {
166		context.App.handleExitCoder(context, err)
167	}
168	return err
169}
170
171func (c *Command) newFlagSet() (*flag.FlagSet, error) {
172	return flagSet(c.Name, c.Flags)
173}
174
175func (c *Command) useShortOptionHandling() bool {
176	return c.UseShortOptionHandling
177}
178
179func (c *Command) parseFlags(args Args, shellComplete bool) (*flag.FlagSet, error) {
180	set, err := c.newFlagSet()
181	if err != nil {
182		return nil, err
183	}
184
185	if c.SkipFlagParsing {
186		return set, set.Parse(append([]string{"--"}, args.Tail()...))
187	}
188
189	err = parseIter(set, c, args.Tail(), shellComplete)
190	if err != nil {
191		return nil, err
192	}
193
194	err = normalizeFlags(c.Flags, set)
195	if err != nil {
196		return nil, err
197	}
198
199	return set, nil
200}
201
202// Names returns the names including short names and aliases.
203func (c *Command) Names() []string {
204	return append([]string{c.Name}, c.Aliases...)
205}
206
207// HasName returns true if Command.Name matches given name
208func (c *Command) HasName(name string) bool {
209	for _, n := range c.Names() {
210		if n == name {
211			return true
212		}
213	}
214	return false
215}
216
217func (c *Command) startApp(ctx *Context) error {
218	app := &App{
219		Metadata: ctx.App.Metadata,
220		Name:     fmt.Sprintf("%s %s", ctx.App.Name, c.Name),
221	}
222
223	if c.HelpName == "" {
224		app.HelpName = c.HelpName
225	} else {
226		app.HelpName = app.Name
227	}
228
229	app.Usage = c.Usage
230	app.Description = c.Description
231	app.ArgsUsage = c.ArgsUsage
232
233	// set CommandNotFound
234	app.CommandNotFound = ctx.App.CommandNotFound
235	app.CustomAppHelpTemplate = c.CustomHelpTemplate
236
237	// set the flags and commands
238	app.Commands = c.Subcommands
239	app.Flags = c.Flags
240	app.HideHelp = c.HideHelp
241	app.HideHelpCommand = c.HideHelpCommand
242
243	app.Version = ctx.App.Version
244	app.HideVersion = true
245	app.Compiled = ctx.App.Compiled
246	app.Writer = ctx.App.Writer
247	app.ErrWriter = ctx.App.ErrWriter
248	app.ExitErrHandler = ctx.App.ExitErrHandler
249	app.UseShortOptionHandling = ctx.App.UseShortOptionHandling
250
251	app.categories = newCommandCategories()
252	for _, command := range c.Subcommands {
253		app.categories.AddCommand(command.Category, command)
254	}
255
256	sort.Sort(app.categories.(*commandCategories))
257
258	// bash completion
259	app.EnableBashCompletion = ctx.App.EnableBashCompletion
260	if c.BashComplete != nil {
261		app.BashComplete = c.BashComplete
262	}
263
264	// set the actions
265	app.Before = c.Before
266	app.After = c.After
267	if c.Action != nil {
268		app.Action = c.Action
269	} else {
270		app.Action = helpSubcommand.Action
271	}
272	app.OnUsageError = c.OnUsageError
273
274	for index, cc := range app.Commands {
275		app.Commands[index].commandNamePath = []string{c.Name, cc.Name}
276	}
277
278	return app.RunAsSubcommand(ctx)
279}
280
281// VisibleFlags returns a slice of the Flags with Hidden=false
282func (c *Command) VisibleFlags() []Flag {
283	return visibleFlags(c.Flags)
284}
285
286func (c *Command) appendFlag(fl Flag) {
287	if !hasFlag(c.Flags, fl) {
288		c.Flags = append(c.Flags, fl)
289	}
290}
291
292func hasCommand(commands []*Command, command *Command) bool {
293	for _, existing := range commands {
294		if command == existing {
295			return true
296		}
297	}
298
299	return false
300}
301