diff options
| -rw-r--r-- | example_test.go | 8 | ||||
| -rw-r--r-- | go.mod | 2 | ||||
| -rw-r--r-- | go.sum | 11 | ||||
| -rw-r--r-- | usage.go | 26 | ||||
| -rw-r--r-- | usage_test.go | 82 |
5 files changed, 105 insertions, 24 deletions
diff --git a/example_test.go b/example_test.go index 20b9225..fd64777 100644 --- a/example_test.go +++ b/example_test.go @@ -154,7 +154,7 @@ func Example_helpText() { os.Args = split("./example --help") var args struct { - Input string `arg:"positional"` + Input string `arg:"positional,required"` Output []string `arg:"positional"` Verbose bool `arg:"-v" help:"verbosity level"` Dataset string `help:"dataset to use"` @@ -188,7 +188,7 @@ func Example_helpPlaceholder() { os.Args = split("./example --help") var args struct { - Input string `arg:"positional" placeholder:"SRC"` + Input string `arg:"positional,required" placeholder:"SRC"` Output []string `arg:"positional" placeholder:"DST"` Optimize int `arg:"-O" help:"optimization level" placeholder:"LEVEL"` MaxJobs int `arg:"-j" help:"maximum number of simultaneous jobs" placeholder:"N"` @@ -259,7 +259,7 @@ func Example_helpTextWhenUsingSubcommand() { os.Args = split("./example get --help") type getCmd struct { - Item string `arg:"positional" help:"item to fetch"` + Item string `arg:"positional,required" help:"item to fetch"` } type listCmd struct { @@ -389,7 +389,7 @@ func Example_errorText() { os.Args = split("./example --optimize INVALID") var args struct { - Input string `arg:"positional"` + Input string `arg:"positional,required"` Output []string `arg:"positional"` Verbose bool `arg:"-v" help:"verbosity level"` Dataset string `help:"dataset to use"` @@ -2,7 +2,7 @@ module github.com/alexflint/go-arg require ( github.com/alexflint/go-scalar v1.1.0 - github.com/stretchr/testify v1.2.2 + github.com/stretchr/testify v1.7.0 ) go 1.13 @@ -1,10 +1,15 @@ -github.com/alexflint/go-scalar v1.0.0 h1:NGupf1XV/Xb04wXskDFzS0KWOLH632W/EO4fAFi+A70= -github.com/alexflint/go-scalar v1.0.0/go.mod h1:GpHzbCOZXEKMEcygYQ5n/aa4Aq84zbxjy3MxYW0gjYw= github.com/alexflint/go-scalar v1.1.0 h1:aaAouLLzI9TChcPXotr6gUhq+Scr8rl0P9P4PnltbhM= github.com/alexflint/go-scalar v1.1.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= @@ -124,22 +124,32 @@ func (p *Parser) writeUsageForSubcommand(w io.Writer, cmd *command) { } } - // write the positional component of the usage message + // When we parse positionals, we check that: + // 1. required positionals come before non-required positionals + // 2. there is at most one multiple-value positional + // 3. if there is a multiple-value positional then it comes after all other positionals + // Here we merely print the usage string, so we do not explicitly re-enforce those rules + + // write the positionals in following form: + // REQUIRED1 REQUIRED2 + // REQUIRED1 REQUIRED2 [OPTIONAL1 [OPTIONAL2]] + // REQUIRED1 REQUIRED2 REPEATED [REPEATED ...] + // REQUIRED1 REQUIRED2 [REPEATEDOPTIONAL [REPEATEDOPTIONAL ...]] + // REQUIRED1 REQUIRED2 [OPTIONAL1 [REPEATEDOPTIONAL [REPEATEDOPTIONAL ...]]] + var closeBrackets int for _, spec := range positionals { - // prefix with a space fmt.Fprint(w, " ") + if !spec.required { + fmt.Fprint(w, "[") + closeBrackets += 1 + } if spec.cardinality == multiple { - if !spec.required { - fmt.Fprint(w, "[") - } fmt.Fprintf(w, "%s [%s ...]", spec.placeholder, spec.placeholder) - if !spec.required { - fmt.Fprint(w, "]") - } } else { fmt.Fprint(w, spec.placeholder) } } + fmt.Fprint(w, strings.Repeat("]", closeBrackets)) // if the program supports subcommands, give a hint to the user about their existence if len(cmd.subcommands) > 0 { diff --git a/usage_test.go b/usage_test.go index f790e34..1744536 100644 --- a/usage_test.go +++ b/usage_test.go @@ -59,7 +59,7 @@ Options: ` var args struct { - Input string `arg:"positional"` + Input string `arg:"positional,required"` Output []string `arg:"positional" help:"list of outputs"` Name string `help:"name to use"` Value int `help:"secret value"` @@ -141,10 +141,10 @@ func TestUsageCannotMarshalToString(t *testing.T) { } func TestUsageLongPositionalWithHelp_legacyForm(t *testing.T) { - expectedUsage := "Usage: example VERYLONGPOSITIONALWITHHELP" + expectedUsage := "Usage: example [VERYLONGPOSITIONALWITHHELP]" expectedHelp := ` -Usage: example VERYLONGPOSITIONALWITHHELP +Usage: example [VERYLONGPOSITIONALWITHHELP] Positional arguments: VERYLONGPOSITIONALWITHHELP @@ -170,10 +170,10 @@ Options: } func TestUsageLongPositionalWithHelp_newForm(t *testing.T) { - expectedUsage := "Usage: example VERYLONGPOSITIONALWITHHELP" + expectedUsage := "Usage: example [VERYLONGPOSITIONALWITHHELP]" expectedHelp := ` -Usage: example VERYLONGPOSITIONALWITHHELP +Usage: example [VERYLONGPOSITIONALWITHHELP] Positional arguments: VERYLONGPOSITIONALWITHHELP @@ -285,8 +285,74 @@ Options: assert.Equal(t, expectedUsage, strings.TrimSpace(usage.String())) } +func TestUsageForRequiredPositionals(t *testing.T) { + expectedUsage := "Usage: example REQUIRED1 REQUIRED2\n" + var args struct { + Required1 string `arg:"positional,required"` + Required2 string `arg:"positional,required"` + } + + p, err := NewParser(Config{Program: "example"}, &args) + require.NoError(t, err) + + var usage bytes.Buffer + p.WriteUsage(&usage) + assert.Equal(t, expectedUsage, usage.String()) +} + +func TestUsageForMixedPositionals(t *testing.T) { + expectedUsage := "Usage: example REQUIRED1 REQUIRED2 [OPTIONAL1 [OPTIONAL2]]\n" + var args struct { + Required1 string `arg:"positional,required"` + Required2 string `arg:"positional,required"` + Optional1 string `arg:"positional"` + Optional2 string `arg:"positional"` + } + + p, err := NewParser(Config{Program: "example"}, &args) + require.NoError(t, err) + + var usage bytes.Buffer + p.WriteUsage(&usage) + assert.Equal(t, expectedUsage, usage.String()) +} + +func TestUsageForRepeatedPositionals(t *testing.T) { + expectedUsage := "Usage: example REQUIRED1 REQUIRED2 REPEATED [REPEATED ...]\n" + var args struct { + Required1 string `arg:"positional,required"` + Required2 string `arg:"positional,required"` + Repeated []string `arg:"positional,required"` + } + + p, err := NewParser(Config{Program: "example"}, &args) + require.NoError(t, err) + + var usage bytes.Buffer + p.WriteUsage(&usage) + assert.Equal(t, expectedUsage, usage.String()) +} + +func TestUsageForMixedAndRepeatedPositionals(t *testing.T) { + expectedUsage := "Usage: example REQUIRED1 REQUIRED2 [OPTIONAL1 [OPTIONAL2 [REPEATED [REPEATED ...]]]]\n" + var args struct { + Required1 string `arg:"positional,required"` + Required2 string `arg:"positional,required"` + Optional1 string `arg:"positional"` + Optional2 string `arg:"positional"` + Repeated []string `arg:"positional"` + } + + p, err := NewParser(Config{Program: "example"}, &args) + require.NoError(t, err) + + var usage bytes.Buffer + p.WriteUsage(&usage) + assert.Equal(t, expectedUsage, usage.String()) +} + func TestRequiredMultiplePositionals(t *testing.T) { - expectedUsage := "Usage: example REQUIREDMULTIPLE [REQUIREDMULTIPLE ...]" + expectedUsage := "Usage: example REQUIREDMULTIPLE [REQUIREDMULTIPLE ...]\n" expectedHelp := ` Usage: example REQUIREDMULTIPLE [REQUIREDMULTIPLE ...] @@ -301,7 +367,7 @@ Options: RequiredMultiple []string `arg:"positional,required" help:"required multiple positional"` } - p, err := NewParser(Config{}, &args) + p, err := NewParser(Config{Program: "example"}, &args) require.NoError(t, err) var help bytes.Buffer @@ -310,7 +376,7 @@ Options: var usage bytes.Buffer p.WriteUsage(&usage) - assert.Equal(t, expectedUsage, strings.TrimSpace(usage.String())) + assert.Equal(t, expectedUsage, usage.String()) } func TestUsageWithNestedSubcommands(t *testing.T) { |
