diff --git a/args_test.go b/args_test.go index 7ae337487f..39ba66ba72 100644 --- a/args_test.go +++ b/args_test.go @@ -507,6 +507,30 @@ func TestArgsUsage(t *testing.T) { } } +func TestArgsConsumedByNamedArguments(t *testing.T) { + var namedVal string + cmd := buildMinimalTestCommand() + cmd.Arguments = []Argument{ + &StringArg{ + Name: "first", + Destination: &namedVal, + }, + } + + var leftover []string + cmd.Action = func(_ context.Context, c *Command) error { + leftover = c.Args().Slice() + return nil + } + + err := cmd.Run(buildTestContext(t), []string{"foo", "boo", "bar"}) + r := require.New(t) + r.NoError(err) + r.Equal("boo", namedVal) + r.Equal("boo", cmd.StringArg("first")) + r.Equal([]string{"bar"}, leftover) +} + func TestSingleOptionalArg(t *testing.T) { tests := []struct { name string diff --git a/command.go b/command.go index f0cf9a715d..274bbe8989 100644 --- a/command.go +++ b/command.go @@ -622,7 +622,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/docs/v3/examples/arguments/advanced.md b/docs/v3/examples/arguments/advanced.md index 7ab3da9a55..a8b2c6ec48 100644 --- a/docs/v3/examples/arguments/advanced.md +++ b/docs/v3/examples/arguments/advanced.md @@ -222,3 +222,63 @@ the following to the end of the Arguments slice and retrieve them as a slice Max: -1, }, ``` + +## Mixing named arguments with `cmd.Args()` + +When a command declares named arguments in `Arguments`, each named argument consumes the positional arguments it needs +from the command line. The `cmd.Args()` method returns only the positional arguments that were **not** consumed by a +named argument. To retrieve the value of a named argument, use the `cmd.{Type}Arg()` function (for e.g `cmd.StringArg()`) +as described above. + +For example + + +```go +package main + +import ( + "context" + "fmt" + "log" + "os" + + "github.com/urfave/cli/v3" +) + +func main() { + cmd := &cli.Command{ + Arguments: []cli.Argument{ + &cli.StringArg{Name: "first"}, + }, + Action: func(ctx context.Context, cmd *cli.Command) error { + fmt.Printf("first=%q leftover=%v", cmd.StringArg("first"), cmd.Args().Slice()) + return nil + }, + } + + if err := cmd.Run(context.Background(), os.Args); err != nil { + log.Fatal(err) + } +} +``` + +```sh-session +$ greet boo bar +first="boo" leftover=[bar] +``` + +Here `boo` is consumed by the named `StringArg` and `cmd.Args()` contains only the leftover `bar`. To collect every +remaining positional argument as a slice, add a glob argument at the end of the `Arguments` slice and read it with the +corresponding `cmd.{Type}Args()` function: + +``` +&StringArgs{ + Name: "rest", + Max: -1, +}, +``` + +With the command above, `cmd.StringArgs("rest")` returns `[]string{"bar"}` while `cmd.Args()` is empty. diff --git a/docs/v3/examples/arguments/basics.md b/docs/v3/examples/arguments/basics.md index ddde3a4779..1d122e9657 100644 --- a/docs/v3/examples/arguments/basics.md +++ b/docs/v3/examples/arguments/basics.md @@ -91,3 +91,9 @@ $ greet Friend 1 bar 2.0 Number of args : 4 Hello Friend 1 bar 2.0 ``` + +!!! note + If the command declares named arguments via the `Arguments` field, each named argument consumes the positional + arguments it needs from the command line and `cmd.Args()` only returns the arguments that were **not** consumed by a + named argument. Use `cmd.{Type}Arg(name)` to retrieve the value of a named argument. See the + [advanced arguments documentation](advanced.md) for details. diff --git a/godoc-current.txt b/godoc-current.txt index b3e9797175..3e1a34dd95 100644 --- a/godoc-current.txt +++ b/godoc-current.txt @@ -567,6 +567,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 diff --git a/testdata/godoc-v3.x.txt b/testdata/godoc-v3.x.txt index b3e9797175..3e1a34dd95 100644 --- a/testdata/godoc-v3.x.txt +++ b/testdata/godoc-v3.x.txt @@ -567,6 +567,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