From 2efc073c1448507c3f69ef26c001494f2e61a97e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Oct 2026 22:19:32 +0000 Subject: [PATCH] build(deps): Bump github.com/urfave/cli/v3 from 3.10.1 to 3.14.0 Bumps [github.com/urfave/cli/v3](https://github.com/urfave/cli) from 3.10.1 to 3.14.0. - [Release notes](https://github.com/urfave/cli/releases) - [Changelog](https://github.com/urfave/cli/blob/main/docs/CHANGELOG.md) - [Commits](https://github.com/urfave/cli/compare/v3.10.1...v3.14.0) --- updated-dependencies: - dependency-name: github.com/urfave/cli/v3 dependency-version: 3.14.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 +- vendor/github.com/urfave/cli/v3/README.md | 21 ++ vendor/github.com/urfave/cli/v3/args.go | 23 +- vendor/github.com/urfave/cli/v3/command.go | 105 +++++- .../github.com/urfave/cli/v3/command_parse.go | 53 ++- .../github.com/urfave/cli/v3/command_run.go | 92 ++++- .../github.com/urfave/cli/v3/command_setup.go | 30 +- vendor/github.com/urfave/cli/v3/completion.go | 2 +- vendor/github.com/urfave/cli/v3/docs.go | 3 +- vendor/github.com/urfave/cli/v3/errors.go | 22 ++ vendor/github.com/urfave/cli/v3/fish.go | 20 +- vendor/github.com/urfave/cli/v3/flag.go | 19 + .../urfave/cli/v3/flag_bool_with_inverse.go | 47 ++- vendor/github.com/urfave/cli/v3/flag_impl.go | 90 ++++- .../github.com/urfave/cli/v3/flag_map_impl.go | 15 +- vendor/github.com/urfave/cli/v3/flag_mutex.go | 79 +++- vendor/github.com/urfave/cli/v3/flag_text.go | 86 +++++ .../urfave/cli/v3/flag_timestamp.go | 18 +- vendor/github.com/urfave/cli/v3/funcs.go | 7 + .../urfave/cli/v3/godoc-current.txt | 90 +++++ vendor/github.com/urfave/cli/v3/help.go | 140 ++------ .../urfave/cli/v3/help_notemplate.go | 338 ++++++++++++++++++ .../github.com/urfave/cli/v3/help_template.go | 204 +++++++++++ .../urfave/cli/v3/mkdocs-requirements.txt | 6 +- .../github.com/urfave/cli/v3/suggestions.go | 11 +- vendor/github.com/urfave/cli/v3/template.go | 88 ----- .../github.com/urfave/cli/v3/value_source.go | 1 + vendor/modules.txt | 2 +- 29 files changed, 1316 insertions(+), 302 deletions(-) create mode 100644 vendor/github.com/urfave/cli/v3/flag_text.go create mode 100644 vendor/github.com/urfave/cli/v3/help_notemplate.go create mode 100644 vendor/github.com/urfave/cli/v3/help_template.go diff --git a/go.mod b/go.mod index 7747f380f..32cfd0698 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( github.com/sirupsen/logrus v1.10.2 github.com/stretchr/testify v1.12.1 github.com/urfave/cli-altsrc/v3 v3.1.0 - github.com/urfave/cli/v3 v3.10.1 + github.com/urfave/cli/v3 v3.14.0 golang.org/x/sys v0.48.0 tags.cncf.io/container-device-interface v1.1.1 tags.cncf.io/container-device-interface/specs-go v1.1.1 diff --git a/go.sum b/go.sum index 41f60db9c..7f796c2a1 100644 --- a/go.sum +++ b/go.sum @@ -52,8 +52,8 @@ github.com/tetratelabs/wazero v1.11.0 h1:+gKemEuKCTevU4d7ZTzlsvgd1uaToIDtlQlmNbw github.com/tetratelabs/wazero v1.11.0/go.mod h1:eV28rsN8Q+xwjogd7f4/Pp4xFxO7uOGbLcD/LzB1wiU= github.com/urfave/cli-altsrc/v3 v3.1.0 h1:6E5+kXeAWmRxXlPgdEVf9VqVoTJ2MJci0UMpUi/w/bA= github.com/urfave/cli-altsrc/v3 v3.1.0/go.mod h1:VcWVTGXcL3nrXUDJZagHAeUX702La3PKeWav7KpISqA= -github.com/urfave/cli/v3 v3.10.1 h1:7Kx9H50hrHbRbyxgO1KP6/BcbiGRz0uYh5YyQ30JEEY= -github.com/urfave/cli/v3 v3.10.1/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso= +github.com/urfave/cli/v3 v3.14.0 h1:a8414NQlHJs0c/iBsulKLzlES0n/lEAskbL2LKpU4/s= +github.com/urfave/cli/v3 v3.14.0/go.mod h1:vXn6HxPNccJSzQr2QvwVncOKrgYGIHU0HY5h8B2nQj4= go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= diff --git a/vendor/github.com/urfave/cli/v3/README.md b/vendor/github.com/urfave/cli/v3/README.md index e04c7295b..a5396e0e0 100644 --- a/vendor/github.com/urfave/cli/v3/README.md +++ b/vendor/github.com/urfave/cli/v3/README.md @@ -28,6 +28,27 @@ command line tools in Go featuring: See the hosted documentation website at . Contents of this website are built from the [`./docs`](./docs) directory. +## Build tags + +You can use the following build tags: + +### `urfave_cli_no_template` + +When set, the default help and fish completion are rendered without using +`text/template`, which makes the Go linker disable dead code elimination (see +[Binary Size](https://cli.urfave.org/v3/binary-size/#urfave_cli_no_template)). +The output is the same, but help templates can not be customized: + +- `RootCommandHelpTemplate`, `CommandHelpTemplate`, `SubcommandHelpTemplate` + and `FishCompletionTemplate` are constants; +- if the default help printer is used with a custom template (such as + `CustomRootCommandHelpTemplate` or `CustomHelpTemplate`), it panics; +- custom template functions other than `wrap` and `wrapAt` are ignored. + +Replacing `cli.HelpPrinter` works as usual, except it receives `"root"`, +`"command"` or `"subcommand"` as `templ` instead of the default template text +(a custom template is passed as is). + ## Support Check the [Q&A discussions]. If you don't find answer to your question, [create diff --git a/vendor/github.com/urfave/cli/v3/args.go b/vendor/github.com/urfave/cli/v3/args.go index b3acfd5cc..0dad20991 100644 --- a/vendor/github.com/urfave/cli/v3/args.go +++ b/vendor/github.com/urfave/cli/v3/args.go @@ -77,6 +77,11 @@ type Argument interface { Get() any } +type requiredArgument interface { + name() string + required() bool +} + // AnyArguments to differentiate between no arguments(nil) vs aleast one var AnyArguments = []Argument{ &StringArgs{ @@ -89,6 +94,7 @@ type ArgumentBase[T any, C any, VC ValueCreator[T, C]] struct { Value T `json:"value"` // the default value of this argument Destination *T `json:"-"` // the destination point for this argument UsageText string `json:"usageText"` // the usage text to show + Required bool `json:"required"` // whether the argument is required or not Config C `json:"config"` // config for this argument similar to Flag Config value *T @@ -98,18 +104,33 @@ func (a *ArgumentBase[T, C, VC]) HasName(s string) bool { return s == a.Name } +func (a *ArgumentBase[T, C, VC]) name() string { + return a.Name +} + +func (a *ArgumentBase[T, C, VC]) required() bool { + return a.Required +} + func (a *ArgumentBase[T, C, VC]) Usage() string { if a.UsageText != "" { return a.UsageText } - usageFormat := "%[1]s" + usageFormat := "[%[1]s]" + if a.Required { + usageFormat = "%[1]s" + } return fmt.Sprintf(usageFormat, a.Name) } func (a *ArgumentBase[T, C, VC]) Parse(s []string) ([]string, error) { tracef("calling arg%[1] parse with args %[2]", a.Name, s) + if a.Required && len(s) == 0 { + return s, &errRequiredArguments{missingArguments: []string{a.Name}} + } + var vc VC var t T value := vc.Create(a.Value, &t, a.Config) diff --git a/vendor/github.com/urfave/cli/v3/command.go b/vendor/github.com/urfave/cli/v3/command.go index 4cd907a55..ade9f021b 100644 --- a/vendor/github.com/urfave/cli/v3/command.go +++ b/vendor/github.com/urfave/cli/v3/command.go @@ -46,6 +46,9 @@ type Command struct { Flags []Flag `json:"flags"` // Boolean to hide built-in help command and help flag HideHelp bool `json:"hideHelp"` + // Boolean to hide the built-in help command. Applies to this command and + // all of its subcommands: as with HideHelp, a true value is inherited and + // a subcommand cannot turn it back off. // Ignored if HideHelp is true. HideHelpCommand bool `json:"hideHelpCommand"` // Boolean to hide built-in version flag and the VERSION section of help @@ -64,6 +67,11 @@ type Command struct { // 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:"-"` + // An action to validate arguments before the command is run. If non-nil, it + // is called before Before and Action. If the current command does not set + // ArgValidator, the nearest ancestor that does is used instead. + // Returning a non-nil error short-circuits the command. + ArgValidator ArgValidatorFunc `json:"-"` // The function to call when this command is invoked Action ActionFunc `json:"-"` // Execute this function if the proper command cannot be found @@ -74,6 +82,10 @@ type Command struct { InvalidFlagAccessHandler InvalidFlagAccessFunc `json:"-"` // Boolean to hide this command from help or completion Hidden bool `json:"hidden"` + // Deprecation message for this command. If non-empty, a warning is + // printed to ErrWriter when the command is invoked. Setting it does not + // hide the command; set Hidden as well for that. + Deprecated string `json:"deprecated"` // List of all authors who contributed (string or fmt.Stringer) // TODO: ~string | fmt.Stringer when interface unions are available Authors []any `json:"authors"` @@ -157,12 +169,18 @@ type Command struct { didSetupDefaults bool // whether in shell completion mode shellCompletion bool + // whether the shell completion request came after a "--" separator, + // after which only positional arguments are accepted and nothing is + // suggested. The request is still a completion, never a command run. + shellCompletionPastDoubleDash bool // whether global help flag was added globaHelpFlagAdded bool // whether global version flag was added globaVersionFlagAdded bool // generated root version flag versionFlag Flag + // generated help flag + helpFlag Flag // whether this is a completion command isCompletionCommand bool // whether this is the built-in help command @@ -182,7 +200,31 @@ func (cmd *Command) Command(name string) *Command { func (cmd *Command) checkHelp() bool { tracef("checking if help is wanted (cmd=%[1]q)", cmd.Name) - return HelpFlag != nil && slices.ContainsFunc(HelpFlag.Names(), cmd.Bool) + helpFlag := cmd.helpFlag + if helpFlag == nil && HelpFlag != nil { + // A user flag that took the name of the help flag, such as + // HelpFlag itself listed in Flags, still shows help. + helpFlag = cmd.lFlag(HelpFlag.Names()[0]) + } + return helpFlag != nil && slices.ContainsFunc(helpFlag.Names(), cmd.Bool) +} + +func (cmd *Command) checkDuplicateFlagNames() error { + seen := map[string]struct{}{} + for _, fl := range cmd.allFlags() { + // Go flags from other packages (AllowExtFlags) can't be renamed + // by the user, so a user flag with the same name wins instead. + if _, ok := fl.(*extFlag); ok { + continue + } + for _, name := range fl.Names() { + if _, ok := seen[name]; ok { + return fmt.Errorf("flag %q defined multiple times in command %q", name, cmd.FullName()) + } + seen[name] = struct{}{} + } + } + return nil } func (cmd *Command) allFlags() []Flag { @@ -298,12 +340,25 @@ func (cmd *Command) VisiblePersistentFlags() []Flag { return nil } var flags []Flag - for _, fl := range cmd.Root().Flags { - pfl, ok := fl.(LocalFlag) - if !ok || pfl.IsLocal() { - continue + lineage := cmd.Lineage() + for i := len(lineage) - 1; i > 0; i-- { + for _, fl := range lineage[i].allFlags() { + pfl, ok := fl.(LocalFlag) + if !ok || pfl.IsLocal() { + continue + } + applies := true + for _, name := range fl.Names() { + if cmd.lookupFlag(name) != fl { + applies = false + break + } + } + if !applies { + continue + } + flags = append(flags, fl) } - flags = append(flags, fl) } return visibleFlags(flags) } @@ -454,6 +509,40 @@ func (cmd *Command) checkRequiredFlags() requiredFlagsErr { return nil } +func (cmd *Command) checkRequiredArguments() requiredArgumentsErr { + // The help and completion commands are allowed to run without + // enforcement of required arguments, since they do not invoke user + // actions that depend on those argument values. + if cmd.builtInHelp || cmd.isCompletionCommand { + return nil + } + + tracef("checking for required arguments (cmd=%[1]q)", cmd.Name) + + missingArguments := []string{} + // This count-based precheck relies on required single-value arguments + // being declared before optional or multi-value arguments, as documented. + // Argument.Parse remains the backstop for unsupported orderings. + providedArguments := cmd.Args().Len() + + for index, arg := range cmd.Arguments { + requiredArg, ok := arg.(requiredArgument) + if ok && requiredArg.required() && index >= providedArguments { + missingArguments = append(missingArguments, requiredArg.name()) + } + } + + if len(missingArguments) != 0 { + tracef("found missing required arguments %[1]q (cmd=%[2]q)", missingArguments, cmd.Name) + + return &errRequiredArguments{missingArguments: missingArguments} + } + + tracef("all required arguments set (cmd=%[1]q)", cmd.Name) + + return nil +} + func (cmd *Command) onInvalidFlag(ctx context.Context, name string) { for cmd != nil { if cmd.InvalidFlagAccessHandler != nil { @@ -619,7 +708,9 @@ func (cmd *Command) Value(name string) any { } // Args returns the command line arguments associated with the -// command. +// command. If the command declares named Arguments, the arguments +// consumed by them are not included in the returned Args and should +// be retrieved via the command.{Type}Arg(name) functions instead. func (cmd *Command) Args() Args { return cmd.parsedArgs } diff --git a/vendor/github.com/urfave/cli/v3/command_parse.go b/vendor/github.com/urfave/cli/v3/command_parse.go index 939ad90b8..9265d8279 100644 --- a/vendor/github.com/urfave/cli/v3/command_parse.go +++ b/vendor/github.com/urfave/cli/v3/command_parse.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" "unicode" + "unicode/utf8" ) const ( @@ -114,15 +115,16 @@ func (cmd *Command) parseFlags(args Args) (Args, error) { return &stringSliceArgs{posArgs}, nil } - posArgs = append(posArgs, firstArg) + // firstArg is a trimmed copy that classifies the argument; the + // argument itself is what the action receives, byte for byte. + posArgs = append(posArgs, rargs[0]) continue } numMinuses := 1 - // this is same as firstArg == "-" - if len(firstArg) == 1 { - posArgs = append(posArgs, firstArg) - break + if firstArg == "-" { + posArgs = append(posArgs, rargs[0]) + continue } shortOptionHandling := cmd.useShortOptionHandling() @@ -131,7 +133,7 @@ func (cmd *Command) parseFlags(args Args) (Args, error) { if firstArg[1] == '-' { numMinuses++ shortOptionHandling = false - } else if !unicode.IsLetter(rune(firstArg[1])) { + } else if firstRune, _ := utf8.DecodeRuneInString(firstArg[1:]); !unicode.IsLetter(firstRune) { // this is not a flag tracef("parseFlags not a unicode letter. Stop parsing") posArgs = append(posArgs, rargs...) @@ -145,7 +147,9 @@ func (cmd *Command) parseFlags(args Args) (Args, error) { valFromEqual := false tracef("flagName:1 (fName=%[1]q)", flagName) if index := strings.Index(flagName, "="); index != -1 { - flagVal = flagName[index+1:] + // Classify the flag using the trimmed token, but leave value + // whitespace handling to the flag's value parser. + _, flagVal, _ = strings.Cut(rargs[0], "=") flagName = flagName[:index] valFromEqual = true } @@ -208,28 +212,42 @@ func (cmd *Command) parseFlags(args Args) (Args, error) { return &stringSliceArgs{posArgs}, fmt.Errorf("%s%s", providedButNotDefinedErrMsg, flagName) } - // try to split the flags + // ranging over flagName yields byte offsets, so the last flag starts + // where its rune starts, which is not len-1 for a multi-byte rune + _, lastSize := utf8.DecodeLastRuneInString(flagName) + lastIndex := len(flagName) - lastSize + + // every flag of the group has to exist before any of them is set, + // otherwise a group holding an unknown flag is reported by whichever + // of its flags happens to be examined first for index, c := range flagName { - tracef("processing flag (fName=%[1]q)", string(c)) - if sf := cmd.lookupFlag(string(c)); sf == nil { + if sf := cmd.lookupAppliedFlag(string(c)); sf == nil { if index == 0 && cmd.DefaultCommand != "" { posArgs = append(posArgs, rargs...) return &stringSliceArgs{posArgs}, nil } return &stringSliceArgs{posArgs}, fmt.Errorf("%s%s", providedButNotDefinedErrMsg, flagName) - } else if fb, ok := sf.(boolFlag); ok && fb.IsBoolFlag() { + } + } + + // try to split the flags + for index, c := range flagName { + tracef("processing flag (fName=%[1]q)", string(c)) + // the check above has already found every flag of the group + sf := cmd.lookupAppliedFlag(string(c)) + if fb, ok := sf.(boolFlag); ok && fb.IsBoolFlag() { fv := flagVal - if index == (len(flagName)-1) && flagVal == "" { + if index == lastIndex && flagVal == "" { fv = "true" } if fv == "" { fv = "true" } - if err := cmd.set(flagName, sf, fv); err != nil { + if err := cmd.set(string(c), sf, fv); err != nil { tracef("processing flag.2 (fName=%[1]q)", string(c)) return &stringSliceArgs{posArgs}, err } - } else if index == len(flagName)-1 { // last flag can take an arg + } else if index == lastIndex { // last flag can take an arg if flagVal == "" { if len(rargs) == 1 { return &stringSliceArgs{posArgs}, fmt.Errorf("%s%s", argumentNotProvidedErrMsg, string(c)) @@ -238,10 +256,15 @@ func (cmd *Command) parseFlags(args Args) (Args, error) { rargs = rargs[1:] } tracef("parseFlags (flagName %[1]q) (flagVal %[2]q)", flagName, flagVal) - if err := cmd.set(flagName, sf, flagVal); err != nil { + if err := cmd.set(string(c), sf, flagVal); err != nil { tracef("processing flag.4 (fName=%[1]q)", string(c)) return &stringSliceArgs{posArgs}, err } + } else { + // only the last flag of a group can take an argument, so a flag + // that needs one has nothing to read it from here + tracef("processing flag.5 (fName=%[1]q)", string(c)) + return &stringSliceArgs{posArgs}, fmt.Errorf("%s%s", argumentNotProvidedErrMsg, string(c)) } } } diff --git a/vendor/github.com/urfave/cli/v3/command_run.go b/vendor/github.com/urfave/cli/v3/command_run.go index 8d5907151..9036ec2cf 100644 --- a/vendor/github.com/urfave/cli/v3/command_run.go +++ b/vendor/github.com/urfave/cli/v3/command_run.go @@ -125,6 +125,11 @@ func (cmd *Command) run(ctx context.Context, osArgs []string) (_ context.Context // 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) + // completion request state is per-run: a Command answering several + // requests (tests, REPL, embedded use) must not carry one request + // into the next + cmd.shellCompletion = false + cmd.shellCompletionPastDoubleDash = false cmd.shellCompletion, osArgs = checkShellCompleteFlag(cmd, osArgs) tracef("setting cmd.shellCompletion=%[1]v from checkShellCompleteFlag (cmd=%[2]q)", cmd.shellCompletion && cmd.EnableShellCompletion, cmd.Name) @@ -138,6 +143,9 @@ func (cmd *Command) run(ctx context.Context, osArgs []string) (_ context.Context if cmd.parent == nil { cmd.setupCommandGraph() + if err := cmd.Walk((*Command).checkDuplicateFlagNames); err != nil { + return ctx, err + } } var rargs Args = &stringSliceArgs{v: osArgs} @@ -327,6 +335,16 @@ func (cmd *Command) run(ctx context.Context, osArgs []string) (_ context.Context // First, resolve the chain of nested commands up to the parent. cmdChain := commandChain(cmd) + printDeprecationWarnings(cmdChain) + + // Run ArgValidator from the nearest ancestor that sets one. + if validator := findArgValidator(cmd); validator != nil { + if err := validator(ctx, cmd); err != nil { + deferErr = cmd.handleExitCoder(ctx, err) + return ctx, deferErr + } + } + // Run Before actions in order. if ctx, err = runBefore(ctx, cmdChain); err != nil { deferErr = err @@ -343,21 +361,14 @@ func (cmd *Command) run(ctx context.Context, osArgs []string) (_ context.Context } } + var requiredErr error if err := cmd.checkAllRequiredFlags(); err != nil { - cmd.isInError = true - if cmd.OnUsageError != nil { - err = cmd.OnUsageError(ctx, cmd, err, cmd.parent != nil) - } else { - fmt.Fprintf(cmd.Root().ErrWriter, "Incorrect Usage: %s\n\n", err.Error()) - if cmd.parent == nil { - _ = ShowRootCommandHelp(cmd) - } else { - if err := ShowCommandHelp(ctx, cmd.parent, cmd.Name); err != nil { - _ = ShowSubcommandHelp(cmd) - } - } - } - return ctx, err + requiredErr = err + } else if err := cmd.checkRequiredArguments(); err != nil { + requiredErr = err + } + if requiredErr != nil { + return cmd.handleRequiredError(ctx, requiredErr) } // Run the command action. @@ -369,6 +380,9 @@ func (cmd *Command) run(ctx context.Context, osArgs []string) (_ context.Context rargs, err = arg.Parse(rargs) if err != nil { tracef("calling with %[1]v (cmd=%[2]q)", err, cmd.Name) + if _, ok := err.(*errRequiredArguments); ok { + return cmd.handleRequiredError(ctx, err) + } if cmd.OnUsageError != nil { err = cmd.OnUsageError(ctx, cmd, err, cmd.parent != nil) } @@ -388,6 +402,21 @@ func (cmd *Command) run(ctx context.Context, osArgs []string) (_ context.Context return ctx, deferErr } +func (cmd *Command) handleRequiredError(ctx context.Context, err error) (context.Context, error) { + cmd.isInError = true + if cmd.OnUsageError != nil { + err = cmd.OnUsageError(ctx, cmd, err, cmd.parent != nil) + } else { + fmt.Fprintf(cmd.Root().ErrWriter, "Incorrect Usage: %s\n\n", err.Error()) + if cmd.parent == nil { + _ = ShowRootCommandHelp(cmd) + } else if helpErr := ShowCommandHelp(ctx, cmd.parent, cmd.Name); helpErr != nil { + _ = ShowSubcommandHelp(cmd) + } + } + return ctx, err +} + func commandChain(cmd *Command) []*Command { var cmdChain []*Command for p := cmd; p != nil; p = p.parent { @@ -397,6 +426,41 @@ func commandChain(cmd *Command) []*Command { return cmdChain } +// printDeprecationWarnings writes a warning to the root ErrWriter for each +// deprecated command in the chain and for each deprecated flag that has been +// set, whether on the command line or from one of its Sources. +func printDeprecationWarnings(cmdChain []*Command) { + w := cmdChain[0].ErrWriter + warned := map[Flag]struct{}{} + for _, cmd := range cmdChain { + if cmd.Deprecated != "" { + fmt.Fprintf(w, "Command %q is deprecated, %s\n", cmd.Name, cmd.Deprecated) + } + for _, fl := range cmd.appliedFlags { + if _, inSet := cmd.setFlags[fl]; !inSet { + continue + } + if _, done := warned[fl]; done { + continue + } + if df, ok := fl.(DeprecatedFlag); ok && df.GetDeprecated() != "" { + warned[fl] = struct{}{} + name := fl.Names()[0] + fmt.Fprintf(w, "Flag %s%s has been deprecated, %s\n", prefixFor(name), name, df.GetDeprecated()) + } + } + } +} + +func findArgValidator(cmd *Command) ArgValidatorFunc { + for c := cmd; c != nil; c = c.parent { + if c.ArgValidator != nil { + return c.ArgValidator + } + } + return nil +} + func runBefore(ctx context.Context, cmdChain []*Command) (context.Context, error) { for _, cmd := range cmdChain { if cmd.Before == nil { diff --git a/vendor/github.com/urfave/cli/v3/command_setup.go b/vendor/github.com/urfave/cli/v3/command_setup.go index 646e270ce..c682eaf41 100644 --- a/vendor/github.com/urfave/cli/v3/command_setup.go +++ b/vendor/github.com/urfave/cli/v3/command_setup.go @@ -24,7 +24,7 @@ func (cmd *Command) setupDefaults(osArgs []string) { cmd.ShellComplete = DefaultCompleteWithFlags } - if cmd.Name == "" && isRoot { + if cmd.Name == "" && isRoot && len(osArgs) > 0 { name := filepath.Base(osArgs[0]) tracef("setting cmd.Name from first arg basename (cmd=%[1]q)", name) cmd.Name = name @@ -153,6 +153,7 @@ func (cmd *Command) setupDefaults(osArgs []string) { tracef("setting category on mutually exclusive flags (cmd=%[1]q)", cmd.Name) for _, grp := range cmd.MutuallyExclusiveFlags { grp.propagateCategory() + grp.propagateStringer() } tracef("setting flag categories (cmd=%[1]q)", cmd.Name) @@ -196,6 +197,7 @@ func (cmd *Command) setupSubcommand() { tracef("setting category on mutually exclusive flags (cmd=%[1]q)", cmd.Name) for _, grp := range cmd.MutuallyExclusiveFlags { grp.propagateCategory() + grp.propagateStringer() } tracef("setting flag categories (cmd=%[1]q)", cmd.Name) @@ -227,6 +229,17 @@ func (cmd *Command) hideHelp() bool { return false } +func (cmd *Command) hideHelpCommand() bool { + tracef("hide help command (cmd=%[1]q)", cmd.Name) + for c := cmd; c != nil; c = c.parent { + if c.HideHelpCommand { + return true + } + } + + return false +} + func (cmd *Command) ensureHelp() { tracef("ensuring help (cmd=%[1]q)", cmd.Name) @@ -234,7 +247,7 @@ func (cmd *Command) ensureHelp() { if !cmd.hideHelp() { if cmd.Command(helpCommand.Name) == nil { - if !cmd.HideHelpCommand { + if !cmd.hideHelpCommand() { tracef("appending helpCommand (cmd=%[1]q)", cmd.Name) cmd.appendCommand(helpCommand) } @@ -245,14 +258,21 @@ func (cmd *Command) ensureHelp() { var localHelpFlag Flag if globalHelpFlag, ok := HelpFlag.(*BoolFlag); ok { flag := *globalHelpFlag + // Drop any alias a user flag already claims (e.g. -h + // for --host) so the user flag wins but --help still + // works, as for the version flag. + flag.Aliases = dropClashingAliases(flag.Aliases, cmd.allFlags(), flag.Name) localHelpFlag = &flag } else { localHelpFlag = HelpFlag } - tracef("appending HelpFlag (cmd=%[1]q)", cmd.Name) - cmd.appendFlag(localHelpFlag) - cmd.globaHelpFlagAdded = true + if !flagNamesInUse(cmd.allFlags(), localHelpFlag.Names()) { + tracef("appending HelpFlag (cmd=%[1]q)", cmd.Name) + cmd.appendFlag(localHelpFlag) + cmd.helpFlag = localHelpFlag + cmd.globaHelpFlagAdded = true + } } else { tracef("HelpFlag already added, skip (cmd=%[1]q)", cmd.Name) } diff --git a/vendor/github.com/urfave/cli/v3/completion.go b/vendor/github.com/urfave/cli/v3/completion.go index f14b01430..b78dc385f 100644 --- a/vendor/github.com/urfave/cli/v3/completion.go +++ b/vendor/github.com/urfave/cli/v3/completion.go @@ -59,7 +59,7 @@ source <($COMMAND completion zsh) $COMMAND completion fish > ~/.config/fish/completions/$COMMAND.fish # Powershell -Output the script to path/to/autocomplete/$COMMAND.ps1 an run it. +Output the script to path/to/autocomplete/$COMMAND.ps1 and run it. ` func buildCompletionCommand(appName string) *Command { diff --git a/vendor/github.com/urfave/cli/v3/docs.go b/vendor/github.com/urfave/cli/v3/docs.go index 88461386b..30c053ca8 100644 --- a/vendor/github.com/urfave/cli/v3/docs.go +++ b/vendor/github.com/urfave/cli/v3/docs.go @@ -5,10 +5,11 @@ import ( "os" "runtime" "strings" + "unicode/utf8" ) func prefixFor(name string) (prefix string) { - if len(name) == 1 { + if utf8.RuneCountInString(name) == 1 { prefix = "-" } else { prefix = "--" diff --git a/vendor/github.com/urfave/cli/v3/errors.go b/vendor/github.com/urfave/cli/v3/errors.go index ffd6471f2..113069674 100644 --- a/vendor/github.com/urfave/cli/v3/errors.go +++ b/vendor/github.com/urfave/cli/v3/errors.go @@ -61,6 +61,22 @@ func (e *errRequiredFlags) Error() string { return fmt.Sprintf("Required flags %q not set", joinedMissingFlags) } +type requiredArgumentsErr interface { + error +} + +type errRequiredArguments struct { + missingArguments []string +} + +func (e *errRequiredArguments) Error() string { + if len(e.missingArguments) == 1 { + return fmt.Sprintf("Required argument %q not set", e.missingArguments[0]) + } + joinedMissingArguments := strings.Join(e.missingArguments, ", ") + return fmt.Sprintf("Required arguments %q not set", joinedMissingArguments) +} + type mutuallyExclusiveGroup struct { flag1Name string flag2Name string @@ -136,6 +152,12 @@ func (ee *exitError) ExitCode() int { return ee.exitCode } +// Unwrap returns the error given to [Exit], so that [errors.Is] and +// [errors.As] can inspect it through the ExitCoder wrapper. +func (ee *exitError) Unwrap() error { + return ee.err +} + // HandleExitCoder handles errors implementing ExitCoder by printing their // message and calling OsExiter with the given exit code. // diff --git a/vendor/github.com/urfave/cli/v3/fish.go b/vendor/github.com/urfave/cli/v3/fish.go index 8002a33ca..de0d2a865 100644 --- a/vendor/github.com/urfave/cli/v3/fish.go +++ b/vendor/github.com/urfave/cli/v3/fish.go @@ -5,7 +5,6 @@ import ( "fmt" "io" "strings" - "text/template" ) // ToFishCompletion creates a fish completion string for the `*Command` @@ -25,12 +24,6 @@ type fishCommandCompletionTemplate struct { } func (cmd *Command) writeFishCompletionTemplate(w io.Writer) error { - const name = "cli" - t, err := template.New(name).Parse(FishCompletionTemplate) - if err != nil { - return err - } - // Add global flags completions := prepareFishFlags(cmd.Name, cmd) @@ -57,7 +50,7 @@ func (cmd *Command) writeFishCompletionTemplate(w io.Writer) error { toplevelCommandNames = append(toplevelCommandNames, child.Names()...) } - return t.ExecuteTemplate(w, name, &fishCommandCompletionTemplate{ + return renderFishCompletion(w, &fishCommandCompletionTemplate{ Command: cmd, Completions: completions, AllCommands: toplevelCommandNames, @@ -216,6 +209,15 @@ func commandAncestry(command *Command) string { return strings.Join(ancestry, "; and ") } +// escapeSingleQuotes escapes a string for use inside a fish single-quoted +// string, such as a `-d '...'` description. Within single quotes fish only +// recognizes the escape sequences \\ and \', so the backslash must be escaped +// as well as the single quote. Escaping the quote without escaping the +// backslash corrupts any description that contains a backslash, and a trailing +// backslash even leaves the single-quoted string unterminated. +// See https://fishshell.com/docs/current/language.html func escapeSingleQuotes(input string) string { - return strings.ReplaceAll(input, `'`, `\'`) + return fishSingleQuoteReplacer.Replace(input) } + +var fishSingleQuoteReplacer = strings.NewReplacer(`\`, `\\`, `'`, `\'`) diff --git a/vendor/github.com/urfave/cli/v3/flag.go b/vendor/github.com/urfave/cli/v3/flag.go index 3cb9ab960..f3a9fb54a 100644 --- a/vendor/github.com/urfave/cli/v3/flag.go +++ b/vendor/github.com/urfave/cli/v3/flag.go @@ -118,6 +118,13 @@ type RequiredFlag interface { IsRequired() bool } +// DeprecatedFlag is an interface that allows us to mark flags as deprecated +type DeprecatedFlag interface { + // GetDeprecated returns the deprecation message, or an empty string + // if the flag is not deprecated + GetDeprecated() string +} + // DocGenerationFlag is an interface that allows documentation generation for the flag type DocGenerationFlag interface { // TakesValue returns true if the flag takes a value, otherwise false @@ -190,6 +197,18 @@ type CategorizableFlag interface { SetCategory(string) } +// StringerSetter is an optional interface that allows an individual +// flag to be given a per-flag override of [FlagStringer]. FlagBase and +// BoolWithInverseFlag implement this. It's used by +// [MutuallyExclusiveFlags.Stringer] to customize how flags within a +// mutually exclusive group are displayed in help output. +type StringerSetter interface { + // SetStringer overrides the [FlagStringFunc] used by this flag's + // String method. Passing nil restores the default behavior of using + // the package-level [FlagStringer]. + SetStringer(FlagStringFunc) +} + // LocalFlag is an interface to enable detection of flags which are local // to current command type LocalFlag interface { diff --git a/vendor/github.com/urfave/cli/v3/flag_bool_with_inverse.go b/vendor/github.com/urfave/cli/v3/flag_bool_with_inverse.go index 199d22c7d..d885bbda9 100644 --- a/vendor/github.com/urfave/cli/v3/flag_bool_with_inverse.go +++ b/vendor/github.com/urfave/cli/v3/flag_bool_with_inverse.go @@ -18,6 +18,7 @@ type BoolWithInverseFlag struct { Sources ValueSourceChain `json:"-"` // sources to load flag value from Required bool `json:"required"` // whether the flag is required or not Hidden bool `json:"hidden"` // whether to hide the flag in help output + Deprecated string `json:"deprecated"` // deprecation message, if set a warning is printed when the flag is set Local bool `json:"local"` // whether the flag needs to be applied to subcommands as well Value bool `json:"defaultValue"` // default value for this flag if not set by from any source Destination *bool `json:"-"` // destination pointer for value when set @@ -33,10 +34,26 @@ type BoolWithInverseFlag struct { // unexported fields for internal use count int // number of times the flag has been set hasBeenSet bool // whether the flag has been set from env or file + fromSource bool // whether the current value came from Sources applied bool // whether the flag has been applied to a flag set already value Value // value representing this flag's value pset bool nset bool + stringer FlagStringFunc // optional per-flag override of FlagStringer +} + +// SetStringer overrides the [FlagStringFunc] used by this flag's String +// method. Passing nil restores the default behavior of using the +// package-level [FlagStringer]. This is used e.g. by +// [MutuallyExclusiveFlags.Stringer]. +// +// Note: unlike [FlagBase], BoolWithInverseFlag.String only honors the +// stringer partially. The names segment (the part before the first tab, +// e.g. "--[no-]env, -e") is always recomputed from Name/Aliases/InversePrefix +// and cannot be overridden; only the tab-delimited details after it come +// from the stringer's output. +func (bif *BoolWithInverseFlag) SetStringer(s FlagStringFunc) { + bif.stringer = s } func (bif *BoolWithInverseFlag) IsSet() bool { @@ -68,10 +85,6 @@ func (bif *BoolWithInverseFlag) inversePrefix() string { } func (bif *BoolWithInverseFlag) PreParse() error { - count := bif.Config.Count - if count == nil { - count = &bif.count - } dest := bif.Destination if dest == nil { dest = new(bool) @@ -79,7 +92,7 @@ func (bif *BoolWithInverseFlag) PreParse() error { *dest = bif.Value bif.value = &boolValue{ destination: dest, - count: count, + count: bif.Config.Count, } // Validate the given default or values set from external sources as well @@ -108,6 +121,7 @@ func (bif *BoolWithInverseFlag) PostParse() error { } bif.hasBeenSet = true + bif.fromSource = true } } @@ -115,6 +129,15 @@ func (bif *BoolWithInverseFlag) PostParse() error { } func (bif *BoolWithInverseFlag) Set(name, val string) error { + // A value from Sources is only a fallback, so a value set afterwards + // replaces it (see FlagBase.Set). + if bif.fromSource { + bif.fromSource = false + bif.count = 0 + bif.pset = false + bif.nset = false + } + if bif.count > 0 && bif.OnlyOnce { return fmt.Errorf("can't duplicate this flag") } @@ -138,6 +161,7 @@ func (bif *BoolWithInverseFlag) Set(name, val string) error { } bif.nset = true } + bif.count++ if bif.Validator != nil { return bif.Validator(bif.value.Get().(bool)) @@ -171,7 +195,11 @@ func (bif *BoolWithInverseFlag) IsVisible() bool { // Example for BoolFlag{Name: "env", Aliases: []string{"e"}} // --[no-]env, -e (default: false) func (bif *BoolWithInverseFlag) String() string { - out := FlagStringer(bif) + fs := FlagStringer + if bif.stringer != nil { + fs = bif.stringer + } + out := fs(bif) i := strings.Index(out, "\t") @@ -219,12 +247,17 @@ func (bif *BoolWithInverseFlag) Count() int { // GetDefaultText returns the default text for this flag func (bif *BoolWithInverseFlag) GetDefaultText() string { - if bif.Required { + if bif.DefaultText != "" { return bif.DefaultText } return boolValue{}.ToString(bif.Value) } +// GetDeprecated returns the deprecation message of the flag +func (bif *BoolWithInverseFlag) GetDeprecated() string { + return bif.Deprecated +} + // GetCategory returns the category of the flag func (bif *BoolWithInverseFlag) GetCategory() string { return bif.Category diff --git a/vendor/github.com/urfave/cli/v3/flag_impl.go b/vendor/github.com/urfave/cli/v3/flag_impl.go index a67208c91..410f7f087 100644 --- a/vendor/github.com/urfave/cli/v3/flag_impl.go +++ b/vendor/github.com/urfave/cli/v3/flag_impl.go @@ -63,6 +63,7 @@ type FlagBase[T any, C any, VC ValueCreator[T, C]] struct { Sources ValueSourceChain `json:"-"` // sources to load flag value from Required bool `json:"required"` // whether the flag is required or not Hidden bool `json:"hidden"` // whether to hide the flag in help output + Deprecated string `json:"deprecated"` // deprecation message, if set a warning is printed when the flag is set Local bool `json:"local"` // whether the flag needs to be applied to subcommands as well Value T `json:"defaultValue"` // default value for this flag if not set by from any source Destination *T `json:"-"` // destination pointer for value when set @@ -75,11 +76,15 @@ type FlagBase[T any, C any, VC ValueCreator[T, C]] struct { ValidateDefaults bool `json:"validateDefaults"` // whether to validate defaults or not // unexported fields for internal use - count int // number of times the flag has been set - hasBeenSet bool // whether the flag has been set from env or file - applied bool // whether the flag has been applied to a flag set already - creator VC // value creator for this flag type - value Value // value representing this flag's value + count int // number of times the flag has been set + hasBeenSet bool // whether the flag has been set from env or file + fromSource bool // whether the current value came from Sources + applied bool // whether the flag has been applied to a flag set already + creator VC // value creator for this flag type + value Value // value representing this flag's value + stringer FlagStringFunc // optional per-flag override of FlagStringer + + multiValueConfig *multiValueParsingConfig // last parsing config passed to value } // GetValue returns the flags value as string representation and an empty @@ -130,19 +135,40 @@ func (f *FlagBase[T, C, V]) PostParse() error { tracef("postparse (flag=%[1]q)", f.Name) if !f.hasBeenSet { - if val, source, found := f.Sources.LookupWithSource(); found { - if val != "" || reflect.TypeOf(f.Value).Kind() == reflect.String { + // reflect.TypeOf yields nil when T is an interface type (e.g. + // GenericFlag) and the value is nil, so the kind has to be + // derived defensively. + kind := reflect.Invalid + if ty := reflect.TypeOf(f.Value); ty != nil { + kind = ty.Kind() + } + + // An empty value is a value only for a string, and reads as false + // for a bool. Any other kind has nothing to parse from it, so an + // empty source is skipped: it neither marks the flag as set nor + // hides a later source in the chain. + emptyIsValue := kind == reflect.String || kind == reflect.Bool + + for _, source := range f.Sources.Chain { + val, found := source.Lookup() + if !found || (val == "" && !emptyIsValue) { + continue + } + + if val != "" || kind == reflect.String { if err := f.Set(f.Name, val); err != nil { return fmt.Errorf( "could not parse %[1]q as %[2]T value from %[3]s for flag %[4]s: %[5]s", val, f.Value, source, f.Name, err, ) } - } else if val == "" && reflect.TypeOf(f.Value).Kind() == reflect.Bool { + } else { _ = f.Set(f.Name, "false") } f.hasBeenSet = true + f.fromSource = true + break } } @@ -152,19 +178,22 @@ func (f *FlagBase[T, C, V]) PostParse() error { // pass configuration of parsing to value func (f *FlagBase[T, C, V]) setMultiValueParsingConfig(c multiValueParsingConfig) { tracef("setMultiValueParsingConfig %T, %+v", f.value, f.value) + f.multiValueConfig = &c if cf, ok := f.value.(multiValueParsingConfigSetter); ok { cf.setMultiValueParsingConfig(c) } } -func (f *FlagBase[T, C, V]) PreParse() error { - newVal := f.Value - +// newValue creates the flag's value holding the default Value. +func (f *FlagBase[T, C, V]) newValue() Value { if f.Destination == nil { - f.value = f.creator.Create(newVal, new(T), f.Config) - } else { - f.value = f.creator.Create(newVal, f.Destination, f.Config) + return f.creator.Create(f.Value, new(T), f.Config) } + return f.creator.Create(f.Value, f.Destination, f.Config) +} + +func (f *FlagBase[T, C, V]) PreParse() error { + f.value = f.newValue() // Validate the given default or values set from external sources as well if f.Validator != nil && f.ValidateDefaults { @@ -193,6 +222,19 @@ func (f *FlagBase[T, C, V]) Set(_ string, val string) error { f.applied = true } + // A value from Sources is only a fallback, so a value set afterwards + // replaces it rather than being added to it or counted as a duplicate. + // This happens for a persistent flag: its command reads the Sources + // before a subcommand parses the flag from the command line. + if f.fromSource { + f.fromSource = false + f.count = 0 + f.value = f.newValue() + if f.multiValueConfig != nil { + f.setMultiValueParsingConfig(*f.multiValueConfig) + } + } + if f.count == 1 && f.OnlyOnce { return fmt.Errorf("can't duplicate this flag") } @@ -224,9 +266,20 @@ func (f *FlagBase[T, C, V]) IsDefaultVisible() bool { // String returns a readable representation of this value (for usage defaults) func (f *FlagBase[T, C, V]) String() string { + if f.stringer != nil { + return f.stringer(f) + } return FlagStringer(f) } +// SetStringer overrides the [FlagStringFunc] used by this flag's String +// method. Passing nil restores the default behavior of using the +// package-level [FlagStringer]. This is used e.g. by +// [MutuallyExclusiveFlags.Stringer]. +func (f *FlagBase[T, C, V]) SetStringer(s FlagStringFunc) { + f.stringer = s +} + // IsSet returns whether or not the flag has been set through env or file func (f *FlagBase[T, C, V]) IsSet() bool { return f.hasBeenSet @@ -247,6 +300,11 @@ func (f *FlagBase[T, C, V]) IsVisible() bool { return !f.Hidden } +// GetDeprecated returns the deprecation message of the flag +func (f *FlagBase[T, C, V]) GetDeprecated() string { + return f.Deprecated +} + // GetCategory returns the category of the flag func (f *FlagBase[T, C, V]) GetCategory() string { return f.Category @@ -316,8 +374,10 @@ func (f *FlagBase[T, C, V]) SchemaType() string { // SchemaItemsType returns the JSON Schema element type for slice flags. func (f *FlagBase[T, C, V]) SchemaItemsType() string { var zero T + // reflect.TypeOf yields nil when T is an interface type (e.g. GenericFlag), + // in which case there are no slice elements to describe. t := reflect.TypeOf(zero) - if t.Kind() == reflect.Slice { + if t != nil && t.Kind() == reflect.Slice { switch t.Elem().Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: diff --git a/vendor/github.com/urfave/cli/v3/flag_map_impl.go b/vendor/github.com/urfave/cli/v3/flag_map_impl.go index cb65903b9..dfe8f0978 100644 --- a/vendor/github.com/urfave/cli/v3/flag_map_impl.go +++ b/vendor/github.com/urfave/cli/v3/flag_map_impl.go @@ -13,6 +13,7 @@ type MapBase[T any, C any, VC ValueCreator[T, C]] struct { dict *map[string]T hasBeenSet bool value Value + trimKeySpace bool multiValueConfig multiValueParsingConfig } @@ -24,9 +25,16 @@ func (i MapBase[T, C, VC]) Create(val map[string]T, p *map[string]T, c C) Value var t T np := new(T) var vc VC + // The key is always a string, so it answers to StringConfig.TrimSpace just + // like the value does. + trimKeySpace := false + if sc, ok := any(c).(StringConfig); ok { + trimKeySpace = sc.TrimSpace + } return &MapBase[T, C, VC]{ - dict: p, - value: vc.Create(t, np, c), + dict: p, + value: vc.Create(t, np, c), + trimKeySpace: trimKeySpace, } } @@ -81,6 +89,9 @@ func (i *MapBase[T, C, VC]) Set(value string) error { if !ok { return fmt.Errorf("item %q is missing separator %q", item, keyValueSeparator) } + if i.trimKeySpace { + key = strings.TrimSpace(key) + } if err := i.value.Set(value); err != nil { return err } diff --git a/vendor/github.com/urfave/cli/v3/flag_mutex.go b/vendor/github.com/urfave/cli/v3/flag_mutex.go index 247bcb569..bd016988e 100644 --- a/vendor/github.com/urfave/cli/v3/flag_mutex.go +++ b/vendor/github.com/urfave/cli/v3/flag_mutex.go @@ -14,35 +14,63 @@ type MutuallyExclusiveFlags struct { // Category to apply to all flags within group Category string + + // Stringer overrides how each flag within this group is displayed in + // help output. If nil, flags use [FlagStringer] as usual. + // + // The returned string must be unique per flag within the group (the + // default [stringifyFlag] guarantees this by embedding the flag's + // name). Help rendering keys flags by their String() output + // (flagCategories.AddFlag), so a Stringer that returns identical text + // for two or more flags in the same category will cause the later + // flag to silently overwrite the earlier one in help output. + Stringer FlagStringFunc `json:"-"` } func (grp MutuallyExclusiveFlags) check(_ *Command) error { - oneSet := false e := &mutuallyExclusiveGroup{} - for _, grpf := range grp.Flags { - for _, f := range grpf { - if f.IsSet() { - if oneSet { - e.flag2Name = f.Names()[0] - return e - } - e.flag1Name = f.Names()[0] - oneSet = true - break - } - if oneSet { - break - } + // Check for the use of a mutually-exclusive flag, starting at + // the first group. + name, i, ok := grp.findSetFlag(0) + if ok { + e.flag1Name = name + i++ + + // Check for the use of a flag in a mutually exclusive + // relationship with the one we just found. + if name2, _, ok := grp.findSetFlag(i); ok { + e.flag2Name = name2 + return e } } - if !oneSet && grp.Required { + if !ok && grp.Required { return &mutuallyExclusiveGroupRequiredFlag{flags: &grp} } + return nil } +// findSetFlag is used in [MutuallyExclusiveFlags.check] to find +// whether at least one flag inside a mutually exclusive flag group is +// set. If so, return the flag name, position at which it's set, and +// Boolean true (indicating that a flag was found.) Else, return all +// zero values. +func (grp MutuallyExclusiveFlags) findSetFlag(startIdx int) (string, int, bool) { + for i := startIdx; i < len(grp.Flags); i++ { + flags := grp.Flags[i] + + for _, flg := range flags { + if flg.IsSet() { + return flg.Names()[0], i, true + } + } + } + + return "", 0, false +} + func (grp MutuallyExclusiveFlags) propagateCategory() { for _, grpf := range grp.Flags { for _, f := range grpf { @@ -52,3 +80,22 @@ func (grp MutuallyExclusiveFlags) propagateCategory() { } } } + +// propagateStringer applies [MutuallyExclusiveFlags.Stringer], if set, to +// every flag within the group that supports a [StringerSetter]. Like +// [MutuallyExclusiveFlags.propagateCategory], this only runs during command +// setup, so mutating Stringer between runs of a reused [Command] will not +// re-propagate the change. +func (grp MutuallyExclusiveFlags) propagateStringer() { + if grp.Stringer == nil { + return + } + + for _, grpf := range grp.Flags { + for _, f := range grpf { + if sf, ok := f.(StringerSetter); ok { + sf.SetStringer(grp.Stringer) + } + } + } +} diff --git a/vendor/github.com/urfave/cli/v3/flag_text.go b/vendor/github.com/urfave/cli/v3/flag_text.go new file mode 100644 index 000000000..a1cc2c363 --- /dev/null +++ b/vendor/github.com/urfave/cli/v3/flag_text.go @@ -0,0 +1,86 @@ +package cli + +import ( + "encoding" + "strings" +) + +// TextMarshalUnmarshaler is the interface implemented by types that can marshal +// themselves to and from a textual form. It is the value type used by TextFlag, +// mirroring the standard library's flag.TextVar, and is satisfied by types such +// as *slog.LevelVar, *net/netip.Addr, and *time.Time. +type TextMarshalUnmarshaler interface { + encoding.TextMarshaler + encoding.TextUnmarshaler +} + +type TextFlag = FlagBase[TextMarshalUnmarshaler, StringConfig, textValue] + +// -- TextMarshalUnmarshaler Value +type textValue struct { + destination TextMarshalUnmarshaler + trimSpace bool +} + +// Below functions are to satisfy the ValueCreator interface + +func (t textValue) Create(val TextMarshalUnmarshaler, p *TextMarshalUnmarshaler, c StringConfig) Value { + // Only overwrite the target when a non-nil default value is given, so that + // a Destination pointing at an existing target is preserved (unlike the + // concrete flag types, T here is an interface whose nil default would + // otherwise clobber the destination). + if val != nil { + *p = val + } + return &textValue{ + destination: *p, + trimSpace: c.TrimSpace, + } +} + +func (t textValue) ToString(val TextMarshalUnmarshaler) string { + if val == nil { + return "" + } + text, err := val.MarshalText() + if err != nil { + return "" + } + return string(text) +} + +// Below functions are to satisfy the flag.Value interface + +func (t *textValue) Set(val string) error { + if t.destination == nil { + return nil + } + if t.trimSpace { + val = strings.TrimSpace(val) + } + return t.destination.UnmarshalText([]byte(val)) +} + +func (t *textValue) Get() any { return t.destination } + +func (t *textValue) String() string { + if t.destination == nil { + return "" + } + text, err := t.destination.MarshalText() + if err != nil { + return "" + } + return string(text) +} + +// Text looks up the value of a local TextFlag, returns nil if not found +func (cmd *Command) Text(name string) TextMarshalUnmarshaler { + if v, ok := cmd.Value(name).(TextMarshalUnmarshaler); ok { + tracef("text available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name) + return v + } + + tracef("text NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name) + return nil +} diff --git a/vendor/github.com/urfave/cli/v3/flag_timestamp.go b/vendor/github.com/urfave/cli/v3/flag_timestamp.go index 82b1cb5cf..0f1736542 100644 --- a/vendor/github.com/urfave/cli/v3/flag_timestamp.go +++ b/vendor/github.com/urfave/cli/v3/flag_timestamp.go @@ -63,6 +63,7 @@ func (t *timestampValue) Set(value string) error { return errors.New("got nil/empty layouts slice") } + matchedLayout := "" for _, layout := range t.layouts { var locErr error @@ -78,6 +79,7 @@ func (t *timestampValue) Set(value string) error { } err = nil + matchedLayout = layout break } @@ -85,12 +87,14 @@ func (t *timestampValue) Set(value string) error { return err } - defaultTS, _ := time.ParseInLocation(time.TimeOnly, time.TimeOnly, timestamp.Location()) - n := time.Now().In(timestamp.Location()) // If format is missing date (or year only), set it explicitly to current - if timestamp.Truncate(time.Hour*24).UnixNano() == defaultTS.Truncate(time.Hour*24).UnixNano() { + // A layout that carries a date parses "January 1" to the same instant a date-less layout does, so ask the layout. + // The date is compared in the timestamp's own location: truncating the + // instant to a day works in UTC and misses January 1 of year 0 for part + // of the day whenever Timezone is not UTC. + if !layoutHasDate(matchedLayout) && timestamp.Year() == 0 && timestamp.YearDay() == 1 { timestamp = time.Date( n.Year(), n.Month(), @@ -121,6 +125,14 @@ func (t *timestampValue) Set(value string) error { return nil } +// layoutHasDate reports whether layout carries month/day components, probed by +// round-tripping a reference date that is not January 1. +func layoutHasDate(layout string) bool { + ref := time.Date(2, time.March, 4, 5, 6, 7, 0, time.UTC) + p, err := time.Parse(layout, ref.Format(layout)) + return err == nil && (p.Month() != time.January || p.Day() != 1) +} + // String returns a readable representation of this value (for usage defaults) func (t *timestampValue) String() string { return fmt.Sprintf("%v", t.timestamp) diff --git a/vendor/github.com/urfave/cli/v3/funcs.go b/vendor/github.com/urfave/cli/v3/funcs.go index fe1224c44..17a73eca6 100644 --- a/vendor/github.com/urfave/cli/v3/funcs.go +++ b/vendor/github.com/urfave/cli/v3/funcs.go @@ -17,6 +17,13 @@ type AfterFunc func(context.Context, *Command) error // ActionFunc is the action to execute when no subcommands are specified type ActionFunc func(context.Context, *Command) error +// ArgValidatorFunc is an action to validate arguments before the command is run. +// If non-nil, it is called before the command's After and Action functions. +// Returning a non-nil error short-circuits the command and propagates as +// the exit error. If the current command does not set ArgValidator, the +// nearest ancestor that does is used instead. +type ArgValidatorFunc func(context.Context, *Command) error + // CommandNotFoundFunc is executed if the proper command cannot be found type CommandNotFoundFunc func(context.Context, *Command, string) diff --git a/vendor/github.com/urfave/cli/v3/godoc-current.txt b/vendor/github.com/urfave/cli/v3/godoc-current.txt index d680c5f54..ce70dbc0c 100644 --- a/vendor/github.com/urfave/cli/v3/godoc-current.txt +++ b/vendor/github.com/urfave/cli/v3/godoc-current.txt @@ -258,6 +258,13 @@ type AfterFunc func(context.Context, *Command) error AfterFunc is an action that executes after any subcommands are run and have finished. The AfterFunc is run even if Action() panics. +type ArgValidatorFunc func(context.Context, *Command) error + ArgValidatorFunc is an action to validate arguments before the command + is run. If non-nil, it is called before the command's After and Action + functions. Returning a non-nil error short-circuits the command and + propagates as the exit error. If the current command does not set + ArgValidator, the nearest ancestor that does is used instead. + type Args interface { // Get returns the nth argument, or else a blank string Get(n int) string @@ -294,6 +301,7 @@ type ArgumentBase[T any, C any, VC ValueCreator[T, C]] struct { Value T `json:"value"` // the default value of this argument Destination *T `json:"-"` // the destination point for this argument UsageText string `json:"usageText"` // the usage text to show + Required bool `json:"required"` // whether the argument is required or not Config C `json:"config"` // config for this argument similar to Flag Config // Has unexported fields. @@ -349,6 +357,7 @@ type BoolWithInverseFlag struct { Sources ValueSourceChain `json:"-"` // sources to load flag value from Required bool `json:"required"` // whether the flag is required or not Hidden bool `json:"hidden"` // whether to hide the flag in help output + Deprecated string `json:"deprecated"` // deprecation message, if set a warning is printed when the flag is set Local bool `json:"local"` // whether the flag needs to be applied to subcommands as well Value bool `json:"defaultValue"` // default value for this flag if not set by from any source Destination *bool `json:"-"` // destination pointer for value when set @@ -375,6 +384,9 @@ func (bif *BoolWithInverseFlag) GetCategory() string func (bif *BoolWithInverseFlag) GetDefaultText() string GetDefaultText returns the default text for this flag +func (bif *BoolWithInverseFlag) GetDeprecated() string + GetDeprecated returns the deprecation message of the flag + func (bif *BoolWithInverseFlag) GetEnvVars() []string GetEnvVars returns the env vars for this flag @@ -415,6 +427,17 @@ func (bif *BoolWithInverseFlag) Set(name, val string) error func (bif *BoolWithInverseFlag) SetCategory(c string) +func (bif *BoolWithInverseFlag) SetStringer(s FlagStringFunc) + SetStringer overrides the FlagStringFunc used by this flag's String method. + Passing nil restores the default behavior of using the package-level + FlagStringer. This is used e.g. by MutuallyExclusiveFlags.Stringer. + + Note: unlike FlagBase, BoolWithInverseFlag.String only honors the + stringer partially. The names segment (the part before the first tab, e.g. + "--[no-]env, -e") is always recomputed from Name/Aliases/InversePrefix and + cannot be overridden; only the tab-delimited details after it come from the + stringer's output. + func (bif *BoolWithInverseFlag) String() string String implements the standard Stringer interface. @@ -464,6 +487,9 @@ type Command struct { Flags []Flag `json:"flags"` // Boolean to hide built-in help command and help flag HideHelp bool `json:"hideHelp"` + // Boolean to hide the built-in help command. Applies to this command and + // all of its subcommands: as with HideHelp, a true value is inherited and + // a subcommand cannot turn it back off. // Ignored if HideHelp is true. HideHelpCommand bool `json:"hideHelpCommand"` // Boolean to hide built-in version flag and the VERSION section of help @@ -482,6 +508,11 @@ type Command struct { // 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:"-"` + // An action to validate arguments before the command is run. If non-nil, it + // is called before Before and Action. If the current command does not set + // ArgValidator, the nearest ancestor that does is used instead. + // Returning a non-nil error short-circuits the command. + ArgValidator ArgValidatorFunc `json:"-"` // The function to call when this command is invoked Action ActionFunc `json:"-"` // Execute this function if the proper command cannot be found @@ -492,6 +523,10 @@ type Command struct { InvalidFlagAccessHandler InvalidFlagAccessFunc `json:"-"` // Boolean to hide this command from help or completion Hidden bool `json:"hidden"` + // Deprecation message for this command. If non-empty, a warning is + // printed to ErrWriter when the command is invoked. Setting it does not + // hide the command; set Hidden as well for that. + Deprecated string `json:"deprecated"` // List of all authors who contributed (string or fmt.Stringer) // TODO: ~string | fmt.Stringer when interface unions are available Authors []any `json:"authors"` @@ -564,6 +599,9 @@ type Command struct { func (cmd *Command) Args() Args Args returns the command line arguments associated with the command. + If the command declares named Arguments, the arguments consumed by them + are not included in the returned Args and should be retrieved via the + command.{Type}Arg(name) functions instead. func (cmd *Command) Bool(name string) bool @@ -726,6 +764,9 @@ func (cmd *Command) StringSlice(name string) []string StringSlice looks up the value of a local StringSliceFlag, returns nil if not found +func (cmd *Command) Text(name string) TextMarshalUnmarshaler + Text looks up the value of a local TextFlag, returns nil if not found + func (cmd *Command) Timestamp(name string) time.Time Timestamp gets the timestamp from a flag name @@ -846,6 +887,13 @@ type Countable interface { Countable is an interface to enable detection of flag values which support repetitive flags +type DeprecatedFlag interface { + // GetDeprecated returns the deprecation message, or an empty string + // if the flag is not deprecated + GetDeprecated() string +} + DeprecatedFlag is an interface that allows us to mark flags as deprecated + type DocGenerationFlag interface { // TakesValue returns true if the flag takes a value, otherwise false TakesValue() bool @@ -974,6 +1022,7 @@ type FlagBase[T any, C any, VC ValueCreator[T, C]] struct { Sources ValueSourceChain `json:"-"` // sources to load flag value from Required bool `json:"required"` // whether the flag is required or not Hidden bool `json:"hidden"` // whether to hide the flag in help output + Deprecated string `json:"deprecated"` // deprecation message, if set a warning is printed when the flag is set Local bool `json:"local"` // whether the flag needs to be applied to subcommands as well Value T `json:"defaultValue"` // default value for this flag if not set by from any source Destination *T `json:"-"` // destination pointer for value when set @@ -1005,6 +1054,9 @@ func (f *FlagBase[T, C, V]) GetCategory() string func (f *FlagBase[T, C, V]) GetDefaultText() string GetDefaultText returns the default text for this flag +func (f *FlagBase[T, C, V]) GetDeprecated() string + GetDeprecated returns the deprecation message of the flag + func (f *FlagBase[T, C, V]) GetEnvVars() []string GetEnvVars returns the env vars for this flag @@ -1059,6 +1111,11 @@ func (f *FlagBase[T, C, V]) Set(_ string, val string) error func (f *FlagBase[T, C, V]) SetCategory(c string) +func (f *FlagBase[T, C, V]) SetStringer(s FlagStringFunc) + SetStringer overrides the FlagStringFunc used by this flag's String method. + Passing nil restores the default behavior of using the package-level + FlagStringer. This is used e.g. by MutuallyExclusiveFlags.Stringer. + func (f *FlagBase[T, C, V]) String() string String returns a readable representation of this value (for usage defaults) @@ -1296,6 +1353,17 @@ type MutuallyExclusiveFlags struct { // Category to apply to all flags within group Category string + + // Stringer overrides how each flag within this group is displayed in + // help output. If nil, flags use [FlagStringer] as usual. + // + // The returned string must be unique per flag within the group (the + // default [stringifyFlag] guarantees this by embedding the flag's + // name). Help rendering keys flags by their String() output + // (flagCategories.AddFlag), so a Stringer that returns identical text + // for two or more flags in the same category will cause the later + // flag to silently overwrite the earlier one in help output. + Stringer FlagStringFunc `json:"-"` } MutuallyExclusiveFlags defines a mutually exclusive flag group Multiple option paths can be provided out of which only one can be defined on cmdline @@ -1393,10 +1461,32 @@ type StringSlice = SliceBase[string, StringConfig, stringValue] type StringSliceFlag = FlagBase[[]string, StringConfig, StringSlice] +type StringerSetter interface { + // SetStringer overrides the [FlagStringFunc] used by this flag's + // String method. Passing nil restores the default behavior of using + // the package-level [FlagStringer]. + SetStringer(FlagStringFunc) +} + StringerSetter is an optional interface that allows an individual flag to be + given a per-flag override of FlagStringer. FlagBase and BoolWithInverseFlag + implement this. It's used by MutuallyExclusiveFlags.Stringer to customize + how flags within a mutually exclusive group are displayed in help output. + type SuggestCommandFunc func(commands []*Command, provided string) string type SuggestFlagFunc func(flags []Flag, provided string, hideHelp bool) string +type TextFlag = FlagBase[TextMarshalUnmarshaler, StringConfig, textValue] + +type TextMarshalUnmarshaler interface { + encoding.TextMarshaler + encoding.TextUnmarshaler +} + TextMarshalUnmarshaler is the interface implemented by types that can + marshal themselves to and from a textual form. It is the value type used by + TextFlag, mirroring the standard library's flag.TextVar, and is satisfied by + types such as *slog.LevelVar, *net/netip.Addr, and *time.Time. + type TimestampArg = ArgumentBase[time.Time, TimestampConfig, timestampValue] type TimestampArgs = ArgumentsBase[time.Time, TimestampConfig, timestampValue] diff --git a/vendor/github.com/urfave/cli/v3/help.go b/vendor/github.com/urfave/cli/v3/help.go index 4bedf87d5..382735bf3 100644 --- a/vendor/github.com/urfave/cli/v3/help.go +++ b/vendor/github.com/urfave/cli/v3/help.go @@ -7,8 +7,6 @@ import ( "os" "slices" "strings" - "text/tabwriter" - "text/template" "unicode/utf8" ) @@ -216,7 +214,7 @@ func printFlagSuggestions(lastArg string, flags []Flag, writer io.Writer) { // Trim to handle both "-short" and "--long" flags. cur := strings.TrimLeft(lastArg, "-") for _, flag := range flags { - if bflag, ok := flag.(*BoolFlag); ok && bflag.Hidden { + if vf, ok := flag.(VisibleFlag); ok && !vf.IsVisible() { continue } @@ -257,12 +255,13 @@ func DefaultCompleteWithFlags(ctx context.Context, cmd *Command) { } argsLen := len(args) lastArg := "" - // parent command will have --generate-shell-completion so we need - // to account for that - if argsLen > 1 { - lastArg = args[argsLen-2] - } else if argsLen > 0 { + // os.Args retains the completion marker, but a child's parsed arguments + // have already had it removed. + if argsLen > 0 { lastArg = args[argsLen-1] + if lastArg == completionFlag && argsLen > 1 { + lastArg = args[argsLen-2] + } } if lastArg == completionFlag { @@ -271,7 +270,15 @@ func DefaultCompleteWithFlags(ctx context.Context, cmd *Command) { if strings.HasPrefix(lastArg, "-") { tracef("printing flag suggestion for flag[%v] on command %[1]q", lastArg, cmd.Name) - printFlagSuggestions(lastArg, cmd.Flags, cmd.Root().Writer) + flags := cmd.allFlags() + if !cmd.SkipFlagParsing { + for _, fl := range cmd.VisiblePersistentFlags() { + if !hasFlag(flags, fl) { + flags = append(flags, fl) + } + } + } + printFlagSuggestions(lastArg, flags, cmd.Root().Writer) return } @@ -369,99 +376,6 @@ func handleTemplateError(err error) { } } -// DefaultPrintHelpCustom is the default implementation of HelpPrinterCustom. -// -// The customFuncs map will be combined with a default template.FuncMap to -// allow using arbitrary functions in template rendering. -func DefaultPrintHelpCustom(out io.Writer, templ string, data any, customFuncs map[string]any) { - const maxLineLength = 10000 - - tracef("building default funcMap") - funcMap := template.FuncMap{ - "join": strings.Join, - "subtract": subtract, - "indent": indent, - "nindent": nindent, - "trim": strings.TrimSpace, - "wrap": func(input string, offset int) string { return wrap(input, offset, maxLineLength) }, - "offset": offset, - "offsetCommands": offsetCommands, - } - - if wa, ok := customFuncs["wrapAt"]; ok { - if wrapAtFunc, ok := wa.(func() int); ok { - wrapAt := wrapAtFunc() - customFuncs["wrap"] = func(input string, offset int) string { - return wrap(input, offset, wrapAt) - } - } - } - - for key, value := range customFuncs { - funcMap[key] = value - } - - w := tabwriter.NewWriter(out, 1, 8, 2, ' ', 0) - t := template.Must(template.New("help").Funcs(funcMap).Parse(templ)) - - if _, err := t.New("helpNameTemplate").Parse(helpNameTemplate); err != nil { - handleTemplateError(err) - } - - if _, err := t.New("argsTemplate").Parse(argsTemplate); err != nil { - handleTemplateError(err) - } - - if _, err := t.New("usageTemplate").Parse(usageTemplate); err != nil { - handleTemplateError(err) - } - - if _, err := t.New("descriptionTemplate").Parse(descriptionTemplate); err != nil { - handleTemplateError(err) - } - - if _, err := t.New("visibleCommandTemplate").Parse(visibleCommandTemplate); err != nil { - handleTemplateError(err) - } - - if _, err := t.New("copyrightTemplate").Parse(copyrightTemplate); err != nil { - handleTemplateError(err) - } - - if _, err := t.New("versionTemplate").Parse(versionTemplate); err != nil { - handleTemplateError(err) - } - - if _, err := t.New("visibleFlagCategoryTemplate").Parse(visibleFlagCategoryTemplate); err != nil { - handleTemplateError(err) - } - - if _, err := t.New("visibleFlagTemplate").Parse(visibleFlagTemplate); err != nil { - handleTemplateError(err) - } - - if _, err := t.New("visiblePersistentFlagTemplate").Parse(visiblePersistentFlagTemplate); err != nil { - handleTemplateError(err) - } - - if _, err := t.New("visibleGlobalFlagCategoryTemplate").Parse(strings.ReplaceAll(visibleFlagCategoryTemplate, "OPTIONS", "GLOBAL OPTIONS")); err != nil { - handleTemplateError(err) - } - - if _, err := t.New("authorsTemplate").Parse(authorsTemplate); err != nil { - handleTemplateError(err) - } - - if _, err := t.New("visibleCommandCategoryTemplate").Parse(visibleCommandCategoryTemplate); err != nil { - handleTemplateError(err) - } - - tracef("executing template") - handleTemplateError(t.Execute(w, data)) - - _ = w.Flush() -} - // DefaultPrintHelp is the default implementation of HelpPrinter. func DefaultPrintHelp(out io.Writer, templ string, data any) { HelpPrinterCustom(out, templ, data, nil) @@ -476,6 +390,10 @@ func checkShellCompleteFlag(c *Command, arguments []string) (bool, []string) { return false, arguments } + if len(arguments) == 0 { + return false, arguments + } + pos := len(arguments) - 1 lastArg := arguments[pos] @@ -483,13 +401,16 @@ func checkShellCompleteFlag(c *Command, arguments []string) (bool, []string) { return false, arguments } - // If arguments include "--" before the token being completed, shell completion - // is disabled because after "--" only positional arguments are accepted. + // If the token being completed is preceded by a "--", only positional + // arguments are accepted after it, so nothing will be suggested. // https://unix.stackexchange.com/a/11382 // Note: The token being completed is at position pos-1 (immediately before completionFlag). - // We only check arguments before that position, so completing "--" itself still works. + // A "--" at exactly that position is the token being completed, not a + // separator, so completing "--" itself still suggests flags. + // The request is still recognized as a completion so that the command + // action is never executed (https://github.com/urfave/cli/issues/1993). if pos >= 1 && slices.Contains(arguments[:pos-1], "--") { - return false, arguments[:pos] + c.shellCompletionPastDoubleDash = true } return true, arguments[:pos] @@ -516,6 +437,13 @@ func shouldRunCompletion(cmd *Command) bool { } func runCompletion(ctx context.Context, cmd *Command) { + // Nothing is suggested past a "--": after it, only positional arguments + // are accepted. The request is still treated as a completion so that the + // command action is never executed (https://github.com/urfave/cli/issues/1993). + if cmd.Root().shellCompletionPastDoubleDash { + tracef("completion requested past double dash; suggesting nothing (cmd=%[1]q)", cmd.Name) + return + } if cmd.ShellComplete != nil { tracef("running shell completion func for command %[1]q", cmd.Name) cmd.ShellComplete(ctx, cmd) diff --git a/vendor/github.com/urfave/cli/v3/help_notemplate.go b/vendor/github.com/urfave/cli/v3/help_notemplate.go new file mode 100644 index 000000000..b417034b4 --- /dev/null +++ b/vendor/github.com/urfave/cli/v3/help_notemplate.go @@ -0,0 +1,338 @@ +//go:build urfave_cli_no_template + +package cli + +import ( + "errors" + "fmt" + "io" + "reflect" + "strings" + "text/tabwriter" +) + +// templatesSupported is true unless built with the urfave_cli_no_template tag. +const templatesSupported = false + +// With urfave_cli_no_template tag, templates are not used, and these are +// only used to tell which help is requested. +const ( + // RootCommandHelpTemplate identifies the help for the root command. + RootCommandHelpTemplate = "root" + // CommandHelpTemplate identifies the help for a command. + CommandHelpTemplate = "command" + // SubcommandHelpTemplate identifies the help for a command with subcommands. + SubcommandHelpTemplate = "subcommand" + // FishCompletionTemplate is not used. + FishCompletionTemplate = "fish" +) + +var errNoTemplate = errors.New("custom templates are not supported when built with urfave_cli_no_template tag") + +// DefaultPrintHelpCustom is the default implementation of HelpPrinterCustom. +// +// The customFuncs map will be combined with a default template.FuncMap to +// allow using arbitrary functions in template rendering. +// +// When built with the urfave_cli_no_template tag, text/template is not used +// (as it disables linker's dead code elimination), and the help is rendered +// directly. In this mode, only the default templates are supported (it panics +// otherwise), and the only custom functions used are "wrap" and "wrapAt". +func DefaultPrintHelpCustom(out io.Writer, templ string, data any, customFuncs map[string]any) { + const maxLineLength = 10000 + + cmd, ok := data.(*Command) + if !ok { + panic(errNoTemplate) + } + + h := helpRenderer{ + cmd: cmd, + wrap: func(input string, offset int) string { return wrap(input, offset, maxLineLength) }, + } + if f, ok := customFuncs["wrap"].(func(string, int) string); ok { + h.wrap = f + } + if wa, ok := customFuncs["wrapAt"]; ok { + if wrapAtFunc, ok := wa.(func() int); ok { + wrapAt := wrapAtFunc() + h.wrap = func(input string, offset int) string { + return wrap(input, offset, wrapAt) + } + } + } + + switch templ { + case RootCommandHelpTemplate: + h.root() + case CommandHelpTemplate: + h.command() + case SubcommandHelpTemplate: + h.subcommand() + default: + panic(fmt.Errorf("command %q: %w", cmd.FullName(), errNoTemplate)) + } + + w := tabwriter.NewWriter(out, 1, 8, 2, ' ', 0) + _, err := io.WriteString(w, h.String()) + handleTemplateError(err) + _ = w.Flush() +} + +// helpRenderer renders the default help templates (see template.go) +// without using text/template. +type helpRenderer struct { + strings.Builder + cmd *Command + wrap func(string, int) string +} + +func (h *helpRenderer) root() { + c := h.cmd + h.WriteString("NAME:\n ") + h.helpName() + h.WriteString("\n\nUSAGE:\n ") + if c.UsageText != "" { + h.WriteString(h.wrap(c.UsageText, 3)) + } else { + h.WriteString(c.FullName() + " ") + if len(c.VisibleFlags()) > 0 { + h.WriteString("[global options]") + } + if len(c.VisibleCommands()) > 0 { + h.WriteString(" [command [command options]]") + } + h.argsUsage() + } + if c.Version != "" && !c.HideVersion { + h.WriteString("\n\nVERSION:\n " + c.Version) + } + h.description() + if len(c.Authors) > 0 { + h.WriteString("\n\nAUTHOR") + h.authors() + } + if len(c.VisibleCommands()) > 0 { + h.WriteString("\n\nCOMMANDS:") + h.visibleCommandCategories() + } + h.flags("GLOBAL OPTIONS:") + if c.Copyright != "" { + h.WriteString("\n\nCOPYRIGHT:\n " + h.wrap(c.Copyright, 3)) + } + h.WriteString("\n") +} + +func (h *helpRenderer) command() { + h.WriteString("NAME:\n ") + h.helpName() + h.WriteString("\n\nUSAGE:\n ") + h.usage() + h.category() + h.description() + h.flags("OPTIONS:") + h.persistentFlags() + h.WriteString("\n") +} + +func (h *helpRenderer) subcommand() { + c := h.cmd + h.WriteString("NAME:\n ") + h.helpName() + h.WriteString("\n\nUSAGE:\n ") + if c.UsageText != "" { + h.WriteString(h.wrap(c.UsageText, 3)) + } else { + h.WriteString(c.FullName()) + if len(c.VisibleCommands()) > 0 { + h.WriteString(" [command [command options]]") + } + h.argsUsage() + } + h.category() + h.description() + if cmds := c.VisibleCommands(); len(cmds) > 0 { + h.WriteString("\n\nCOMMANDS:") + h.visibleCommands(cmds) + } + h.flags("OPTIONS:") + h.persistentFlags() + h.WriteString("\n") +} + +// helpName implements helpNameTemplate. +func (h *helpRenderer) helpName() { + name := h.cmd.FullName() + h.WriteString(h.wrap(name, 3)) + if h.cmd.Usage != "" { + h.WriteString(" - " + h.wrap(h.cmd.Usage, offset(name, 6))) + } +} + +// usage implements usageTemplate. +func (h *helpRenderer) usage() { + c := h.cmd + if c.UsageText != "" { + h.WriteString(h.wrap(c.UsageText, 3)) + return + } + h.WriteString(c.FullName()) + if len(c.VisibleFlags()) > 0 { + h.WriteString(" [options]") + } + if len(c.VisibleCommands()) > 0 { + h.WriteString(" [command [command options]]") + } + if c.ArgsUsage != "" { + h.WriteString(" " + c.ArgsUsage) + } else if len(c.Arguments) > 0 { + // argsTemplate. + h.WriteString(" ") + for _, arg := range c.Arguments { + h.WriteString(arg.Usage() + " ") + } + } +} + +// argsUsage implements the part of root and subcommand usage +// that follows the command name and options. +func (h *helpRenderer) argsUsage() { + if h.cmd.ArgsUsage != "" { + h.WriteString(" " + h.cmd.ArgsUsage) + } else if len(h.cmd.Arguments) > 0 { + h.WriteString(" [arguments...]") + } +} + +func (h *helpRenderer) category() { + if h.cmd.Category != "" { + h.WriteString("\n\nCATEGORY:\n " + h.cmd.Category) + } +} + +// description implements descriptionTemplate. +func (h *helpRenderer) description() { + if h.cmd.Description != "" { + h.WriteString("\n\nDESCRIPTION:\n " + h.wrap(h.cmd.Description, 3)) + } +} + +// authors implements authorsTemplate. +func (h *helpRenderer) authors() { + if len(h.cmd.Authors) != 1 { + h.WriteString("S") + } + h.WriteString(":\n ") + for i, author := range h.cmd.Authors { + if i > 0 { + h.WriteString("\n ") + } + h.WriteString(printableValue(author)) + } +} + +// visibleCommandCategories implements visibleCommandCategoryTemplate. +func (h *helpRenderer) visibleCommandCategories() { + for _, cat := range h.cmd.VisibleCategories() { + if cat.Name() == "" { + h.visibleCommands(cat.VisibleCommands()) + continue + } + h.WriteString("\n\n " + cat.Name() + ":") + for _, cmd := range cat.VisibleCommands() { + h.WriteString("\n " + strings.Join(cmd.Names(), ", ") + "\t" + cmd.Usage) + } + } +} + +// visibleCommands implements visibleCommandTemplate. +func (h *helpRenderer) visibleCommands(cmds []*Command) { + cv := offsetCommands(cmds, 5) + for _, cmd := range cmds { + s := strings.Join(cmd.Names(), ", ") + h.WriteString("\n " + s + indent(subtract(cv, offset(s, 3)), "") + h.wrap(cmd.Usage, cv)) + } +} + +// flags implements the OPTIONS (or GLOBAL OPTIONS) section, +// using visibleFlagCategoryTemplate or visibleFlagTemplate. +func (h *helpRenderer) flags(title string) { + if cats := h.cmd.VisibleFlagCategories(); len(cats) > 0 { + h.WriteString("\n\n" + title) + for _, cat := range cats { + h.WriteString("\n ") + if cat.Name() != "" { + h.WriteString(cat.Name() + "\n\n ") + } + flags := cat.Flags() + for i, f := range flags { + h.WriteString(printableValue(f)) + if i == len(flags)-1 { + h.WriteString("\n") + } else { + h.WriteString("\n ") + } + } + } + } else if flags := h.cmd.VisibleFlags(); len(flags) > 0 { + h.WriteString("\n\n" + title) + h.visibleFlags(flags) + } +} + +// persistentFlags implements the GLOBAL OPTIONS section +// of command and subcommand help. +func (h *helpRenderer) persistentFlags() { + if flags := h.cmd.VisiblePersistentFlags(); len(flags) > 0 { + h.WriteString("\n\nGLOBAL OPTIONS:") + h.visibleFlags(flags) + } +} + +// visibleFlags implements visibleFlagTemplate +// and visiblePersistentFlagTemplate. +func (h *helpRenderer) visibleFlags(flags []Flag) { + for _, f := range flags { + h.WriteString("\n " + h.wrap(f.String(), 6)) + } +} + +// printableValue formats v the same way text/template prints a value. +func printableValue(v any) string { + if v == nil { + return "" + } + switch v.(type) { + case error, fmt.Stringer: + return fmt.Sprint(v) + } + rv := reflect.ValueOf(v) + for rv.Kind() == reflect.Pointer && !rv.IsNil() { + rv = rv.Elem() + } + return fmt.Sprint(rv.Interface()) +} + +func renderFishCompletion(w io.Writer, data *fishCommandCompletionTemplate) error { + name := data.Command.Name + var b strings.Builder + b.WriteString("# " + name + " fish shell completion\n\n" + + "function __fish_" + name + "_no_subcommand --description 'Test if there has been any subcommand yet'\n" + + " for i in (commandline -opc)\n" + + " if contains -- $i") + for _, c := range data.AllCommands { + b.WriteString(" " + c) + } + b.WriteString("\n" + + " return 1\n" + + " end\n" + + " end\n" + + " return 0\n" + + "end\n\n") + for _, c := range data.Completions { + b.WriteString(c + "\n") + } + + _, err := io.WriteString(w, b.String()) + return err +} diff --git a/vendor/github.com/urfave/cli/v3/help_template.go b/vendor/github.com/urfave/cli/v3/help_template.go new file mode 100644 index 000000000..a05cb5aee --- /dev/null +++ b/vendor/github.com/urfave/cli/v3/help_template.go @@ -0,0 +1,204 @@ +//go:build !urfave_cli_no_template + +package cli + +import ( + "io" + "strings" + "text/tabwriter" + "text/template" +) + +// templatesSupported is true unless built with the urfave_cli_no_template tag. +const templatesSupported = true + +// RootCommandHelpTemplate is the text template for the Default help topic. +// cli.go uses text/template to render templates. You can +// render custom help text by setting this variable. +var RootCommandHelpTemplate = `NAME: + {{template "helpNameTemplate" .}} + +USAGE: + {{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.FullName}} {{if .VisibleFlags}}[global options]{{end}}{{if .VisibleCommands}} [command [command options]]{{end}}{{if .ArgsUsage}} {{.ArgsUsage}}{{else}}{{if .Arguments}} [arguments...]{{end}}{{end}}{{end}}{{if .Version}}{{if not .HideVersion}} + +VERSION: + {{.Version}}{{end}}{{end}}{{if .Description}} + +DESCRIPTION: + {{template "descriptionTemplate" .}}{{end}} +{{- if len .Authors}} + +AUTHOR{{template "authorsTemplate" .}}{{end}}{{if .VisibleCommands}} + +COMMANDS:{{template "visibleCommandCategoryTemplate" .}}{{end}}{{if .VisibleFlagCategories}} + +GLOBAL OPTIONS:{{template "visibleFlagCategoryTemplate" .}}{{else if .VisibleFlags}} + +GLOBAL OPTIONS:{{template "visibleFlagTemplate" .}}{{end}}{{if .Copyright}} + +COPYRIGHT: + {{template "copyrightTemplate" .}}{{end}} +` + +// CommandHelpTemplate is 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. +var CommandHelpTemplate = `NAME: + {{template "helpNameTemplate" .}} + +USAGE: + {{template "usageTemplate" .}}{{if .Category}} + +CATEGORY: + {{.Category}}{{end}}{{if .Description}} + +DESCRIPTION: + {{template "descriptionTemplate" .}}{{end}}{{if .VisibleFlagCategories}} + +OPTIONS:{{template "visibleFlagCategoryTemplate" .}}{{else if .VisibleFlags}} + +OPTIONS:{{template "visibleFlagTemplate" .}}{{end}}{{if .VisiblePersistentFlags}} + +GLOBAL OPTIONS:{{template "visiblePersistentFlagTemplate" .}}{{end}} +` + +// SubcommandHelpTemplate is the text template for the subcommand help topic. +// cli.go uses text/template to render templates. You can +// render custom help text by setting this variable. +var SubcommandHelpTemplate = `NAME: + {{template "helpNameTemplate" .}} + +USAGE: + {{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.FullName}}{{if .VisibleCommands}} [command [command options]]{{end}}{{if .ArgsUsage}} {{.ArgsUsage}}{{else}}{{if .Arguments}} [arguments...]{{end}}{{end}}{{end}}{{if .Category}} + +CATEGORY: + {{.Category}}{{end}}{{if .Description}} + +DESCRIPTION: + {{template "descriptionTemplate" .}}{{end}}{{if .VisibleCommands}} + +COMMANDS:{{template "visibleCommandTemplate" .}}{{end}}{{if .VisibleFlagCategories}} + +OPTIONS:{{template "visibleFlagCategoryTemplate" .}}{{else if .VisibleFlags}} + +OPTIONS:{{template "visibleFlagTemplate" .}}{{end}}{{if .VisiblePersistentFlags}} + +GLOBAL OPTIONS:{{template "visiblePersistentFlagTemplate" .}}{{end}} +` + +var FishCompletionTemplate = `# {{ .Command.Name }} fish shell completion + +function __fish_{{ .Command.Name }}_no_subcommand --description 'Test if there has been any subcommand yet' + for i in (commandline -opc) + if contains -- $i{{ range $v := .AllCommands }} {{ $v }}{{ end }} + return 1 + end + end + return 0 +end + +{{ range $v := .Completions }}{{ $v }} +{{ end }}` + +// DefaultPrintHelpCustom is the default implementation of HelpPrinterCustom. +// +// The customFuncs map will be combined with a default template.FuncMap to +// allow using arbitrary functions in template rendering. +func DefaultPrintHelpCustom(out io.Writer, templ string, data any, customFuncs map[string]any) { + const maxLineLength = 10000 + + tracef("building default funcMap") + funcMap := template.FuncMap{ + "join": strings.Join, + "subtract": subtract, + "indent": indent, + "nindent": nindent, + "trim": strings.TrimSpace, + "wrap": func(input string, offset int) string { return wrap(input, offset, maxLineLength) }, + "offset": offset, + "offsetCommands": offsetCommands, + } + + if wa, ok := customFuncs["wrapAt"]; ok { + if wrapAtFunc, ok := wa.(func() int); ok { + wrapAt := wrapAtFunc() + customFuncs["wrap"] = func(input string, offset int) string { + return wrap(input, offset, wrapAt) + } + } + } + + for key, value := range customFuncs { + funcMap[key] = value + } + + w := tabwriter.NewWriter(out, 1, 8, 2, ' ', 0) + t := template.Must(template.New("help").Funcs(funcMap).Parse(templ)) + + if _, err := t.New("helpNameTemplate").Parse(helpNameTemplate); err != nil { + handleTemplateError(err) + } + + if _, err := t.New("argsTemplate").Parse(argsTemplate); err != nil { + handleTemplateError(err) + } + + if _, err := t.New("usageTemplate").Parse(usageTemplate); err != nil { + handleTemplateError(err) + } + + if _, err := t.New("descriptionTemplate").Parse(descriptionTemplate); err != nil { + handleTemplateError(err) + } + + if _, err := t.New("visibleCommandTemplate").Parse(visibleCommandTemplate); err != nil { + handleTemplateError(err) + } + + if _, err := t.New("copyrightTemplate").Parse(copyrightTemplate); err != nil { + handleTemplateError(err) + } + + if _, err := t.New("versionTemplate").Parse(versionTemplate); err != nil { + handleTemplateError(err) + } + + if _, err := t.New("visibleFlagCategoryTemplate").Parse(visibleFlagCategoryTemplate); err != nil { + handleTemplateError(err) + } + + if _, err := t.New("visibleFlagTemplate").Parse(visibleFlagTemplate); err != nil { + handleTemplateError(err) + } + + if _, err := t.New("visiblePersistentFlagTemplate").Parse(visiblePersistentFlagTemplate); err != nil { + handleTemplateError(err) + } + + if _, err := t.New("visibleGlobalFlagCategoryTemplate").Parse(strings.ReplaceAll(visibleFlagCategoryTemplate, "OPTIONS", "GLOBAL OPTIONS")); err != nil { + handleTemplateError(err) + } + + if _, err := t.New("authorsTemplate").Parse(authorsTemplate); err != nil { + handleTemplateError(err) + } + + if _, err := t.New("visibleCommandCategoryTemplate").Parse(visibleCommandCategoryTemplate); err != nil { + handleTemplateError(err) + } + + tracef("executing template") + handleTemplateError(t.Execute(w, data)) + + _ = w.Flush() +} + +func renderFishCompletion(w io.Writer, data *fishCommandCompletionTemplate) error { + const name = "cli" + t, err := template.New(name).Parse(FishCompletionTemplate) + if err != nil { + return err + } + + return t.ExecuteTemplate(w, name, data) +} diff --git a/vendor/github.com/urfave/cli/v3/mkdocs-requirements.txt b/vendor/github.com/urfave/cli/v3/mkdocs-requirements.txt index 701403ae5..8ed82f8c5 100644 --- a/vendor/github.com/urfave/cli/v3/mkdocs-requirements.txt +++ b/vendor/github.com/urfave/cli/v3/mkdocs-requirements.txt @@ -1,5 +1,5 @@ -mkdocs-git-revision-date-localized-plugin==1.5.3 -mkdocs-material==9.7.6 +mkdocs-git-revision-date-localized-plugin==1.6.0 +mkdocs-material==9.7.7 mkdocs==1.6.1 mkdocs-redirects==1.2.3 -pygments==2.20.0 +pygments==2.21.0 diff --git a/vendor/github.com/urfave/cli/v3/suggestions.go b/vendor/github.com/urfave/cli/v3/suggestions.go index 401dcef25..4c1ce9aad 100644 --- a/vendor/github.com/urfave/cli/v3/suggestions.go +++ b/vendor/github.com/urfave/cli/v3/suggestions.go @@ -120,13 +120,14 @@ func suggestFlag(flags []Flag, provided string, hideHelp bool) string { } } - if len(suggestion) == 1 { - suggestion = "-" + suggestion - } else if len(suggestion) > 1 { - suggestion = "--" + suggestion + if len(suggestion) == 0 { + return "" } - return suggestion + // Use the same rune-counting rule that drives the help output + // (prefixFor) so that a single-rune name is suggested as a short flag + // regardless of how many bytes that rune is encoded in. + return prefixFor(suggestion) + suggestion } // suggestCommand takes a list of commands and a provided string to suggest a diff --git a/vendor/github.com/urfave/cli/v3/template.go b/vendor/github.com/urfave/cli/v3/template.go index 92030cc8f..48fbcf859 100644 --- a/vendor/github.com/urfave/cli/v3/template.go +++ b/vendor/github.com/urfave/cli/v3/template.go @@ -37,91 +37,3 @@ VERSION: {{.Version}}{{end}}{{end}}` var copyrightTemplate = `{{wrap .Copyright 3}}` - -// RootCommandHelpTemplate is the text template for the Default help topic. -// cli.go uses text/template to render templates. You can -// render custom help text by setting this variable. -var RootCommandHelpTemplate = `NAME: - {{template "helpNameTemplate" .}} - -USAGE: - {{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.FullName}} {{if .VisibleFlags}}[global options]{{end}}{{if .VisibleCommands}} [command [command options]]{{end}}{{if .ArgsUsage}} {{.ArgsUsage}}{{else}}{{if .Arguments}} [arguments...]{{end}}{{end}}{{end}}{{if .Version}}{{if not .HideVersion}} - -VERSION: - {{.Version}}{{end}}{{end}}{{if .Description}} - -DESCRIPTION: - {{template "descriptionTemplate" .}}{{end}} -{{- if len .Authors}} - -AUTHOR{{template "authorsTemplate" .}}{{end}}{{if .VisibleCommands}} - -COMMANDS:{{template "visibleCommandCategoryTemplate" .}}{{end}}{{if .VisibleFlagCategories}} - -GLOBAL OPTIONS:{{template "visibleFlagCategoryTemplate" .}}{{else if .VisibleFlags}} - -GLOBAL OPTIONS:{{template "visibleFlagTemplate" .}}{{end}}{{if .Copyright}} - -COPYRIGHT: - {{template "copyrightTemplate" .}}{{end}} -` - -// CommandHelpTemplate is 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. -var CommandHelpTemplate = `NAME: - {{template "helpNameTemplate" .}} - -USAGE: - {{template "usageTemplate" .}}{{if .Category}} - -CATEGORY: - {{.Category}}{{end}}{{if .Description}} - -DESCRIPTION: - {{template "descriptionTemplate" .}}{{end}}{{if .VisibleFlagCategories}} - -OPTIONS:{{template "visibleFlagCategoryTemplate" .}}{{else if .VisibleFlags}} - -OPTIONS:{{template "visibleFlagTemplate" .}}{{end}}{{if .VisiblePersistentFlags}} - -GLOBAL OPTIONS:{{template "visiblePersistentFlagTemplate" .}}{{end}} -` - -// SubcommandHelpTemplate is the text template for the subcommand help topic. -// cli.go uses text/template to render templates. You can -// render custom help text by setting this variable. -var SubcommandHelpTemplate = `NAME: - {{template "helpNameTemplate" .}} - -USAGE: - {{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.FullName}}{{if .VisibleCommands}} [command [command options]]{{end}}{{if .ArgsUsage}} {{.ArgsUsage}}{{else}}{{if .Arguments}} [arguments...]{{end}}{{end}}{{end}}{{if .Category}} - -CATEGORY: - {{.Category}}{{end}}{{if .Description}} - -DESCRIPTION: - {{template "descriptionTemplate" .}}{{end}}{{if .VisibleCommands}} - -COMMANDS:{{template "visibleCommandTemplate" .}}{{end}}{{if .VisibleFlagCategories}} - -OPTIONS:{{template "visibleFlagCategoryTemplate" .}}{{else if .VisibleFlags}} - -OPTIONS:{{template "visibleFlagTemplate" .}}{{end}}{{if .VisiblePersistentFlags}} - -GLOBAL OPTIONS:{{template "visiblePersistentFlagTemplate" .}}{{end}} -` - -var FishCompletionTemplate = `# {{ .Command.Name }} fish shell completion - -function __fish_{{ .Command.Name }}_no_subcommand --description 'Test if there has been any subcommand yet' - for i in (commandline -opc) - if contains -- $i{{ range $v := .AllCommands }} {{ $v }}{{ end }} - return 1 - end - end - return 0 -end - -{{ range $v := .Completions }}{{ $v }} -{{ end }}` diff --git a/vendor/github.com/urfave/cli/v3/value_source.go b/vendor/github.com/urfave/cli/v3/value_source.go index 44b0173b4..2dcee7587 100644 --- a/vendor/github.com/urfave/cli/v3/value_source.go +++ b/vendor/github.com/urfave/cli/v3/value_source.go @@ -119,6 +119,7 @@ func (e *envVarValueSource) Key() string { } func (e *envVarValueSource) String() string { return fmt.Sprintf("environment variable %[1]q", e.key) } + func (e *envVarValueSource) GoString() string { return fmt.Sprintf("&envVarValueSource{Key:%[1]q}", e.key) } diff --git a/vendor/modules.txt b/vendor/modules.txt index 14988a94f..aa1b986cb 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -146,7 +146,7 @@ github.com/tetratelabs/wazero/sys # github.com/urfave/cli-altsrc/v3 v3.1.0 ## explicit; go 1.23.2 github.com/urfave/cli-altsrc/v3 -# github.com/urfave/cli/v3 v3.10.1 +# github.com/urfave/cli/v3 v3.14.0 ## explicit; go 1.22 github.com/urfave/cli/v3 # go.yaml.in/yaml/v3 v3.0.5