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			_ = ShowCommandHelp(context, c.Name)
154			context.App.handleExitCoder(context, err)
155			return err
156		}
157	}
158
159	if c.Action == nil {
160		c.Action = helpSubcommand.Action
161	}
162
163	context.Command = c
164	err = c.Action(context)
165
166	if err != nil {
167		context.App.handleExitCoder(context, err)
168	}
169	return err
170}
171
172func (c *Command) newFlagSet() (*flag.FlagSet, error) {
173	return flagSet(c.Name, c.Flags)
174}
175
176func (c *Command) useShortOptionHandling() bool {
177	return c.UseShortOptionHandling
178}
179
180func (c *Command) parseFlags(args Args, shellComplete bool) (*flag.FlagSet, error) {
181	set, err := c.newFlagSet()
182	if err != nil {
183		return nil, err
184	}
185
186	if c.SkipFlagParsing {
187		return set, set.Parse(append([]string{"--"}, args.Tail()...))
188	}
189
190	err = parseIter(set, c, args.Tail(), shellComplete)
191	if err != nil {
192		return nil, err
193	}
194
195	err = normalizeFlags(c.Flags, set)
196	if err != nil {
197		return nil, err
198	}
199
200	return set, nil
201}
202
203// Names returns the names including short names and aliases.
204func (c *Command) Names() []string {
205	return append([]string{c.Name}, c.Aliases...)
206}
207
208// HasName returns true if Command.Name matches given name
209func (c *Command) HasName(name string) bool {
210	for _, n := range c.Names() {
211		if n == name {
212			return true
213		}
214	}
215	return false
216}
217
218func (c *Command) startApp(ctx *Context) error {
219	app := &App{
220		Metadata: ctx.App.Metadata,
221		Name:     fmt.Sprintf("%s %s", ctx.App.Name, c.Name),
222	}
223
224	if c.HelpName == "" {
225		app.HelpName = c.HelpName
226	} else {
227		app.HelpName = app.Name
228	}
229
230	app.Usage = c.Usage
231	app.Description = c.Description
232	app.ArgsUsage = c.ArgsUsage
233
234	// set CommandNotFound
235	app.CommandNotFound = ctx.App.CommandNotFound
236	app.CustomAppHelpTemplate = c.CustomHelpTemplate
237
238	// set the flags and commands
239	app.Commands = c.Subcommands
240	app.Flags = c.Flags
241	app.HideHelp = c.HideHelp
242	app.HideHelpCommand = c.HideHelpCommand
243
244	app.Version = ctx.App.Version
245	app.HideVersion = ctx.App.HideVersion
246	app.Compiled = ctx.App.Compiled
247	app.Writer = ctx.App.Writer
248	app.ErrWriter = ctx.App.ErrWriter
249	app.ExitErrHandler = ctx.App.ExitErrHandler
250	app.UseShortOptionHandling = ctx.App.UseShortOptionHandling
251
252	app.categories = newCommandCategories()
253	for _, command := range c.Subcommands {
254		app.categories.AddCommand(command.Category, command)
255	}
256
257	sort.Sort(app.categories.(*commandCategories))
258
259	// bash completion
260	app.EnableBashCompletion = ctx.App.EnableBashCompletion
261	if c.BashComplete != nil {
262		app.BashComplete = c.BashComplete
263	}
264
265	// set the actions
266	app.Before = c.Before
267	app.After = c.After
268	if c.Action != nil {
269		app.Action = c.Action
270	} else {
271		app.Action = helpSubcommand.Action
272	}
273	app.OnUsageError = c.OnUsageError
274
275	for index, cc := range app.Commands {
276		app.Commands[index].commandNamePath = []string{c.Name, cc.Name}
277	}
278
279	return app.RunAsSubcommand(ctx)
280}
281
282// VisibleFlags returns a slice of the Flags with Hidden=false
283func (c *Command) VisibleFlags() []Flag {
284	return visibleFlags(c.Flags)
285}
286
287func (c *Command) appendFlag(fl Flag) {
288	if !hasFlag(c.Flags, fl) {
289		c.Flags = append(c.Flags, fl)
290	}
291}
292
293func hasCommand(commands []*Command, command *Command) bool {
294	for _, existing := range commands {
295		if command == existing {
296			return true
297		}
298	}
299
300	return false
301}
302