1package cli
2
3import (
4	"context"
5	"flag"
6	"fmt"
7	"io"
8	"os"
9	"path/filepath"
10	"sort"
11	"time"
12)
13
14var (
15	changeLogURL            = "https://github.com/urfave/cli/blob/master/docs/CHANGELOG.md"
16	appActionDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-action-signature", changeLogURL)
17	contactSysadmin         = "This is an error in the application.  Please contact the distributor of this application if this is not you."
18	errInvalidActionType    = NewExitError("ERROR invalid Action type. "+
19		fmt.Sprintf("Must be `func(*Context`)` or `func(*Context) error).  %s", contactSysadmin)+
20		fmt.Sprintf("See %s", appActionDeprecationURL), 2)
21)
22
23// App is the main structure of a cli application. It is recommended that
24// an app be created with the cli.NewApp() function
25type App struct {
26	// The name of the program. Defaults to path.Base(os.Args[0])
27	Name string
28	// Full name of command for help, defaults to Name
29	HelpName string
30	// Description of the program.
31	Usage string
32	// Text to override the USAGE section of help
33	UsageText string
34	// Description of the program argument format.
35	ArgsUsage string
36	// Version of the program
37	Version string
38	// Description of the program
39	Description string
40	// List of commands to execute
41	Commands []*Command
42	// List of flags to parse
43	Flags []Flag
44	// Boolean to enable bash completion commands
45	EnableBashCompletion bool
46	// Boolean to hide built-in help command and help flag
47	HideHelp bool
48	// Boolean to hide built-in help command but keep help flag.
49	// Ignored if HideHelp is true.
50	HideHelpCommand bool
51	// Boolean to hide built-in version flag and the VERSION section of help
52	HideVersion bool
53	// categories contains the categorized commands and is populated on app startup
54	categories CommandCategories
55	// An action to execute when the shell completion flag is set
56	BashComplete BashCompleteFunc
57	// An action to execute before any subcommands are run, but after the context is ready
58	// If a non-nil error is returned, no subcommands are run
59	Before BeforeFunc
60	// An action to execute after any subcommands are run, but after the subcommand has finished
61	// It is run even if Action() panics
62	After AfterFunc
63	// The action to execute when no subcommands are specified
64	Action ActionFunc
65	// Execute this function if the proper command cannot be found
66	CommandNotFound CommandNotFoundFunc
67	// Execute this function if a usage error occurs
68	OnUsageError OnUsageErrorFunc
69	// Compilation date
70	Compiled time.Time
71	// List of all authors who contributed
72	Authors []*Author
73	// Copyright of the binary if any
74	Copyright string
75	// Reader reader to write input to (useful for tests)
76	Reader io.Reader
77	// Writer writer to write output to
78	Writer io.Writer
79	// ErrWriter writes error output
80	ErrWriter io.Writer
81	// ExitErrHandler processes any error encountered while running an App before
82	// it is returned to the caller. If no function is provided, HandleExitCoder
83	// is used as the default behavior.
84	ExitErrHandler ExitErrHandlerFunc
85	// Other custom info
86	Metadata map[string]interface{}
87	// Carries a function which returns app specific info.
88	ExtraInfo func() map[string]string
89	// CustomAppHelpTemplate the text template for app help topic.
90	// cli.go uses text/template to render templates. You can
91	// render custom help text by setting this variable.
92	CustomAppHelpTemplate string
93	// Boolean to enable short-option handling so user can combine several
94	// single-character bool arguments into one
95	// i.e. foobar -o -v -> foobar -ov
96	UseShortOptionHandling bool
97
98	didSetup bool
99}
100
101// Tries to find out when this binary was compiled.
102// Returns the current time if it fails to find it.
103func compileTime() time.Time {
104	info, err := os.Stat(os.Args[0])
105	if err != nil {
106		return time.Now()
107	}
108	return info.ModTime()
109}
110
111// NewApp creates a new cli Application with some reasonable defaults for Name,
112// Usage, Version and Action.
113func NewApp() *App {
114	return &App{
115		Name:         filepath.Base(os.Args[0]),
116		HelpName:     filepath.Base(os.Args[0]),
117		Usage:        "A new cli application",
118		UsageText:    "",
119		BashComplete: DefaultAppComplete,
120		Action:       helpCommand.Action,
121		Compiled:     compileTime(),
122		Reader:       os.Stdin,
123		Writer:       os.Stdout,
124		ErrWriter:    os.Stderr,
125	}
126}
127
128// Setup runs initialization code to ensure all data structures are ready for
129// `Run` or inspection prior to `Run`.  It is internally called by `Run`, but
130// will return early if setup has already happened.
131func (a *App) Setup() {
132	if a.didSetup {
133		return
134	}
135
136	a.didSetup = true
137
138	if a.Name == "" {
139		a.Name = filepath.Base(os.Args[0])
140	}
141
142	if a.HelpName == "" {
143		a.HelpName = filepath.Base(os.Args[0])
144	}
145
146	if a.Usage == "" {
147		a.Usage = "A new cli application"
148	}
149
150	if a.Version == "" {
151		a.HideVersion = true
152	}
153
154	if a.BashComplete == nil {
155		a.BashComplete = DefaultAppComplete
156	}
157
158	if a.Action == nil {
159		a.Action = helpCommand.Action
160	}
161
162	if a.Compiled == (time.Time{}) {
163		a.Compiled = compileTime()
164	}
165
166	if a.Reader == nil {
167		a.Reader = os.Stdin
168	}
169
170	if a.Writer == nil {
171		a.Writer = os.Stdout
172	}
173
174	if a.ErrWriter == nil {
175		a.ErrWriter = os.Stderr
176	}
177
178	var newCommands []*Command
179
180	for _, c := range a.Commands {
181		if c.HelpName == "" {
182			c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name)
183		}
184		newCommands = append(newCommands, c)
185	}
186	a.Commands = newCommands
187
188	if a.Command(helpCommand.Name) == nil && !a.HideHelp {
189		if !a.HideHelpCommand {
190			a.appendCommand(helpCommand)
191		}
192
193		if HelpFlag != nil {
194			a.appendFlag(HelpFlag)
195		}
196	}
197
198	if !a.HideVersion {
199		a.appendFlag(VersionFlag)
200	}
201
202	a.categories = newCommandCategories()
203	for _, command := range a.Commands {
204		a.categories.AddCommand(command.Category, command)
205	}
206	sort.Sort(a.categories.(*commandCategories))
207
208	if a.Metadata == nil {
209		a.Metadata = make(map[string]interface{})
210	}
211}
212
213func (a *App) newFlagSet() (*flag.FlagSet, error) {
214	return flagSet(a.Name, a.Flags)
215}
216
217func (a *App) useShortOptionHandling() bool {
218	return a.UseShortOptionHandling
219}
220
221// Run is the entry point to the cli app. Parses the arguments slice and routes
222// to the proper flag/args combination
223func (a *App) Run(arguments []string) (err error) {
224	return a.RunContext(context.Background(), arguments)
225}
226
227// RunContext is like Run except it takes a Context that will be
228// passed to its commands and sub-commands. Through this, you can
229// propagate timeouts and cancellation requests
230func (a *App) RunContext(ctx context.Context, arguments []string) (err error) {
231	a.Setup()
232
233	// handle the completion flag separately from the flagset since
234	// completion could be attempted after a flag, but before its value was put
235	// on the command line. this causes the flagset to interpret the completion
236	// flag name as the value of the flag before it which is undesirable
237	// note that we can only do this because the shell autocomplete function
238	// always appends the completion flag at the end of the command
239	shellComplete, arguments := checkShellCompleteFlag(a, arguments)
240
241	set, err := a.newFlagSet()
242	if err != nil {
243		return err
244	}
245
246	err = parseIter(set, a, arguments[1:], shellComplete)
247	nerr := normalizeFlags(a.Flags, set)
248	context := NewContext(a, set, &Context{Context: ctx})
249	if nerr != nil {
250		_, _ = fmt.Fprintln(a.Writer, nerr)
251		_ = ShowAppHelp(context)
252		return nerr
253	}
254	context.shellComplete = shellComplete
255
256	if checkCompletions(context) {
257		return nil
258	}
259
260	if err != nil {
261		if a.OnUsageError != nil {
262			err := a.OnUsageError(context, err, false)
263			a.handleExitCoder(context, err)
264			return err
265		}
266		_, _ = fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
267		_ = ShowAppHelp(context)
268		return err
269	}
270
271	if !a.HideHelp && checkHelp(context) {
272		_ = ShowAppHelp(context)
273		return nil
274	}
275
276	if !a.HideVersion && checkVersion(context) {
277		ShowVersion(context)
278		return nil
279	}
280
281	cerr := checkRequiredFlags(a.Flags, context)
282	if cerr != nil {
283		_ = ShowAppHelp(context)
284		return cerr
285	}
286
287	if a.After != nil {
288		defer func() {
289			if afterErr := a.After(context); afterErr != nil {
290				if err != nil {
291					err = newMultiError(err, afterErr)
292				} else {
293					err = afterErr
294				}
295			}
296		}()
297	}
298
299	if a.Before != nil {
300		beforeErr := a.Before(context)
301		if beforeErr != nil {
302			a.handleExitCoder(context, beforeErr)
303			err = beforeErr
304			return err
305		}
306	}
307
308	args := context.Args()
309	if args.Present() {
310		name := args.First()
311		c := a.Command(name)
312		if c != nil {
313			return c.Run(context)
314		}
315	}
316
317	if a.Action == nil {
318		a.Action = helpCommand.Action
319	}
320
321	// Run default Action
322	err = a.Action(context)
323
324	a.handleExitCoder(context, err)
325	return err
326}
327
328// RunAndExitOnError calls .Run() and exits non-zero if an error was returned
329//
330// Deprecated: instead you should return an error that fulfills cli.ExitCoder
331// to cli.App.Run. This will cause the application to exit with the given eror
332// code in the cli.ExitCoder
333func (a *App) RunAndExitOnError() {
334	if err := a.Run(os.Args); err != nil {
335		_, _ = fmt.Fprintln(a.ErrWriter, err)
336		OsExiter(1)
337	}
338}
339
340// RunAsSubcommand invokes the subcommand given the context, parses ctx.Args() to
341// generate command-specific flags
342func (a *App) RunAsSubcommand(ctx *Context) (err error) {
343	// Setup also handles HideHelp and HideHelpCommand
344	a.Setup()
345
346	var newCmds []*Command
347	for _, c := range a.Commands {
348		if c.HelpName == "" {
349			c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name)
350		}
351		newCmds = append(newCmds, c)
352	}
353	a.Commands = newCmds
354
355	set, err := a.newFlagSet()
356	if err != nil {
357		return err
358	}
359
360	err = parseIter(set, a, ctx.Args().Tail(), ctx.shellComplete)
361	nerr := normalizeFlags(a.Flags, set)
362	context := NewContext(a, set, ctx)
363
364	if nerr != nil {
365		_, _ = fmt.Fprintln(a.Writer, nerr)
366		_, _ = fmt.Fprintln(a.Writer)
367		if len(a.Commands) > 0 {
368			_ = ShowSubcommandHelp(context)
369		} else {
370			_ = ShowCommandHelp(ctx, context.Args().First())
371		}
372		return nerr
373	}
374
375	if checkCompletions(context) {
376		return nil
377	}
378
379	if err != nil {
380		if a.OnUsageError != nil {
381			err = a.OnUsageError(context, err, true)
382			a.handleExitCoder(context, err)
383			return err
384		}
385		_, _ = fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
386		_ = ShowSubcommandHelp(context)
387		return err
388	}
389
390	if len(a.Commands) > 0 {
391		if checkSubcommandHelp(context) {
392			return nil
393		}
394	} else {
395		if checkCommandHelp(ctx, context.Args().First()) {
396			return nil
397		}
398	}
399
400	cerr := checkRequiredFlags(a.Flags, context)
401	if cerr != nil {
402		_ = ShowSubcommandHelp(context)
403		return cerr
404	}
405
406	if a.After != nil {
407		defer func() {
408			afterErr := a.After(context)
409			if afterErr != nil {
410				a.handleExitCoder(context, err)
411				if err != nil {
412					err = newMultiError(err, afterErr)
413				} else {
414					err = afterErr
415				}
416			}
417		}()
418	}
419
420	if a.Before != nil {
421		beforeErr := a.Before(context)
422		if beforeErr != nil {
423			a.handleExitCoder(context, beforeErr)
424			err = beforeErr
425			return err
426		}
427	}
428
429	args := context.Args()
430	if args.Present() {
431		name := args.First()
432		c := a.Command(name)
433		if c != nil {
434			return c.Run(context)
435		}
436	}
437
438	// Run default Action
439	err = a.Action(context)
440
441	a.handleExitCoder(context, err)
442	return err
443}
444
445// Command returns the named command on App. Returns nil if the command does not exist
446func (a *App) Command(name string) *Command {
447	for _, c := range a.Commands {
448		if c.HasName(name) {
449			return c
450		}
451	}
452
453	return nil
454}
455
456// VisibleCategories returns a slice of categories and commands that are
457// Hidden=false
458func (a *App) VisibleCategories() []CommandCategory {
459	ret := []CommandCategory{}
460	for _, category := range a.categories.Categories() {
461		if visible := func() CommandCategory {
462			if len(category.VisibleCommands()) > 0 {
463				return category
464			}
465			return nil
466		}(); visible != nil {
467			ret = append(ret, visible)
468		}
469	}
470	return ret
471}
472
473// VisibleCommands returns a slice of the Commands with Hidden=false
474func (a *App) VisibleCommands() []*Command {
475	var ret []*Command
476	for _, command := range a.Commands {
477		if !command.Hidden {
478			ret = append(ret, command)
479		}
480	}
481	return ret
482}
483
484// VisibleFlags returns a slice of the Flags with Hidden=false
485func (a *App) VisibleFlags() []Flag {
486	return visibleFlags(a.Flags)
487}
488
489func (a *App) appendFlag(fl Flag) {
490	if !hasFlag(a.Flags, fl) {
491		a.Flags = append(a.Flags, fl)
492	}
493}
494
495func (a *App) appendCommand(c *Command) {
496	if !hasCommand(a.Commands, c) {
497		a.Commands = append(a.Commands, c)
498	}
499}
500
501func (a *App) handleExitCoder(context *Context, err error) {
502	if a.ExitErrHandler != nil {
503		a.ExitErrHandler(context, err)
504	} else {
505		HandleExitCoder(err)
506	}
507}
508
509// Author represents someone who has contributed to a cli project.
510type Author struct {
511	Name  string // The Authors name
512	Email string // The Authors email
513}
514
515// String makes Author comply to the Stringer interface, to allow an easy print in the templating process
516func (a *Author) String() string {
517	e := ""
518	if a.Email != "" {
519		e = " <" + a.Email + ">"
520	}
521
522	return fmt.Sprintf("%v%v", a.Name, e)
523}
524
525// HandleAction attempts to figure out which Action signature was used.  If
526// it's an ActionFunc or a func with the legacy signature for Action, the func
527// is run!
528func HandleAction(action interface{}, context *Context) (err error) {
529	switch a := action.(type) {
530	case ActionFunc:
531		return a(context)
532	case func(*Context) error:
533		return a(context)
534	case func(*Context): // deprecated function signature
535		a(context)
536		return nil
537	}
538
539	return errInvalidActionType
540}
541