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