mirror of https://github.com/gogits/gogs.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
490 lines
12 KiB
490 lines
12 KiB
package cli |
|
|
|
import ( |
|
"fmt" |
|
"io" |
|
"io/ioutil" |
|
"os" |
|
"path/filepath" |
|
"sort" |
|
"time" |
|
) |
|
|
|
var ( |
|
changeLogURL = "https://github.com/urfave/cli/blob/master/CHANGELOG.md" |
|
appActionDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-action-signature", changeLogURL) |
|
runAndExitOnErrorDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-runandexitonerror", changeLogURL) |
|
|
|
contactSysadmin = "This is an error in the application. Please contact the distributor of this application if this is not you." |
|
|
|
errInvalidActionType = NewExitError("ERROR invalid Action type. "+ |
|
fmt.Sprintf("Must be `func(*Context`)` or `func(*Context) error). %s", contactSysadmin)+ |
|
fmt.Sprintf("See %s", appActionDeprecationURL), 2) |
|
) |
|
|
|
// App is the main structure of a cli application. It is recommended that |
|
// an app be created with the cli.NewApp() function |
|
type App struct { |
|
// The name of the program. Defaults to path.Base(os.Args[0]) |
|
Name string |
|
// Full name of command for help, defaults to Name |
|
HelpName string |
|
// Description of the program. |
|
Usage string |
|
// Text to override the USAGE section of help |
|
UsageText string |
|
// Description of the program argument format. |
|
ArgsUsage string |
|
// Version of the program |
|
Version string |
|
// Description of the program |
|
Description string |
|
// List of commands to execute |
|
Commands []Command |
|
// List of flags to parse |
|
Flags []Flag |
|
// Boolean to enable bash completion commands |
|
EnableBashCompletion bool |
|
// Boolean to hide built-in help command |
|
HideHelp bool |
|
// Boolean to hide built-in version flag and the VERSION section of help |
|
HideVersion bool |
|
// Populate on app startup, only gettable through method Categories() |
|
categories CommandCategories |
|
// An action to execute when the bash-completion flag is set |
|
BashComplete BashCompleteFunc |
|
// An action to execute before any subcommands are run, but after the context is ready |
|
// If a non-nil error is returned, no subcommands are run |
|
Before BeforeFunc |
|
// An action to execute after any subcommands are run, but after the subcommand has finished |
|
// It is run even if Action() panics |
|
After AfterFunc |
|
|
|
// The action to execute when no subcommands are specified |
|
// Expects a `cli.ActionFunc` but will accept the *deprecated* signature of `func(*cli.Context) {}` |
|
// *Note*: support for the deprecated `Action` signature will be removed in a future version |
|
Action interface{} |
|
|
|
// Execute this function if the proper command cannot be found |
|
CommandNotFound CommandNotFoundFunc |
|
// Execute this function if an usage error occurs |
|
OnUsageError OnUsageErrorFunc |
|
// Compilation date |
|
Compiled time.Time |
|
// List of all authors who contributed |
|
Authors []Author |
|
// Copyright of the binary if any |
|
Copyright string |
|
// Name of Author (Note: Use App.Authors, this is deprecated) |
|
Author string |
|
// Email of Author (Note: Use App.Authors, this is deprecated) |
|
Email string |
|
// Writer writer to write output to |
|
Writer io.Writer |
|
// ErrWriter writes error output |
|
ErrWriter io.Writer |
|
// Other custom info |
|
Metadata map[string]interface{} |
|
|
|
didSetup bool |
|
} |
|
|
|
// Tries to find out when this binary was compiled. |
|
// Returns the current time if it fails to find it. |
|
func compileTime() time.Time { |
|
info, err := os.Stat(os.Args[0]) |
|
if err != nil { |
|
return time.Now() |
|
} |
|
return info.ModTime() |
|
} |
|
|
|
// NewApp creates a new cli Application with some reasonable defaults for Name, |
|
// Usage, Version and Action. |
|
func NewApp() *App { |
|
return &App{ |
|
Name: filepath.Base(os.Args[0]), |
|
HelpName: filepath.Base(os.Args[0]), |
|
Usage: "A new cli application", |
|
UsageText: "", |
|
Version: "0.0.0", |
|
BashComplete: DefaultAppComplete, |
|
Action: helpCommand.Action, |
|
Compiled: compileTime(), |
|
Writer: os.Stdout, |
|
} |
|
} |
|
|
|
// Setup runs initialization code to ensure all data structures are ready for |
|
// `Run` or inspection prior to `Run`. It is internally called by `Run`, but |
|
// will return early if setup has already happened. |
|
func (a *App) Setup() { |
|
if a.didSetup { |
|
return |
|
} |
|
|
|
a.didSetup = true |
|
|
|
if a.Author != "" || a.Email != "" { |
|
a.Authors = append(a.Authors, Author{Name: a.Author, Email: a.Email}) |
|
} |
|
|
|
newCmds := []Command{} |
|
for _, c := range a.Commands { |
|
if c.HelpName == "" { |
|
c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name) |
|
} |
|
newCmds = append(newCmds, c) |
|
} |
|
a.Commands = newCmds |
|
|
|
if a.Command(helpCommand.Name) == nil && !a.HideHelp { |
|
a.Commands = append(a.Commands, helpCommand) |
|
if (HelpFlag != BoolFlag{}) { |
|
a.appendFlag(HelpFlag) |
|
} |
|
} |
|
|
|
if !a.HideVersion { |
|
a.appendFlag(VersionFlag) |
|
} |
|
|
|
a.categories = CommandCategories{} |
|
for _, command := range a.Commands { |
|
a.categories = a.categories.AddCommand(command.Category, command) |
|
} |
|
sort.Sort(a.categories) |
|
|
|
if a.Metadata == nil { |
|
a.Metadata = make(map[string]interface{}) |
|
} |
|
|
|
if a.Writer == nil { |
|
a.Writer = os.Stdout |
|
} |
|
} |
|
|
|
// Run is the entry point to the cli app. Parses the arguments slice and routes |
|
// to the proper flag/args combination |
|
func (a *App) Run(arguments []string) (err error) { |
|
a.Setup() |
|
|
|
// handle the completion flag separately from the flagset since |
|
// completion could be attempted after a flag, but before its value was put |
|
// on the command line. this causes the flagset to interpret the completion |
|
// flag name as the value of the flag before it which is undesirable |
|
// note that we can only do this because the shell autocomplete function |
|
// always appends the completion flag at the end of the command |
|
shellComplete, arguments := checkShellCompleteFlag(a, arguments) |
|
|
|
// parse flags |
|
set, err := flagSet(a.Name, a.Flags) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
set.SetOutput(ioutil.Discard) |
|
err = set.Parse(arguments[1:]) |
|
nerr := normalizeFlags(a.Flags, set) |
|
context := NewContext(a, set, nil) |
|
if nerr != nil { |
|
fmt.Fprintln(a.Writer, nerr) |
|
ShowAppHelp(context) |
|
return nerr |
|
} |
|
context.shellComplete = shellComplete |
|
|
|
if checkCompletions(context) { |
|
return nil |
|
} |
|
|
|
if err != nil { |
|
if a.OnUsageError != nil { |
|
err := a.OnUsageError(context, err, false) |
|
HandleExitCoder(err) |
|
return err |
|
} |
|
fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error()) |
|
ShowAppHelp(context) |
|
return err |
|
} |
|
|
|
if !a.HideHelp && checkHelp(context) { |
|
ShowAppHelp(context) |
|
return nil |
|
} |
|
|
|
if !a.HideVersion && checkVersion(context) { |
|
ShowVersion(context) |
|
return nil |
|
} |
|
|
|
if a.After != nil { |
|
defer func() { |
|
if afterErr := a.After(context); afterErr != nil { |
|
if err != nil { |
|
err = NewMultiError(err, afterErr) |
|
} else { |
|
err = afterErr |
|
} |
|
} |
|
}() |
|
} |
|
|
|
if a.Before != nil { |
|
beforeErr := a.Before(context) |
|
if beforeErr != nil { |
|
fmt.Fprintf(a.Writer, "%v\n\n", beforeErr) |
|
ShowAppHelp(context) |
|
HandleExitCoder(beforeErr) |
|
err = beforeErr |
|
return err |
|
} |
|
} |
|
|
|
args := context.Args() |
|
if args.Present() { |
|
name := args.First() |
|
c := a.Command(name) |
|
if c != nil { |
|
return c.Run(context) |
|
} |
|
} |
|
|
|
if a.Action == nil { |
|
a.Action = helpCommand.Action |
|
} |
|
|
|
// Run default Action |
|
err = HandleAction(a.Action, context) |
|
|
|
HandleExitCoder(err) |
|
return err |
|
} |
|
|
|
// RunAndExitOnError calls .Run() and exits non-zero if an error was returned |
|
// |
|
// Deprecated: instead you should return an error that fulfills cli.ExitCoder |
|
// to cli.App.Run. This will cause the application to exit with the given eror |
|
// code in the cli.ExitCoder |
|
func (a *App) RunAndExitOnError() { |
|
if err := a.Run(os.Args); err != nil { |
|
fmt.Fprintln(a.errWriter(), err) |
|
OsExiter(1) |
|
} |
|
} |
|
|
|
// RunAsSubcommand invokes the subcommand given the context, parses ctx.Args() to |
|
// generate command-specific flags |
|
func (a *App) RunAsSubcommand(ctx *Context) (err error) { |
|
// append help to commands |
|
if len(a.Commands) > 0 { |
|
if a.Command(helpCommand.Name) == nil && !a.HideHelp { |
|
a.Commands = append(a.Commands, helpCommand) |
|
if (HelpFlag != BoolFlag{}) { |
|
a.appendFlag(HelpFlag) |
|
} |
|
} |
|
} |
|
|
|
newCmds := []Command{} |
|
for _, c := range a.Commands { |
|
if c.HelpName == "" { |
|
c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name) |
|
} |
|
newCmds = append(newCmds, c) |
|
} |
|
a.Commands = newCmds |
|
|
|
// parse flags |
|
set, err := flagSet(a.Name, a.Flags) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
set.SetOutput(ioutil.Discard) |
|
err = set.Parse(ctx.Args().Tail()) |
|
nerr := normalizeFlags(a.Flags, set) |
|
context := NewContext(a, set, ctx) |
|
|
|
if nerr != nil { |
|
fmt.Fprintln(a.Writer, nerr) |
|
fmt.Fprintln(a.Writer) |
|
if len(a.Commands) > 0 { |
|
ShowSubcommandHelp(context) |
|
} else { |
|
ShowCommandHelp(ctx, context.Args().First()) |
|
} |
|
return nerr |
|
} |
|
|
|
if checkCompletions(context) { |
|
return nil |
|
} |
|
|
|
if err != nil { |
|
if a.OnUsageError != nil { |
|
err = a.OnUsageError(context, err, true) |
|
HandleExitCoder(err) |
|
return err |
|
} |
|
fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error()) |
|
ShowSubcommandHelp(context) |
|
return err |
|
} |
|
|
|
if len(a.Commands) > 0 { |
|
if checkSubcommandHelp(context) { |
|
return nil |
|
} |
|
} else { |
|
if checkCommandHelp(ctx, context.Args().First()) { |
|
return nil |
|
} |
|
} |
|
|
|
if a.After != nil { |
|
defer func() { |
|
afterErr := a.After(context) |
|
if afterErr != nil { |
|
HandleExitCoder(err) |
|
if err != nil { |
|
err = NewMultiError(err, afterErr) |
|
} else { |
|
err = afterErr |
|
} |
|
} |
|
}() |
|
} |
|
|
|
if a.Before != nil { |
|
beforeErr := a.Before(context) |
|
if beforeErr != nil { |
|
HandleExitCoder(beforeErr) |
|
err = beforeErr |
|
return err |
|
} |
|
} |
|
|
|
args := context.Args() |
|
if args.Present() { |
|
name := args.First() |
|
c := a.Command(name) |
|
if c != nil { |
|
return c.Run(context) |
|
} |
|
} |
|
|
|
// Run default Action |
|
err = HandleAction(a.Action, context) |
|
|
|
HandleExitCoder(err) |
|
return err |
|
} |
|
|
|
// Command returns the named command on App. Returns nil if the command does not exist |
|
func (a *App) Command(name string) *Command { |
|
for _, c := range a.Commands { |
|
if c.HasName(name) { |
|
return &c |
|
} |
|
} |
|
|
|
return nil |
|
} |
|
|
|
// Categories returns a slice containing all the categories with the commands they contain |
|
func (a *App) Categories() CommandCategories { |
|
return a.categories |
|
} |
|
|
|
// VisibleCategories returns a slice of categories and commands that are |
|
// Hidden=false |
|
func (a *App) VisibleCategories() []*CommandCategory { |
|
ret := []*CommandCategory{} |
|
for _, category := range a.categories { |
|
if visible := func() *CommandCategory { |
|
for _, command := range category.Commands { |
|
if !command.Hidden { |
|
return category |
|
} |
|
} |
|
return nil |
|
}(); visible != nil { |
|
ret = append(ret, visible) |
|
} |
|
} |
|
return ret |
|
} |
|
|
|
// VisibleCommands returns a slice of the Commands with Hidden=false |
|
func (a *App) VisibleCommands() []Command { |
|
ret := []Command{} |
|
for _, command := range a.Commands { |
|
if !command.Hidden { |
|
ret = append(ret, command) |
|
} |
|
} |
|
return ret |
|
} |
|
|
|
// VisibleFlags returns a slice of the Flags with Hidden=false |
|
func (a *App) VisibleFlags() []Flag { |
|
return visibleFlags(a.Flags) |
|
} |
|
|
|
func (a *App) hasFlag(flag Flag) bool { |
|
for _, f := range a.Flags { |
|
if flag == f { |
|
return true |
|
} |
|
} |
|
|
|
return false |
|
} |
|
|
|
func (a *App) errWriter() io.Writer { |
|
|
|
// When the app ErrWriter is nil use the package level one. |
|
if a.ErrWriter == nil { |
|
return ErrWriter |
|
} |
|
|
|
return a.ErrWriter |
|
} |
|
|
|
func (a *App) appendFlag(flag Flag) { |
|
if !a.hasFlag(flag) { |
|
a.Flags = append(a.Flags, flag) |
|
} |
|
} |
|
|
|
// Author represents someone who has contributed to a cli project. |
|
type Author struct { |
|
Name string // The Authors name |
|
Email string // The Authors email |
|
} |
|
|
|
// String makes Author comply to the Stringer interface, to allow an easy print in the templating process |
|
func (a Author) String() string { |
|
e := "" |
|
if a.Email != "" { |
|
e = " <" + a.Email + ">" |
|
} |
|
|
|
return fmt.Sprintf("%v%v", a.Name, e) |
|
} |
|
|
|
// HandleAction attempts to figure out which Action signature was used. If |
|
// it's an ActionFunc or a func with the legacy signature for Action, the func |
|
// is run! |
|
func HandleAction(action interface{}, context *Context) (err error) { |
|
if a, ok := action.(func(*Context) error); ok { |
|
return a(context) |
|
} else if a, ok := action.(func(*Context)); ok { // deprecated function signature |
|
a(context) |
|
return nil |
|
} else { |
|
return errInvalidActionType |
|
} |
|
}
|
|
|