forked from cerc-io/plugeth
d78f9b834a
The Azure SDK doesn't support Go 1.5 anymore. We can't upgrade it until Go 1.8 comes out.
493 lines
12 KiB
Go
493 lines
12 KiB
Go
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.(ActionFunc); ok {
|
|
return a(context)
|
|
} else 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
|
|
}
|
|
}
|