Merge pull request #175 from alexflint/bracketing-positionals
Fix bracketing for non-required positionals in usage
This commit is contained in:
commit
f0f44b65d1
|
@ -154,7 +154,7 @@ func Example_helpText() {
|
||||||
os.Args = split("./example --help")
|
os.Args = split("./example --help")
|
||||||
|
|
||||||
var args struct {
|
var args struct {
|
||||||
Input string `arg:"positional"`
|
Input string `arg:"positional,required"`
|
||||||
Output []string `arg:"positional"`
|
Output []string `arg:"positional"`
|
||||||
Verbose bool `arg:"-v" help:"verbosity level"`
|
Verbose bool `arg:"-v" help:"verbosity level"`
|
||||||
Dataset string `help:"dataset to use"`
|
Dataset string `help:"dataset to use"`
|
||||||
|
@ -188,7 +188,7 @@ func Example_helpPlaceholder() {
|
||||||
os.Args = split("./example --help")
|
os.Args = split("./example --help")
|
||||||
|
|
||||||
var args struct {
|
var args struct {
|
||||||
Input string `arg:"positional" placeholder:"SRC"`
|
Input string `arg:"positional,required" placeholder:"SRC"`
|
||||||
Output []string `arg:"positional" placeholder:"DST"`
|
Output []string `arg:"positional" placeholder:"DST"`
|
||||||
Optimize int `arg:"-O" help:"optimization level" placeholder:"LEVEL"`
|
Optimize int `arg:"-O" help:"optimization level" placeholder:"LEVEL"`
|
||||||
MaxJobs int `arg:"-j" help:"maximum number of simultaneous jobs" placeholder:"N"`
|
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")
|
os.Args = split("./example get --help")
|
||||||
|
|
||||||
type getCmd struct {
|
type getCmd struct {
|
||||||
Item string `arg:"positional" help:"item to fetch"`
|
Item string `arg:"positional,required" help:"item to fetch"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type listCmd struct {
|
type listCmd struct {
|
||||||
|
@ -389,7 +389,7 @@ func Example_errorText() {
|
||||||
os.Args = split("./example --optimize INVALID")
|
os.Args = split("./example --optimize INVALID")
|
||||||
|
|
||||||
var args struct {
|
var args struct {
|
||||||
Input string `arg:"positional"`
|
Input string `arg:"positional,required"`
|
||||||
Output []string `arg:"positional"`
|
Output []string `arg:"positional"`
|
||||||
Verbose bool `arg:"-v" help:"verbosity level"`
|
Verbose bool `arg:"-v" help:"verbosity level"`
|
||||||
Dataset string `help:"dataset to use"`
|
Dataset string `help:"dataset to use"`
|
||||||
|
|
26
usage.go
26
usage.go
|
@ -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 {
|
for _, spec := range positionals {
|
||||||
// prefix with a space
|
|
||||||
fmt.Fprint(w, " ")
|
fmt.Fprint(w, " ")
|
||||||
|
if !spec.required {
|
||||||
|
fmt.Fprint(w, "[")
|
||||||
|
closeBrackets += 1
|
||||||
|
}
|
||||||
if spec.cardinality == multiple {
|
if spec.cardinality == multiple {
|
||||||
if !spec.required {
|
|
||||||
fmt.Fprint(w, "[")
|
|
||||||
}
|
|
||||||
fmt.Fprintf(w, "%s [%s ...]", spec.placeholder, spec.placeholder)
|
fmt.Fprintf(w, "%s [%s ...]", spec.placeholder, spec.placeholder)
|
||||||
if !spec.required {
|
|
||||||
fmt.Fprint(w, "]")
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprint(w, spec.placeholder)
|
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 the program supports subcommands, give a hint to the user about their existence
|
||||||
if len(cmd.subcommands) > 0 {
|
if len(cmd.subcommands) > 0 {
|
||||||
|
|
|
@ -59,7 +59,7 @@ Options:
|
||||||
`
|
`
|
||||||
|
|
||||||
var args struct {
|
var args struct {
|
||||||
Input string `arg:"positional"`
|
Input string `arg:"positional,required"`
|
||||||
Output []string `arg:"positional" help:"list of outputs"`
|
Output []string `arg:"positional" help:"list of outputs"`
|
||||||
Name string `help:"name to use"`
|
Name string `help:"name to use"`
|
||||||
Value int `help:"secret value"`
|
Value int `help:"secret value"`
|
||||||
|
@ -141,10 +141,10 @@ func TestUsageCannotMarshalToString(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUsageLongPositionalWithHelp_legacyForm(t *testing.T) {
|
func TestUsageLongPositionalWithHelp_legacyForm(t *testing.T) {
|
||||||
expectedUsage := "Usage: example VERYLONGPOSITIONALWITHHELP"
|
expectedUsage := "Usage: example [VERYLONGPOSITIONALWITHHELP]"
|
||||||
|
|
||||||
expectedHelp := `
|
expectedHelp := `
|
||||||
Usage: example VERYLONGPOSITIONALWITHHELP
|
Usage: example [VERYLONGPOSITIONALWITHHELP]
|
||||||
|
|
||||||
Positional arguments:
|
Positional arguments:
|
||||||
VERYLONGPOSITIONALWITHHELP
|
VERYLONGPOSITIONALWITHHELP
|
||||||
|
@ -170,10 +170,10 @@ Options:
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUsageLongPositionalWithHelp_newForm(t *testing.T) {
|
func TestUsageLongPositionalWithHelp_newForm(t *testing.T) {
|
||||||
expectedUsage := "Usage: example VERYLONGPOSITIONALWITHHELP"
|
expectedUsage := "Usage: example [VERYLONGPOSITIONALWITHHELP]"
|
||||||
|
|
||||||
expectedHelp := `
|
expectedHelp := `
|
||||||
Usage: example VERYLONGPOSITIONALWITHHELP
|
Usage: example [VERYLONGPOSITIONALWITHHELP]
|
||||||
|
|
||||||
Positional arguments:
|
Positional arguments:
|
||||||
VERYLONGPOSITIONALWITHHELP
|
VERYLONGPOSITIONALWITHHELP
|
||||||
|
@ -285,8 +285,74 @@ Options:
|
||||||
assert.Equal(t, expectedUsage, strings.TrimSpace(usage.String()))
|
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) {
|
func TestRequiredMultiplePositionals(t *testing.T) {
|
||||||
expectedUsage := "Usage: example REQUIREDMULTIPLE [REQUIREDMULTIPLE ...]"
|
expectedUsage := "Usage: example REQUIREDMULTIPLE [REQUIREDMULTIPLE ...]\n"
|
||||||
|
|
||||||
expectedHelp := `
|
expectedHelp := `
|
||||||
Usage: example REQUIREDMULTIPLE [REQUIREDMULTIPLE ...]
|
Usage: example REQUIREDMULTIPLE [REQUIREDMULTIPLE ...]
|
||||||
|
@ -301,7 +367,7 @@ Options:
|
||||||
RequiredMultiple []string `arg:"positional,required" help:"required multiple positional"`
|
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)
|
require.NoError(t, err)
|
||||||
|
|
||||||
var help bytes.Buffer
|
var help bytes.Buffer
|
||||||
|
@ -310,7 +376,7 @@ Options:
|
||||||
|
|
||||||
var usage bytes.Buffer
|
var usage bytes.Buffer
|
||||||
p.WriteUsage(&usage)
|
p.WriteUsage(&usage)
|
||||||
assert.Equal(t, expectedUsage, strings.TrimSpace(usage.String()))
|
assert.Equal(t, expectedUsage, usage.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUsageWithNestedSubcommands(t *testing.T) {
|
func TestUsageWithNestedSubcommands(t *testing.T) {
|
||||||
|
|
Loading…
Reference in New Issue