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