diff options
| author | Alex Flint <[email protected]> | 2022-02-09 18:04:03 -0800 |
|---|---|---|
| committer | GitHub <[email protected]> | 2022-02-09 18:04:03 -0800 |
| commit | f0f44b65d1179ccedb4c56f493f97ec569a6654e (patch) | |
| tree | 0d202fcd6c6a194e8c7c14bbc3af3ed386938fe6 | |
| parent | d3706100bf0cfef2b8f6f9119889541c302d72d1 (diff) | |
| parent | 5fb236a65dd1edf0de556d5bac83a1103b9cf73a (diff) | |
Merge pull request #175 from alexflint/bracketing-positionalsv1.4.3
Fix bracketing for non-required positionals in usage
| -rw-r--r-- | example_test.go | 8 | ||||
| -rw-r--r-- | usage.go | 26 | ||||
| -rw-r--r-- | usage_test.go | 82 |
3 files changed, 96 insertions, 20 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"` @@ -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) { |
