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	// Boolean to hide built-in help command
50	HideHelp bool
51	// Boolean to hide this command from help or completion
52	Hidden bool
53
54	// Full name of command for help, defaults to full command name, including parent commands.
55	HelpName        string
56	commandNamePath []string
57}
58
59// FullName returns the full name of the command.
60// For subcommands this ensures that parent commands are part of the command path
61func (c Command) FullName() string {
62	if c.commandNamePath == nil {
63		return c.Name
64	}
65	return strings.Join(c.commandNamePath, " ")
66}
67
68// Commands is a slice of Command
69type Commands []Command
70
71// Run invokes the command given the context, parses ctx.Args() to generate command-specific flags
72func (c Command) Run(ctx *Context) (err error) {
73	if len(c.Subcommands) > 0 {
74		return c.startApp(ctx)
75	}
76
77	if !c.HideHelp && (HelpFlag != BoolFlag{}) {
78		// append help to flags
79		c.Flags = append(
80			c.Flags,
81			HelpFlag,
82		)
83	}
84
85	if ctx.App.EnableBashCompletion {
86		c.Flags = append(c.Flags, BashCompletionFlag)
87	}
88
89	set := flagSet(c.Name, c.Flags)
90	set.SetOutput(ioutil.Discard)
91
92	if !c.SkipFlagParsing {
93		firstFlagIndex := -1
94		terminatorIndex := -1
95		for index, arg := range ctx.Args() {
96			if arg == "--" {
97				terminatorIndex = index
98				break
99			} else if arg == "-" {
100				// Do nothing. A dash alone is not really a flag.
101				continue
102			} else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 {
103				firstFlagIndex = index
104			}
105		}
106
107		if firstFlagIndex > -1 {
108			args := ctx.Args()
109			regularArgs := make([]string, len(args[1:firstFlagIndex]))
110			copy(regularArgs, args[1:firstFlagIndex])
111
112			var flagArgs []string
113			if terminatorIndex > -1 {
114				flagArgs = args[firstFlagIndex:terminatorIndex]
115				regularArgs = append(regularArgs, args[terminatorIndex:]...)
116			} else {
117				flagArgs = args[firstFlagIndex:]
118			}
119
120			err = set.Parse(append(flagArgs, regularArgs...))
121		} else {
122			err = set.Parse(ctx.Args().Tail())
123		}
124	} else {
125		if c.SkipFlagParsing {
126			err = set.Parse(append([]string{"--"}, ctx.Args().Tail()...))
127		}
128	}
129
130	if err != nil {
131		if c.OnUsageError != nil {
132			err := c.OnUsageError(ctx, err, false)
133			HandleExitCoder(err)
134			return err
135		}
136		fmt.Fprintln(ctx.App.Writer, "Incorrect Usage.")
137		fmt.Fprintln(ctx.App.Writer)
138		ShowCommandHelp(ctx, c.Name)
139		return err
140	}
141
142	nerr := normalizeFlags(c.Flags, set)
143	if nerr != nil {
144		fmt.Fprintln(ctx.App.Writer, nerr)
145		fmt.Fprintln(ctx.App.Writer)
146		ShowCommandHelp(ctx, c.Name)
147		return nerr
148	}
149
150	context := NewContext(ctx.App, set, ctx)
151
152	if checkCommandCompletions(context, c.Name) {
153		return nil
154	}
155
156	if checkCommandHelp(context, c.Name) {
157		return nil
158	}
159
160	if c.After != nil {
161		defer func() {
162			afterErr := c.After(context)
163			if afterErr != nil {
164				HandleExitCoder(err)
165				if err != nil {
166					err = NewMultiError(err, afterErr)
167				} else {
168					err = afterErr
169				}
170			}
171		}()
172	}
173
174	if c.Before != nil {
175		err = c.Before(context)
176		if err != nil {
177			fmt.Fprintln(ctx.App.Writer, err)
178			fmt.Fprintln(ctx.App.Writer)
179			ShowCommandHelp(ctx, c.Name)
180			HandleExitCoder(err)
181			return err
182		}
183	}
184
185	context.Command = c
186	err = HandleAction(c.Action, context)
187
188	if err != nil {
189		HandleExitCoder(err)
190	}
191	return err
192}
193
194// Names returns the names including short names and aliases.
195func (c Command) Names() []string {
196	names := []string{c.Name}
197
198	if c.ShortName != "" {
199		names = append(names, c.ShortName)
200	}
201
202	return append(names, c.Aliases...)
203}
204
205// HasName returns true if Command.Name or Command.ShortName matches given name
206func (c Command) HasName(name string) bool {
207	for _, n := range c.Names() {
208		if n == name {
209			return true
210		}
211	}
212	return false
213}
214
215func (c Command) startApp(ctx *Context) error {
216	app := NewApp()
217	app.Metadata = ctx.App.Metadata
218	// set the name and usage
219	app.Name = fmt.Sprintf("%s %s", ctx.App.Name, c.Name)
220	if c.HelpName == "" {
221		app.HelpName = c.HelpName
222	} else {
223		app.HelpName = app.Name
224	}
225
226	if c.Description != "" {
227		app.Usage = c.Description
228	} else {
229		app.Usage = c.Usage
230	}
231
232	// set CommandNotFound
233	app.CommandNotFound = ctx.App.CommandNotFound
234
235	// set the flags and commands
236	app.Commands = c.Subcommands
237	app.Flags = c.Flags
238	app.HideHelp = c.HideHelp
239
240	app.Version = ctx.App.Version
241	app.HideVersion = ctx.App.HideVersion
242	app.Compiled = ctx.App.Compiled
243	app.Author = ctx.App.Author
244	app.Email = ctx.App.Email
245	app.Writer = ctx.App.Writer
246
247	app.categories = CommandCategories{}
248	for _, command := range c.Subcommands {
249		app.categories = app.categories.AddCommand(command.Category, command)
250	}
251
252	sort.Sort(app.categories)
253
254	// bash completion
255	app.EnableBashCompletion = ctx.App.EnableBashCompletion
256	if c.BashComplete != nil {
257		app.BashComplete = c.BashComplete
258	}
259
260	// set the actions
261	app.Before = c.Before
262	app.After = c.After
263	if c.Action != nil {
264		app.Action = c.Action
265	} else {
266		app.Action = helpSubcommand.Action
267	}
268
269	for index, cc := range app.Commands {
270		app.Commands[index].commandNamePath = []string{c.Name, cc.Name}
271	}
272
273	return app.RunAsSubcommand(ctx)
274}
275
276// VisibleFlags returns a slice of the Flags with Hidden=false
277func (c Command) VisibleFlags() []Flag {
278	return visibleFlags(c.Flags)
279}
280