Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion args.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,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
Expand All @@ -103,13 +104,20 @@ func (a *ArgumentBase[T, C, VC]) Usage() string {
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, fmt.Errorf("required argument %q not set", a.Name)
}

var vc VC
var t T
value := vc.Create(a.Value, &t, a.Config)
Expand Down
83 changes: 82 additions & 1 deletion args_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -422,10 +422,16 @@ func TestArgUsage(t *testing.T) {
tests := []struct {
name string
usage string
required bool
expected string
}{
{
name: "default",
expected: "[ia]",
},
{
name: "required",
required: true,
expected: "ia",
},
{
Expand All @@ -436,7 +442,7 @@ func TestArgUsage(t *testing.T) {
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
arg.UsageText = test.usage
arg.UsageText, arg.Required = test.usage, test.required
require.Equal(t, test.expected, arg.Usage())
})
}
Expand Down Expand Up @@ -552,6 +558,81 @@ func TestSingleOptionalArg(t *testing.T) {
}
}

func TestSingleRequiredArg(t *testing.T) {
tests := []struct {
name string
args []string
argValue string
exp string
expErr string
}{
{
name: "no args",
args: []string{"foo"},
expErr: `required argument "sa" not set`,
},
{
name: "no arg with def value",
args: []string{"foo"},
argValue: "bar",
expErr: `required argument "sa" not set`,
},
{
name: "one arg",
args: []string{"foo", "zbar"},
exp: "zbar",
},
{
name: "empty string arg",
args: []string{"foo", ""},
exp: "",
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
cmd := buildMinimalTestCommand()
var s1 string
arg := &StringArg{
Name: "sa",
Value: test.argValue,
Destination: &s1,
Required: true,
}
cmd.Arguments = []Argument{
arg,
}

err := cmd.Run(buildTestContext(t), test.args)
r := require.New(t)
if test.expErr != "" {
r.EqualError(err, test.expErr)
return
}
r.NoError(err)
r.Equal(test.exp, s1)
})
}
}

func TestChainedRequiredArgs(t *testing.T) {
cmd := buildMinimalTestCommand()
cmd.Arguments = []Argument{
&StringArg{
Name: "first",
Required: true,
},
&StringArg{
Name: "second",
Required: true,
},
}

r := require.New(t)
r.EqualError(cmd.Run(buildTestContext(t), []string{"foo", "one"}), `required argument "second" not set`)
r.NoError(cmd.Run(buildTestContext(t), []string{"foo", "one", "two"}))
}

func TestUnboundedArgs(t *testing.T) {
arg := &StringArgs{
Min: 0,
Expand Down
39 changes: 39 additions & 0 deletions docs/v3/examples/arguments/advanced.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,45 @@ Some of the basic types arguments supported are
This is ok for single value arguments. Any number of these single value arguments can be concatenated in the `Arguments`
slice field of `Command`.

Single value arguments are optional by default. If the argument is not provided the default `Value` is used instead.
You can mark a single value argument as *required* by setting the `Required` field to `true`. If a user does not
provide a required argument, they will be shown an error message.

<!-- {
"error": "required argument \"someint\" not set"
} -->
```go
package main

import (
"fmt"
"log"
"os"
"context"

"github.com/urfave/cli/v3"
)

func main() {
cmd := &cli.Command{
Arguments: []cli.Argument{
&cli.IntArg{
Name: "someint",
Required: true,
},
},
Action: func(ctx context.Context, cmd *cli.Command) error {
fmt.Printf("We got %d", cmd.IntArg("someint"))
return nil
},
}

if err := cmd.Run(context.Background(), os.Args); err != nil {
log.Fatal(err)
}
}
```

The library also support multi value arguments for e.g

<!-- {
Expand Down
1 change: 1 addition & 0 deletions godoc-current.txt
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,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.
Expand Down
1 change: 1 addition & 0 deletions testdata/godoc-v3.x.txt
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,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.
Expand Down