|
|
|
@ -7,8 +7,8 @@ import (
|
|
|
|
|
"io"
|
|
|
|
|
"os"
|
|
|
|
|
"path/filepath"
|
|
|
|
|
"reflect"
|
|
|
|
|
"sort"
|
|
|
|
|
"strings"
|
|
|
|
|
"time"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
@ -21,6 +21,7 @@ var (
|
|
|
|
|
errInvalidActionType = NewExitError("ERROR invalid Action type. "+
|
|
|
|
|
fmt.Sprintf("Must be `func(*Context`)` or `func(*Context) error). %s", contactSysadmin)+
|
|
|
|
|
fmt.Sprintf("See %s", appActionDeprecationURL), 2)
|
|
|
|
|
ignoreFlagPrefix = "test." // this is to ignore test flags when adding flags from other packages
|
|
|
|
|
|
|
|
|
|
SuggestFlag SuggestFlagFunc = suggestFlag
|
|
|
|
|
SuggestCommand SuggestCommandFunc = suggestCommand
|
|
|
|
@ -104,14 +105,23 @@ type App struct {
|
|
|
|
|
// cli.go uses text/template to render templates. You can
|
|
|
|
|
// render custom help text by setting this variable.
|
|
|
|
|
CustomAppHelpTemplate string
|
|
|
|
|
// SliceFlagSeparator is used to customize the separator for SliceFlag, the default is ","
|
|
|
|
|
SliceFlagSeparator string
|
|
|
|
|
// Boolean to enable short-option handling so user can combine several
|
|
|
|
|
// single-character bool arguments into one
|
|
|
|
|
// i.e. foobar -o -v -> foobar -ov
|
|
|
|
|
UseShortOptionHandling bool
|
|
|
|
|
// Enable suggestions for commands and flags
|
|
|
|
|
Suggest bool
|
|
|
|
|
// Allows global flags set by libraries which use flag.XXXVar(...) directly
|
|
|
|
|
// to be parsed through this library
|
|
|
|
|
AllowExtFlags bool
|
|
|
|
|
// Treat all flags as normal arguments if true
|
|
|
|
|
SkipFlagParsing bool
|
|
|
|
|
|
|
|
|
|
didSetup bool
|
|
|
|
|
|
|
|
|
|
rootCommand *Command
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type SuggestFlagFunc func(flags []Flag, provided string, hideHelp bool) string
|
|
|
|
@ -194,12 +204,24 @@ func (a *App) Setup() {
|
|
|
|
|
a.ErrWriter = os.Stderr
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if a.AllowExtFlags {
|
|
|
|
|
// add global flags added by other packages
|
|
|
|
|
flag.VisitAll(func(f *flag.Flag) {
|
|
|
|
|
// skip test flags
|
|
|
|
|
if !strings.HasPrefix(f.Name, ignoreFlagPrefix) {
|
|
|
|
|
a.Flags = append(a.Flags, &extFlag{f})
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var newCommands []*Command
|
|
|
|
|
|
|
|
|
|
for _, c := range a.Commands {
|
|
|
|
|
if c.HelpName == "" {
|
|
|
|
|
c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name)
|
|
|
|
|
cname := c.Name
|
|
|
|
|
if c.HelpName != "" {
|
|
|
|
|
cname = c.HelpName
|
|
|
|
|
}
|
|
|
|
|
c.HelpName = fmt.Sprintf("%s %s", a.HelpName, cname)
|
|
|
|
|
|
|
|
|
|
c.flagCategories = newFlagCategoriesFromFlags(c.Flags)
|
|
|
|
|
newCommands = append(newCommands, c)
|
|
|
|
@ -238,6 +260,36 @@ func (a *App) Setup() {
|
|
|
|
|
if a.Metadata == nil {
|
|
|
|
|
a.Metadata = make(map[string]interface{})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(a.SliceFlagSeparator) != 0 {
|
|
|
|
|
defaultSliceFlagSeparator = a.SliceFlagSeparator
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a *App) newRootCommand() *Command {
|
|
|
|
|
return &Command{
|
|
|
|
|
Name: a.Name,
|
|
|
|
|
Usage: a.Usage,
|
|
|
|
|
UsageText: a.UsageText,
|
|
|
|
|
Description: a.Description,
|
|
|
|
|
ArgsUsage: a.ArgsUsage,
|
|
|
|
|
BashComplete: a.BashComplete,
|
|
|
|
|
Before: a.Before,
|
|
|
|
|
After: a.After,
|
|
|
|
|
Action: a.Action,
|
|
|
|
|
OnUsageError: a.OnUsageError,
|
|
|
|
|
Subcommands: a.Commands,
|
|
|
|
|
Flags: a.Flags,
|
|
|
|
|
flagCategories: a.flagCategories,
|
|
|
|
|
HideHelp: a.HideHelp,
|
|
|
|
|
HideHelpCommand: a.HideHelpCommand,
|
|
|
|
|
UseShortOptionHandling: a.UseShortOptionHandling,
|
|
|
|
|
HelpName: a.HelpName,
|
|
|
|
|
CustomHelpTemplate: a.CustomAppHelpTemplate,
|
|
|
|
|
categories: a.categories,
|
|
|
|
|
SkipFlagParsing: a.SkipFlagParsing,
|
|
|
|
|
isRoot: true,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a *App) newFlagSet() (*flag.FlagSet, error) {
|
|
|
|
@ -268,136 +320,20 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) {
|
|
|
|
|
// always appends the completion flag at the end of the command
|
|
|
|
|
shellComplete, arguments := checkShellCompleteFlag(a, arguments)
|
|
|
|
|
|
|
|
|
|
set, err := a.newFlagSet()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = parseIter(set, a, arguments[1:], shellComplete)
|
|
|
|
|
nerr := normalizeFlags(a.Flags, set)
|
|
|
|
|
cCtx := NewContext(a, set, &Context{Context: ctx})
|
|
|
|
|
if nerr != nil {
|
|
|
|
|
_, _ = fmt.Fprintln(a.Writer, nerr)
|
|
|
|
|
if !a.HideHelp {
|
|
|
|
|
_ = ShowAppHelp(cCtx)
|
|
|
|
|
}
|
|
|
|
|
return nerr
|
|
|
|
|
}
|
|
|
|
|
cCtx := NewContext(a, nil, &Context{Context: ctx})
|
|
|
|
|
cCtx.shellComplete = shellComplete
|
|
|
|
|
|
|
|
|
|
if checkCompletions(cCtx) {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
if a.OnUsageError != nil {
|
|
|
|
|
err := a.OnUsageError(cCtx, err, false)
|
|
|
|
|
a.handleExitCoder(cCtx, err)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
_, _ = fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
|
|
|
|
|
if a.Suggest {
|
|
|
|
|
if suggestion, err := a.suggestFlagFromError(err, ""); err == nil {
|
|
|
|
|
fmt.Fprintf(a.Writer, suggestion)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if !a.HideHelp {
|
|
|
|
|
_ = ShowAppHelp(cCtx)
|
|
|
|
|
}
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if a.After != nil && !cCtx.shellComplete {
|
|
|
|
|
defer func() {
|
|
|
|
|
if afterErr := a.After(cCtx); afterErr != nil {
|
|
|
|
|
if err != nil {
|
|
|
|
|
err = newMultiError(err, afterErr)
|
|
|
|
|
} else {
|
|
|
|
|
err = afterErr
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !a.HideHelp && checkHelp(cCtx) {
|
|
|
|
|
_ = ShowAppHelp(cCtx)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !a.HideVersion && checkVersion(cCtx) {
|
|
|
|
|
ShowVersion(cCtx)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cerr := cCtx.checkRequiredFlags(a.Flags)
|
|
|
|
|
if cerr != nil {
|
|
|
|
|
_ = ShowAppHelp(cCtx)
|
|
|
|
|
return cerr
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if a.Before != nil && !cCtx.shellComplete {
|
|
|
|
|
beforeErr := a.Before(cCtx)
|
|
|
|
|
if beforeErr != nil {
|
|
|
|
|
a.handleExitCoder(cCtx, beforeErr)
|
|
|
|
|
err = beforeErr
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err = runFlagActions(cCtx, a.Flags); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var c *Command
|
|
|
|
|
args := cCtx.Args()
|
|
|
|
|
if args.Present() {
|
|
|
|
|
name := args.First()
|
|
|
|
|
if a.validCommandName(name) {
|
|
|
|
|
c = a.Command(name)
|
|
|
|
|
} else {
|
|
|
|
|
hasDefault := a.DefaultCommand != ""
|
|
|
|
|
isFlagName := checkStringSliceIncludes(name, cCtx.FlagNames())
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
|
isDefaultSubcommand = false
|
|
|
|
|
defaultHasSubcommands = false
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if hasDefault {
|
|
|
|
|
dc := a.Command(a.DefaultCommand)
|
|
|
|
|
defaultHasSubcommands = len(dc.Subcommands) > 0
|
|
|
|
|
for _, dcSub := range dc.Subcommands {
|
|
|
|
|
if checkStringSliceIncludes(name, dcSub.Names()) {
|
|
|
|
|
isDefaultSubcommand = true
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if isFlagName || (hasDefault && (defaultHasSubcommands && isDefaultSubcommand)) {
|
|
|
|
|
argsWithDefault := a.argsWithDefaultCommand(args)
|
|
|
|
|
if !reflect.DeepEqual(args, argsWithDefault) {
|
|
|
|
|
c = a.Command(argsWithDefault.First())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if a.DefaultCommand != "" {
|
|
|
|
|
c = a.Command(a.DefaultCommand)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if c != nil {
|
|
|
|
|
return c.Run(cCtx)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if a.Action == nil {
|
|
|
|
|
a.Action = helpCommand.Action
|
|
|
|
|
}
|
|
|
|
|
a.rootCommand = a.newRootCommand()
|
|
|
|
|
cCtx.Command = a.rootCommand
|
|
|
|
|
|
|
|
|
|
// Run default Action
|
|
|
|
|
err = a.Action(cCtx)
|
|
|
|
|
return a.rootCommand.Run(cCtx, arguments...)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
a.handleExitCoder(cCtx, err)
|
|
|
|
|
return err
|
|
|
|
|
// This is a stub function to keep public API unchanged from old code
|
|
|
|
|
//
|
|
|
|
|
// Deprecated: use App.Run or App.RunContext
|
|
|
|
|
func (a *App) RunAsSubcommand(ctx *Context) (err error) {
|
|
|
|
|
return a.RunContext(ctx.Context, ctx.Args().Slice())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a *App) suggestFlagFromError(err error, command string) (string, error) {
|
|
|
|
@ -407,15 +343,17 @@ func (a *App) suggestFlagFromError(err error, command string) (string, error) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
flags := a.Flags
|
|
|
|
|
hideHelp := a.HideHelp
|
|
|
|
|
if command != "" {
|
|
|
|
|
cmd := a.Command(command)
|
|
|
|
|
if cmd == nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
flags = cmd.Flags
|
|
|
|
|
hideHelp = hideHelp || cmd.HideHelp
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
suggestion := SuggestFlag(flags, flag, a.HideHelp)
|
|
|
|
|
suggestion := SuggestFlag(flags, flag, hideHelp)
|
|
|
|
|
if len(suggestion) == 0 {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
@ -435,120 +373,6 @@ func (a *App) RunAndExitOnError() {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// RunAsSubcommand invokes the subcommand given the context, parses ctx.Args() to
|
|
|
|
|
// generate command-specific flags
|
|
|
|
|
func (a *App) RunAsSubcommand(ctx *Context) (err error) {
|
|
|
|
|
// Setup also handles HideHelp and HideHelpCommand
|
|
|
|
|
a.Setup()
|
|
|
|
|
|
|
|
|
|
var 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
|
|
|
|
|
|
|
|
|
|
set, err := a.newFlagSet()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = parseIter(set, a, ctx.Args().Tail(), ctx.shellComplete)
|
|
|
|
|
nerr := normalizeFlags(a.Flags, set)
|
|
|
|
|
cCtx := NewContext(a, set, ctx)
|
|
|
|
|
|
|
|
|
|
if nerr != nil {
|
|
|
|
|
_, _ = fmt.Fprintln(a.Writer, nerr)
|
|
|
|
|
_, _ = fmt.Fprintln(a.Writer)
|
|
|
|
|
if len(a.Commands) > 0 {
|
|
|
|
|
_ = ShowSubcommandHelp(cCtx)
|
|
|
|
|
} else {
|
|
|
|
|
_ = ShowCommandHelp(ctx, cCtx.Args().First())
|
|
|
|
|
}
|
|
|
|
|
return nerr
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if checkCompletions(cCtx) {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
if a.OnUsageError != nil {
|
|
|
|
|
err = a.OnUsageError(cCtx, err, true)
|
|
|
|
|
a.handleExitCoder(cCtx, err)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
_, _ = fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
|
|
|
|
|
if a.Suggest {
|
|
|
|
|
if suggestion, err := a.suggestFlagFromError(err, cCtx.Command.Name); err == nil {
|
|
|
|
|
fmt.Fprintf(a.Writer, suggestion)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_ = ShowSubcommandHelp(cCtx)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(a.Commands) > 0 {
|
|
|
|
|
if checkSubcommandHelp(cCtx) {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if checkCommandHelp(ctx, cCtx.Args().First()) {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cerr := cCtx.checkRequiredFlags(a.Flags)
|
|
|
|
|
if cerr != nil {
|
|
|
|
|
_ = ShowSubcommandHelp(cCtx)
|
|
|
|
|
return cerr
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if a.After != nil && !cCtx.shellComplete {
|
|
|
|
|
defer func() {
|
|
|
|
|
afterErr := a.After(cCtx)
|
|
|
|
|
if afterErr != nil {
|
|
|
|
|
a.handleExitCoder(cCtx, err)
|
|
|
|
|
if err != nil {
|
|
|
|
|
err = newMultiError(err, afterErr)
|
|
|
|
|
} else {
|
|
|
|
|
err = afterErr
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if a.Before != nil && !cCtx.shellComplete {
|
|
|
|
|
beforeErr := a.Before(cCtx)
|
|
|
|
|
if beforeErr != nil {
|
|
|
|
|
a.handleExitCoder(cCtx, beforeErr)
|
|
|
|
|
err = beforeErr
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err = runFlagActions(cCtx, a.Flags); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
args := cCtx.Args()
|
|
|
|
|
if args.Present() {
|
|
|
|
|
name := args.First()
|
|
|
|
|
c := a.Command(name)
|
|
|
|
|
if c != nil {
|
|
|
|
|
return c.Run(cCtx)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Run default Action
|
|
|
|
|
err = a.Action(cCtx)
|
|
|
|
|
|
|
|
|
|
a.handleExitCoder(cCtx, 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 {
|
|
|
|
|