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}