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