chord/vendor/github.com/urfave/cli/v3/command.go
2025-03-15 20:42:37 -04:00

1300 lines
34 KiB
Go

package cli
import (
"bufio"
"context"
"flag"
"fmt"
"io"
"os"
"path/filepath"
"reflect"
"sort"
"strings"
"unicode"
)
const (
// ignoreFlagPrefix is to ignore test flags when adding flags from other packages
ignoreFlagPrefix = "test."
commandContextKey = contextKey("cli.context")
)
type contextKey string
// Command contains everything needed to run an application that
// accepts a string slice of arguments such as os.Args. A given
// Command may contain Flags and sub-commands in Commands.
type Command struct {
// The name of the command
Name string `json:"name"`
// A list of aliases for the command
Aliases []string `json:"aliases"`
// A short description of the usage of this command
Usage string `json:"usage"`
// Text to override the USAGE section of help
UsageText string `json:"usageText"`
// A short description of the arguments of this command
ArgsUsage string `json:"argsUsage"`
// Version of the command
Version string `json:"version"`
// Longer explanation of how the command works
Description string `json:"description"`
// DefaultCommand is the (optional) name of a command
// to run if no command names are passed as CLI arguments.
DefaultCommand string `json:"defaultCommand"`
// The category the command is part of
Category string `json:"category"`
// List of child commands
Commands []*Command `json:"commands"`
// List of flags to parse
Flags []Flag `json:"flags"`
// Boolean to hide built-in help command and help flag
HideHelp bool `json:"hideHelp"`
// Ignored if HideHelp is true.
HideHelpCommand bool `json:"hideHelpCommand"`
// Boolean to hide built-in version flag and the VERSION section of help
HideVersion bool `json:"hideVersion"`
// Boolean to enable shell completion commands
EnableShellCompletion bool `json:"-"`
// Shell Completion generation command name
ShellCompletionCommandName string `json:"-"`
// The function to call when checking for shell command completions
ShellComplete ShellCompleteFunc `json:"-"`
// 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 `json:"-"`
// An action to execute after any subcommands are run, but after the subcommand has finished
// It is run even if Action() panics
After AfterFunc `json:"-"`
// The function to call when this command is invoked
Action ActionFunc `json:"-"`
// Execute this function if the proper command cannot be found
CommandNotFound CommandNotFoundFunc `json:"-"`
// Execute this function if a usage error occurs.
OnUsageError OnUsageErrorFunc `json:"-"`
// Execute this function when an invalid flag is accessed from the context
InvalidFlagAccessHandler InvalidFlagAccessFunc `json:"-"`
// Boolean to hide this command from help or completion
Hidden bool `json:"hidden"`
// List of all authors who contributed (string or fmt.Stringer)
// TODO: ~string | fmt.Stringer when interface unions are available
Authors []any `json:"authors"`
// Copyright of the binary if any
Copyright string `json:"copyright"`
// Reader reader to write input to (useful for tests)
Reader io.Reader `json:"-"`
// Writer writer to write output to
Writer io.Writer `json:"-"`
// ErrWriter writes error output
ErrWriter io.Writer `json:"-"`
// ExitErrHandler processes any error encountered while running an App before
// it is returned to the caller. If no function is provided, HandleExitCoder
// is used as the default behavior.
ExitErrHandler ExitErrHandlerFunc `json:"-"`
// Other custom info
Metadata map[string]interface{} `json:"metadata"`
// Carries a function which returns app specific info.
ExtraInfo func() map[string]string `json:"-"`
// CustomRootCommandHelpTemplate the text template for app help topic.
// cli.go uses text/template to render templates. You can
// render custom help text by setting this variable.
CustomRootCommandHelpTemplate string `json:"-"`
// SliceFlagSeparator is used to customize the separator for SliceFlag, the default is ","
SliceFlagSeparator string `json:"sliceFlagSeparator"`
// DisableSliceFlagSeparator is used to disable SliceFlagSeparator, the default is false
DisableSliceFlagSeparator bool `json:"disableSliceFlagSeparator"`
// 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 `json:"useShortOptionHandling"`
// Enable suggestions for commands and flags
Suggest bool `json:"suggest"`
// Allows global flags set by libraries which use flag.XXXVar(...) directly
// to be parsed through this library
AllowExtFlags bool `json:"allowExtFlags"`
// Treat all flags as normal arguments if true
SkipFlagParsing bool `json:"skipFlagParsing"`
// CustomHelpTemplate the text template for the command help topic.
// cli.go uses text/template to render templates. You can
// render custom help text by setting this variable.
CustomHelpTemplate string `json:"-"`
// Use longest prefix match for commands
PrefixMatchCommands bool `json:"prefixMatchCommands"`
// Custom suggest command for matching
SuggestCommandFunc SuggestCommandFunc `json:"-"`
// Flag exclusion group
MutuallyExclusiveFlags []MutuallyExclusiveFlags `json:"mutuallyExclusiveFlags"`
// Arguments to parse for this command
Arguments []Argument `json:"arguments"`
// Whether to read arguments from stdin
// applicable to root command only
ReadArgsFromStdin bool `json:"readArgsFromStdin"`
// categories contains the categorized commands and is populated on app startup
categories CommandCategories
// flagCategories contains the categorized flags and is populated on app startup
flagCategories FlagCategories
// flags that have been applied in current parse
appliedFlags []Flag
// The parent of this command. This value will be nil for the
// command at the root of the graph.
parent *Command
// the flag.FlagSet for this command
flagSet *flag.FlagSet
// parsed args
parsedArgs Args
// track state of error handling
isInError bool
// track state of defaults
didSetupDefaults bool
// whether in shell completion mode
shellCompletion bool
}
// FullName returns the full name of the command.
// For commands with parents this ensures that the parent commands
// are part of the command path.
func (cmd *Command) FullName() string {
namePath := []string{}
if cmd.parent != nil {
namePath = append(namePath, cmd.parent.FullName())
}
return strings.Join(append(namePath, cmd.Name), " ")
}
func (cmd *Command) Command(name string) *Command {
for _, subCmd := range cmd.Commands {
if subCmd.HasName(name) {
return subCmd
}
}
return nil
}
func (cmd *Command) setupDefaults(osArgs []string) {
if cmd.didSetupDefaults {
tracef("already did setup (cmd=%[1]q)", cmd.Name)
return
}
cmd.didSetupDefaults = true
isRoot := cmd.parent == nil
tracef("isRoot? %[1]v (cmd=%[2]q)", isRoot, cmd.Name)
if cmd.ShellComplete == nil {
tracef("setting default ShellComplete (cmd=%[1]q)", cmd.Name)
cmd.ShellComplete = DefaultCompleteWithFlags
}
if cmd.Name == "" && isRoot {
name := filepath.Base(osArgs[0])
tracef("setting cmd.Name from first arg basename (cmd=%[1]q)", name)
cmd.Name = name
}
if cmd.Usage == "" && isRoot {
tracef("setting default Usage (cmd=%[1]q)", cmd.Name)
cmd.Usage = "A new cli application"
}
if cmd.Version == "" {
tracef("setting HideVersion=true due to empty Version (cmd=%[1]q)", cmd.Name)
cmd.HideVersion = true
}
if cmd.Action == nil {
tracef("setting default Action as help command action (cmd=%[1]q)", cmd.Name)
cmd.Action = helpCommandAction
}
if cmd.Reader == nil {
tracef("setting default Reader as os.Stdin (cmd=%[1]q)", cmd.Name)
cmd.Reader = os.Stdin
}
if cmd.Writer == nil {
tracef("setting default Writer as os.Stdout (cmd=%[1]q)", cmd.Name)
cmd.Writer = os.Stdout
}
if cmd.ErrWriter == nil {
tracef("setting default ErrWriter as os.Stderr (cmd=%[1]q)", cmd.Name)
cmd.ErrWriter = os.Stderr
}
if cmd.AllowExtFlags {
tracef("visiting all flags given AllowExtFlags=true (cmd=%[1]q)", cmd.Name)
// add global flags added by other packages
flag.VisitAll(func(f *flag.Flag) {
// skip test flags
if !strings.HasPrefix(f.Name, ignoreFlagPrefix) {
cmd.Flags = append(cmd.Flags, &extFlag{f})
}
})
}
for _, subCmd := range cmd.Commands {
tracef("setting sub-command (cmd=%[1]q) parent as self (cmd=%[2]q)", subCmd.Name, cmd.Name)
subCmd.parent = cmd
}
cmd.ensureHelp()
if !cmd.HideVersion && isRoot {
tracef("appending version flag (cmd=%[1]q)", cmd.Name)
cmd.appendFlag(VersionFlag)
}
if cmd.PrefixMatchCommands && cmd.SuggestCommandFunc == nil {
tracef("setting default SuggestCommandFunc (cmd=%[1]q)", cmd.Name)
cmd.SuggestCommandFunc = suggestCommand
}
if cmd.EnableShellCompletion || cmd.Root().shellCompletion {
completionCommand := buildCompletionCommand(cmd.Name)
if cmd.ShellCompletionCommandName != "" {
tracef(
"setting completion command name (%[1]q) from "+
"cmd.ShellCompletionCommandName (cmd=%[2]q)",
cmd.ShellCompletionCommandName, cmd.Name,
)
completionCommand.Name = cmd.ShellCompletionCommandName
}
tracef("appending completionCommand (cmd=%[1]q)", cmd.Name)
cmd.appendCommand(completionCommand)
}
tracef("setting command categories (cmd=%[1]q)", cmd.Name)
cmd.categories = newCommandCategories()
for _, subCmd := range cmd.Commands {
cmd.categories.AddCommand(subCmd.Category, subCmd)
}
tracef("sorting command categories (cmd=%[1]q)", cmd.Name)
sort.Sort(cmd.categories.(*commandCategories))
tracef("setting category on mutually exclusive flags (cmd=%[1]q)", cmd.Name)
for _, grp := range cmd.MutuallyExclusiveFlags {
grp.propagateCategory()
}
tracef("setting flag categories (cmd=%[1]q)", cmd.Name)
cmd.flagCategories = newFlagCategoriesFromFlags(cmd.allFlags())
if cmd.Metadata == nil {
tracef("setting default Metadata (cmd=%[1]q)", cmd.Name)
cmd.Metadata = map[string]any{}
}
if len(cmd.SliceFlagSeparator) != 0 {
tracef("setting defaultSliceFlagSeparator from cmd.SliceFlagSeparator (cmd=%[1]q)", cmd.Name)
defaultSliceFlagSeparator = cmd.SliceFlagSeparator
}
tracef("setting disableSliceFlagSeparator from cmd.DisableSliceFlagSeparator (cmd=%[1]q)", cmd.Name)
disableSliceFlagSeparator = cmd.DisableSliceFlagSeparator
}
func (cmd *Command) setupCommandGraph() {
tracef("setting up command graph (cmd=%[1]q)", cmd.Name)
for _, subCmd := range cmd.Commands {
subCmd.parent = cmd
subCmd.setupSubcommand()
subCmd.setupCommandGraph()
}
}
func (cmd *Command) setupSubcommand() {
tracef("setting up self as sub-command (cmd=%[1]q)", cmd.Name)
cmd.ensureHelp()
tracef("setting command categories (cmd=%[1]q)", cmd.Name)
cmd.categories = newCommandCategories()
for _, subCmd := range cmd.Commands {
cmd.categories.AddCommand(subCmd.Category, subCmd)
}
tracef("sorting command categories (cmd=%[1]q)", cmd.Name)
sort.Sort(cmd.categories.(*commandCategories))
tracef("setting category on mutually exclusive flags (cmd=%[1]q)", cmd.Name)
for _, grp := range cmd.MutuallyExclusiveFlags {
grp.propagateCategory()
}
tracef("setting flag categories (cmd=%[1]q)", cmd.Name)
cmd.flagCategories = newFlagCategoriesFromFlags(cmd.allFlags())
}
func (cmd *Command) hideHelp() bool {
tracef("hide help (cmd=%[1]q)", cmd.Name)
for c := cmd; c != nil; c = c.parent {
if c.HideHelp {
return true
}
}
return false
}
func (cmd *Command) ensureHelp() {
tracef("ensuring help (cmd=%[1]q)", cmd.Name)
helpCommand := buildHelpCommand(true)
if !cmd.hideHelp() {
if cmd.Command(helpCommand.Name) == nil {
if !cmd.HideHelpCommand {
tracef("appending helpCommand (cmd=%[1]q)", cmd.Name)
cmd.appendCommand(helpCommand)
}
}
if HelpFlag != nil {
tracef("appending HelpFlag (cmd=%[1]q)", cmd.Name)
cmd.appendFlag(HelpFlag)
}
}
}
func (cmd *Command) parseArgsFromStdin() ([]string, error) {
type state int
const (
stateSearchForToken state = -1
stateSearchForString state = 0
)
st := stateSearchForToken
linenum := 1
token := ""
args := []string{}
breader := bufio.NewReader(cmd.Reader)
outer:
for {
ch, _, err := breader.ReadRune()
if err == io.EOF {
switch st {
case stateSearchForToken:
if token != "--" {
args = append(args, token)
}
case stateSearchForString:
// make sure string is not empty
for _, t := range token {
if !unicode.IsSpace(t) {
args = append(args, token)
}
}
}
break outer
}
if err != nil {
return nil, err
}
switch st {
case stateSearchForToken:
if unicode.IsSpace(ch) || ch == '"' {
if ch == '\n' {
linenum++
}
if token != "" {
// end the processing here
if token == "--" {
break outer
}
args = append(args, token)
token = ""
}
if ch == '"' {
st = stateSearchForString
}
continue
}
token += string(ch)
case stateSearchForString:
if ch != '"' {
token += string(ch)
} else {
if token != "" {
args = append(args, token)
token = ""
}
/*else {
//TODO. Should we pass in empty strings ?
}*/
st = stateSearchForToken
}
}
}
tracef("parsed stdin args as %v (cmd=%[2]q)", args, cmd.Name)
return args, nil
}
// Run is the entry point to the command graph. The positional
// arguments are parsed according to the Flag and Command
// definitions and the matching Action functions are run.
func (cmd *Command) Run(ctx context.Context, osArgs []string) (deferErr error) {
tracef("running with arguments %[1]q (cmd=%[2]q)", osArgs, cmd.Name)
cmd.setupDefaults(osArgs)
if v, ok := ctx.Value(commandContextKey).(*Command); ok {
tracef("setting parent (cmd=%[1]q) command from context.Context value (cmd=%[2]q)", v.Name, cmd.Name)
cmd.parent = v
}
if cmd.parent == nil {
if cmd.ReadArgsFromStdin {
if args, err := cmd.parseArgsFromStdin(); err != nil {
return err
} else {
osArgs = append(osArgs, args...)
}
}
// 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
tracef("checking osArgs %v (cmd=%[2]q)", osArgs, cmd.Name)
cmd.shellCompletion, osArgs = checkShellCompleteFlag(cmd, osArgs)
tracef("setting cmd.shellCompletion=%[1]v from checkShellCompleteFlag (cmd=%[2]q)", cmd.shellCompletion && cmd.EnableShellCompletion, cmd.Name)
cmd.shellCompletion = cmd.EnableShellCompletion && cmd.shellCompletion
}
tracef("using post-checkShellCompleteFlag arguments %[1]q (cmd=%[2]q)", osArgs, cmd.Name)
tracef("setting self as cmd in context (cmd=%[1]q)", cmd.Name)
ctx = context.WithValue(ctx, commandContextKey, cmd)
if cmd.parent == nil {
cmd.setupCommandGraph()
}
args, err := cmd.parseFlags(&stringSliceArgs{v: osArgs})
tracef("using post-parse arguments %[1]q (cmd=%[2]q)", args, cmd.Name)
if checkCompletions(ctx, cmd) {
return nil
}
if err != nil {
tracef("setting deferErr from %[1]q (cmd=%[2]q)", err, cmd.Name)
deferErr = err
cmd.isInError = true
if cmd.OnUsageError != nil {
err = cmd.OnUsageError(ctx, cmd, err, cmd.parent != nil)
err = cmd.handleExitCoder(ctx, err)
return err
}
fmt.Fprintf(cmd.Root().ErrWriter, "Incorrect Usage: %s\n\n", err.Error())
if cmd.Suggest {
if suggestion, err := cmd.suggestFlagFromError(err, ""); err == nil {
fmt.Fprintf(cmd.Root().ErrWriter, "%s", suggestion)
}
}
if !cmd.hideHelp() {
if cmd.parent == nil {
tracef("running ShowAppHelp")
if err := ShowAppHelp(cmd); err != nil {
tracef("SILENTLY IGNORING ERROR running ShowAppHelp %[1]v (cmd=%[2]q)", err, cmd.Name)
}
} else {
tracef("running ShowCommandHelp with %[1]q", cmd.Name)
if err := ShowCommandHelp(ctx, cmd, cmd.Name); err != nil {
tracef("SILENTLY IGNORING ERROR running ShowCommandHelp with %[1]q %[2]v", cmd.Name, err)
}
}
}
return err
}
if cmd.checkHelp() {
return helpCommandAction(ctx, cmd)
} else {
tracef("no help is wanted (cmd=%[1]q)", cmd.Name)
}
if cmd.parent == nil && !cmd.HideVersion && checkVersion(cmd) {
ShowVersion(cmd)
return nil
}
if cmd.After != nil && !cmd.Root().shellCompletion {
defer func() {
if err := cmd.After(ctx, cmd); err != nil {
err = cmd.handleExitCoder(ctx, err)
if deferErr != nil {
deferErr = newMultiError(deferErr, err)
} else {
deferErr = err
}
}
}()
}
for _, grp := range cmd.MutuallyExclusiveFlags {
if err := grp.check(cmd); err != nil {
_ = ShowSubcommandHelp(cmd)
return err
}
}
if cmd.Before != nil && !cmd.Root().shellCompletion {
if bctx, err := cmd.Before(ctx, cmd); err != nil {
deferErr = cmd.handleExitCoder(ctx, err)
return deferErr
} else if bctx != nil {
ctx = bctx
}
}
tracef("running flag actions (cmd=%[1]q)", cmd.Name)
if err := cmd.runFlagActions(ctx); err != nil {
return err
}
var subCmd *Command
if args.Present() {
tracef("checking positional args %[1]q (cmd=%[2]q)", args, cmd.Name)
name := args.First()
tracef("using first positional argument as sub-command name=%[1]q (cmd=%[2]q)", name, cmd.Name)
if cmd.SuggestCommandFunc != nil {
name = cmd.SuggestCommandFunc(cmd.Commands, name)
}
subCmd = cmd.Command(name)
if subCmd == nil {
hasDefault := cmd.DefaultCommand != ""
isFlagName := checkStringSliceIncludes(name, cmd.FlagNames())
if hasDefault {
tracef("using default command=%[1]q (cmd=%[2]q)", cmd.DefaultCommand, cmd.Name)
}
if isFlagName || hasDefault {
argsWithDefault := cmd.argsWithDefaultCommand(args)
tracef("using default command args=%[1]q (cmd=%[2]q)", argsWithDefault, cmd.Name)
if !reflect.DeepEqual(args, argsWithDefault) {
subCmd = cmd.Command(argsWithDefault.First())
}
}
}
} else if cmd.parent == nil && cmd.DefaultCommand != "" {
tracef("no positional args present; checking default command %[1]q (cmd=%[2]q)", cmd.DefaultCommand, cmd.Name)
if dc := cmd.Command(cmd.DefaultCommand); dc != cmd {
subCmd = dc
}
}
if subCmd != nil {
tracef("running sub-command %[1]q with arguments %[2]q (cmd=%[3]q)", subCmd.Name, cmd.Args(), cmd.Name)
return subCmd.Run(ctx, cmd.Args().Slice())
}
if cmd.Action == nil {
cmd.Action = helpCommandAction
} else {
if err := cmd.checkAllRequiredFlags(); err != nil {
cmd.isInError = true
_ = ShowSubcommandHelp(cmd)
return err
}
if len(cmd.Arguments) > 0 {
rargs := cmd.Args().Slice()
tracef("calling argparse with %[1]v", rargs)
for _, arg := range cmd.Arguments {
var err error
rargs, err = arg.Parse(rargs)
if err != nil {
tracef("calling with %[1]v (cmd=%[2]q)", err, cmd.Name)
return err
}
}
cmd.parsedArgs = &stringSliceArgs{v: rargs}
}
}
if err := cmd.Action(ctx, cmd); err != nil {
tracef("calling handleExitCoder with %[1]v (cmd=%[2]q)", err, cmd.Name)
deferErr = cmd.handleExitCoder(ctx, err)
}
tracef("returning deferErr (cmd=%[1]q) %[2]q", cmd.Name, deferErr)
return deferErr
}
func (cmd *Command) checkHelp() bool {
tracef("checking if help is wanted (cmd=%[1]q)", cmd.Name)
if HelpFlag == nil {
return false
}
for _, name := range HelpFlag.Names() {
if cmd.Bool(name) {
return true
}
}
return false
}
func (cmd *Command) newFlagSet() (*flag.FlagSet, error) {
allFlags := cmd.allFlags()
cmd.appliedFlags = append(cmd.appliedFlags, allFlags...)
tracef("making new flag set (cmd=%[1]q)", cmd.Name)
return newFlagSet(cmd.Name, allFlags)
}
func (cmd *Command) allFlags() []Flag {
var flags []Flag
flags = append(flags, cmd.Flags...)
for _, grpf := range cmd.MutuallyExclusiveFlags {
for _, f1 := range grpf.Flags {
flags = append(flags, f1...)
}
}
return flags
}
// useShortOptionHandling traverses Lineage() for *any* ancestors
// with UseShortOptionHandling
func (cmd *Command) useShortOptionHandling() bool {
for _, pCmd := range cmd.Lineage() {
if pCmd.UseShortOptionHandling {
return true
}
}
return false
}
func (cmd *Command) suggestFlagFromError(err error, commandName string) (string, error) {
fl, parseErr := flagFromError(err)
if parseErr != nil {
return "", err
}
flags := cmd.Flags
hideHelp := cmd.hideHelp()
if commandName != "" {
subCmd := cmd.Command(commandName)
if subCmd == nil {
return "", err
}
flags = subCmd.Flags
hideHelp = hideHelp || subCmd.HideHelp
}
suggestion := SuggestFlag(flags, fl, hideHelp)
if len(suggestion) == 0 {
return "", err
}
return fmt.Sprintf(SuggestDidYouMeanTemplate, suggestion) + "\n\n", nil
}
func (cmd *Command) parseFlags(args Args) (Args, error) {
tracef("parsing flags from arguments %[1]q (cmd=%[2]q)", args, cmd.Name)
cmd.parsedArgs = nil
if v, err := cmd.newFlagSet(); err != nil {
return args, err
} else {
cmd.flagSet = v
}
if cmd.SkipFlagParsing {
tracef("skipping flag parsing (cmd=%[1]q)", cmd.Name)
return cmd.Args(), cmd.flagSet.Parse(append([]string{"--"}, args.Tail()...))
}
tracef("walking command lineage for persistent flags (cmd=%[1]q)", cmd.Name)
for pCmd := cmd.parent; pCmd != nil; pCmd = pCmd.parent {
tracef(
"checking ancestor command=%[1]q for persistent flags (cmd=%[2]q)",
pCmd.Name, cmd.Name,
)
for _, fl := range pCmd.Flags {
flNames := fl.Names()
pfl, ok := fl.(LocalFlag)
if !ok || pfl.IsLocal() {
tracef("skipping non-persistent flag %[1]q (cmd=%[2]q)", flNames, cmd.Name)
continue
}
tracef(
"checking for applying persistent flag=%[1]q pCmd=%[2]q (cmd=%[3]q)",
flNames, pCmd.Name, cmd.Name,
)
applyPersistentFlag := true
cmd.flagSet.VisitAll(func(f *flag.Flag) {
for _, name := range flNames {
if name == f.Name {
applyPersistentFlag = false
break
}
}
})
if !applyPersistentFlag {
tracef("not applying as persistent flag=%[1]q (cmd=%[2]q)", flNames, cmd.Name)
continue
}
tracef("applying as persistent flag=%[1]q (cmd=%[2]q)", flNames, cmd.Name)
if err := fl.Apply(cmd.flagSet); err != nil {
return cmd.Args(), err
}
tracef("appending to applied flags flag=%[1]q (cmd=%[2]q)", flNames, cmd.Name)
cmd.appliedFlags = append(cmd.appliedFlags, fl)
}
}
tracef("parsing flags iteratively tail=%[1]q (cmd=%[2]q)", args.Tail(), cmd.Name)
defer tracef("done parsing flags (cmd=%[1]q)", cmd.Name)
rargs := args.Tail()
posArgs := []string{}
for {
tracef("rearrange:1 (cmd=%[1]q) %[2]q", cmd.Name, rargs)
for {
tracef("rearrange:2 (cmd=%[1]q) %[2]q %[3]q", cmd.Name, posArgs, rargs)
// no more args to parse. Break out of inner loop
if len(rargs) == 0 {
break
}
if strings.TrimSpace(rargs[0]) == "" {
break
}
// stop parsing once we see a "--"
if rargs[0] == "--" {
posArgs = append(posArgs, rargs...)
cmd.parsedArgs = &stringSliceArgs{posArgs}
return cmd.parsedArgs, nil
}
// let flagset parse this
if rargs[0][0] == '-' {
break
}
tracef("rearrange-3 (cmd=%[1]q) check %[2]q", cmd.Name, rargs[0])
// if there is a command by that name let the command handle the
// rest of the parsing
if cmd.Command(rargs[0]) != nil {
posArgs = append(posArgs, rargs...)
cmd.parsedArgs = &stringSliceArgs{posArgs}
return cmd.parsedArgs, nil
}
posArgs = append(posArgs, rargs[0])
// if this is the sole argument then
// break from inner loop
if len(rargs) == 1 {
rargs = []string{}
break
}
rargs = rargs[1:]
}
if err := parseIter(cmd.flagSet, cmd, rargs, cmd.Root().shellCompletion); err != nil {
posArgs = append(posArgs, cmd.flagSet.Args()...)
tracef("returning-1 (cmd=%[1]q) args %[2]q", cmd.Name, posArgs)
cmd.parsedArgs = &stringSliceArgs{posArgs}
return cmd.parsedArgs, err
}
tracef("rearrange-4 (cmd=%[1]q) check %[2]q", cmd.Name, cmd.flagSet.Args())
rargs = cmd.flagSet.Args()
if len(rargs) == 0 || strings.TrimSpace(rargs[0]) == "" || rargs[0] == "-" {
break
}
}
posArgs = append(posArgs, cmd.flagSet.Args()...)
tracef("returning-2 (cmd=%[1]q) args %[2]q", cmd.Name, posArgs)
cmd.parsedArgs = &stringSliceArgs{posArgs}
return cmd.parsedArgs, nil
}
// Names returns the names including short names and aliases.
func (cmd *Command) Names() []string {
return append([]string{cmd.Name}, cmd.Aliases...)
}
// HasName returns true if Command.Name matches given name
func (cmd *Command) HasName(name string) bool {
for _, n := range cmd.Names() {
if n == name {
return true
}
}
return false
}
// VisibleCategories returns a slice of categories and commands that are
// Hidden=false
func (cmd *Command) VisibleCategories() []CommandCategory {
ret := []CommandCategory{}
for _, category := range cmd.categories.Categories() {
if visible := func() CommandCategory {
if len(category.VisibleCommands()) > 0 {
return category
}
return nil
}(); visible != nil {
ret = append(ret, visible)
}
}
return ret
}
// VisibleCommands returns a slice of the Commands with Hidden=false
func (cmd *Command) VisibleCommands() []*Command {
var ret []*Command
for _, command := range cmd.Commands {
if command.Hidden || command.Name == helpName {
continue
}
ret = append(ret, command)
}
return ret
}
// VisibleFlagCategories returns a slice containing all the visible flag categories with the flags they contain
func (cmd *Command) VisibleFlagCategories() []VisibleFlagCategory {
if cmd.flagCategories == nil {
cmd.flagCategories = newFlagCategoriesFromFlags(cmd.allFlags())
}
return cmd.flagCategories.VisibleCategories()
}
// VisibleFlags returns a slice of the Flags with Hidden=false
func (cmd *Command) VisibleFlags() []Flag {
return visibleFlags(cmd.allFlags())
}
func (cmd *Command) appendFlag(fl Flag) {
if !hasFlag(cmd.Flags, fl) {
cmd.Flags = append(cmd.Flags, fl)
}
}
// VisiblePersistentFlags returns a slice of [LocalFlag] with Persistent=true and Hidden=false.
func (cmd *Command) VisiblePersistentFlags() []Flag {
var flags []Flag
for _, fl := range cmd.Root().Flags {
pfl, ok := fl.(LocalFlag)
if !ok || pfl.IsLocal() {
continue
}
flags = append(flags, fl)
}
return visibleFlags(flags)
}
func (cmd *Command) appendCommand(aCmd *Command) {
if !hasCommand(cmd.Commands, aCmd) {
aCmd.parent = cmd
cmd.Commands = append(cmd.Commands, aCmd)
}
}
func (cmd *Command) handleExitCoder(ctx context.Context, err error) error {
if cmd.parent != nil {
return cmd.parent.handleExitCoder(ctx, err)
}
if cmd.ExitErrHandler != nil {
cmd.ExitErrHandler(ctx, cmd, err)
return err
}
HandleExitCoder(err)
return err
}
func (cmd *Command) argsWithDefaultCommand(oldArgs Args) Args {
if cmd.DefaultCommand != "" {
rawArgs := append([]string{cmd.DefaultCommand}, oldArgs.Slice()...)
newArgs := &stringSliceArgs{v: rawArgs}
return newArgs
}
return oldArgs
}
// Root returns the Command at the root of the graph
func (cmd *Command) Root() *Command {
if cmd.parent == nil {
return cmd
}
return cmd.parent.Root()
}
func (cmd *Command) lookupFlag(name string) Flag {
for _, pCmd := range cmd.Lineage() {
for _, f := range pCmd.Flags {
for _, n := range f.Names() {
if n == name {
tracef("flag found for name %[1]q (cmd=%[2]q)", name, cmd.Name)
return f
}
}
}
}
tracef("flag NOT found for name %[1]q (cmd=%[2]q)", name, cmd.Name)
return nil
}
func (cmd *Command) lookupFlagSet(name string) *flag.FlagSet {
for _, pCmd := range cmd.Lineage() {
if pCmd.flagSet == nil {
continue
}
if f := pCmd.flagSet.Lookup(name); f != nil {
tracef("matching flag set found for name %[1]q (cmd=%[2]q)", name, cmd.Name)
return pCmd.flagSet
}
}
tracef("matching flag set NOT found for name %[1]q (cmd=%[2]q)", name, cmd.Name)
cmd.onInvalidFlag(context.TODO(), name)
return nil
}
func (cmd *Command) checkRequiredFlag(f Flag) (bool, string) {
if rf, ok := f.(RequiredFlag); ok && rf.IsRequired() {
flagPresent := false
flagName := ""
for _, key := range f.Names() {
// use the first name to return since that is the
// primary flag name
if flagName == "" {
flagName = key
}
if cmd.IsSet(strings.TrimSpace(key)) {
flagPresent = true
break
}
}
if !flagPresent && flagName != "" {
return false, flagName
}
}
return true, ""
}
func (cmd *Command) checkAllRequiredFlags() requiredFlagsErr {
for pCmd := cmd; pCmd != nil; pCmd = pCmd.parent {
if err := pCmd.checkRequiredFlags(); err != nil {
return err
}
}
return nil
}
func (cmd *Command) checkRequiredFlags() requiredFlagsErr {
tracef("checking for required flags (cmd=%[1]q)", cmd.Name)
missingFlags := []string{}
for _, f := range cmd.appliedFlags {
if ok, name := cmd.checkRequiredFlag(f); !ok {
missingFlags = append(missingFlags, name)
}
}
if len(missingFlags) != 0 {
tracef("found missing required flags %[1]q (cmd=%[2]q)", missingFlags, cmd.Name)
return &errRequiredFlags{missingFlags: missingFlags}
}
tracef("all required flags set (cmd=%[1]q)", cmd.Name)
return nil
}
func (cmd *Command) onInvalidFlag(ctx context.Context, name string) {
for cmd != nil {
if cmd.InvalidFlagAccessHandler != nil {
cmd.InvalidFlagAccessHandler(ctx, cmd, name)
break
}
cmd = cmd.parent
}
}
// NumFlags returns the number of flags set
func (cmd *Command) NumFlags() int {
return cmd.flagSet.NFlag()
}
// Set sets a context flag to a value.
func (cmd *Command) Set(name, value string) error {
if fs := cmd.lookupFlagSet(name); fs != nil {
return fs.Set(name, value)
}
return fmt.Errorf("no such flag -%s", name)
}
// IsSet determines if the flag was actually set
func (cmd *Command) IsSet(name string) bool {
flSet := cmd.lookupFlagSet(name)
if flSet == nil {
return false
}
isSet := false
flSet.Visit(func(f *flag.Flag) {
if f.Name == name {
isSet = true
}
})
if isSet {
tracef("flag with name %[1]q found via flag set lookup (cmd=%[2]q)", name, cmd.Name)
return true
}
fl := cmd.lookupFlag(name)
if fl == nil {
tracef("flag with name %[1]q NOT found; assuming not set (cmd=%[2]q)", name, cmd.Name)
return false
}
isSet = fl.IsSet()
if isSet {
tracef("flag with name %[1]q is set (cmd=%[2]q)", name, cmd.Name)
} else {
tracef("flag with name %[1]q is NOT set (cmd=%[2]q)", name, cmd.Name)
}
return isSet
}
// LocalFlagNames returns a slice of flag names used in this
// command.
func (cmd *Command) LocalFlagNames() []string {
names := []string{}
cmd.flagSet.Visit(makeFlagNameVisitor(&names))
// Check the flags which have been set via env or file
for _, f := range cmd.Flags {
if f.IsSet() {
names = append(names, f.Names()...)
}
}
// Sort out the duplicates since flag could be set via multiple
// paths
m := map[string]struct{}{}
uniqNames := []string{}
for _, name := range names {
if _, ok := m[name]; !ok {
m[name] = struct{}{}
uniqNames = append(uniqNames, name)
}
}
return uniqNames
}
// FlagNames returns a slice of flag names used by the this command
// and all of its parent commands.
func (cmd *Command) FlagNames() []string {
names := cmd.LocalFlagNames()
if cmd.parent != nil {
names = append(cmd.parent.FlagNames(), names...)
}
return names
}
// Lineage returns *this* command and all of its ancestor commands
// in order from child to parent
func (cmd *Command) Lineage() []*Command {
lineage := []*Command{cmd}
if cmd.parent != nil {
lineage = append(lineage, cmd.parent.Lineage()...)
}
return lineage
}
// Count returns the num of occurrences of this flag
func (cmd *Command) Count(name string) int {
if fs := cmd.lookupFlagSet(name); fs != nil {
if cf, ok := fs.Lookup(name).Value.(Countable); ok {
return cf.Count()
}
}
return 0
}
// Value returns the value of the flag corresponding to `name`
func (cmd *Command) Value(name string) interface{} {
if fs := cmd.lookupFlagSet(name); fs != nil {
tracef("value found for name %[1]q (cmd=%[2]q)", name, cmd.Name)
return fs.Lookup(name).Value.(flag.Getter).Get()
}
tracef("value NOT found for name %[1]q (cmd=%[2]q)", name, cmd.Name)
return nil
}
// Args returns the command line arguments associated with the
// command.
func (cmd *Command) Args() Args {
if cmd.parsedArgs != nil {
return cmd.parsedArgs
}
return &stringSliceArgs{v: cmd.flagSet.Args()}
}
// NArg returns the number of the command line arguments.
func (cmd *Command) NArg() int {
return cmd.Args().Len()
}
func hasCommand(commands []*Command, command *Command) bool {
for _, existing := range commands {
if command == existing {
return true
}
}
return false
}
func (cmd *Command) runFlagActions(ctx context.Context) error {
for _, fl := range cmd.appliedFlags {
isSet := false
// check only local flagset for running local flag actions
for _, name := range fl.Names() {
cmd.flagSet.Visit(func(f *flag.Flag) {
if f.Name == name {
isSet = true
}
})
if isSet {
break
}
}
// If the flag hasnt been set on cmd line then we need to further
// check if it has been set via other means. If however it has
// been set by other means but it is persistent(and not set via current cmd)
// do not run the flag action
if !isSet {
if !fl.IsSet() {
continue
}
if pf, ok := fl.(LocalFlag); ok && !pf.IsLocal() {
continue
}
}
if af, ok := fl.(ActionableFlag); ok {
if err := af.RunAction(ctx, cmd); err != nil {
return err
}
}
}
return nil
}
func checkStringSliceIncludes(want string, sSlice []string) bool {
found := false
for _, s := range sSlice {
if want == s {
found = true
break
}
}
return found
}
func makeFlagNameVisitor(names *[]string) func(*flag.Flag) {
return func(f *flag.Flag) {
nameParts := strings.Split(f.Name, ",")
name := strings.TrimSpace(nameParts[0])
for _, part := range nameParts {
part = strings.TrimSpace(part)
if len(part) > len(name) {
name = part
}
}
if name != "" {
*names = append(*names, name)
}
}
}