diff --git a/parse.go b/parse.go index 8fdbd9d..3b4ecd3 100644 --- a/parse.go +++ b/parse.go @@ -48,6 +48,7 @@ func (p path) Child(f reflect.StructField) path { type spec struct { dest path typ reflect.Type + name string // canonical name for the option long string short string multiple bool @@ -280,6 +281,8 @@ func cmdFromStruct(name string, dest path, t reflect.Type) (*command, error) { typ: field.Type, } + spec.name = spec.long + help, exists := field.Tag.Lookup("help") if exists { spec.help = help @@ -308,6 +311,9 @@ func cmdFromStruct(name string, dest path, t reflect.Type) (*command, error) { errs = append(errs, fmt.Sprintf("%s.%s: too many hyphens", t.Name(), field.Name)) case strings.HasPrefix(key, "--"): spec.long = key[2:] + if spec.long != "" { + spec.name = spec.long + } case strings.HasPrefix(key, "-"): if len(key) != 2 { errs = append(errs, fmt.Sprintf("%s.%s: short arguments must be one character only", @@ -364,7 +370,7 @@ func cmdFromStruct(name string, dest path, t reflect.Type) (*command, error) { if hasPlaceholder { spec.placeholder = placeholder } else { - spec.placeholder = strings.ToUpper(spec.long) + spec.placeholder = strings.ToUpper(spec.name) } // Check whether this field is supported. It's good to do this here rather than @@ -617,13 +623,13 @@ func (p *Parser) process(args []string) error { if spec.multiple { err := setSlice(p.val(spec.dest), positionals, true) if err != nil { - return fmt.Errorf("error processing %s: %v", spec.long, err) + return fmt.Errorf("error processing %s: %v", spec.name, err) } positionals = nil } else { err := scalar.ParseValue(p.val(spec.dest), positionals[0]) if err != nil { - return fmt.Errorf("error processing %s: %v", spec.long, err) + return fmt.Errorf("error processing %s: %v", spec.name, err) } positionals = positionals[1:] } @@ -638,8 +644,8 @@ func (p *Parser) process(args []string) error { continue } - name := spec.long - if !spec.positional { + name := spec.name + if spec.long != "" && !spec.positional { name = "--" + spec.long } diff --git a/parse_test.go b/parse_test.go index ad668a9..91d8f54 100644 --- a/parse_test.go +++ b/parse_test.go @@ -231,6 +231,18 @@ func TestPlaceholder(t *testing.T) { assert.NoError(t, err) } +func TestNoLongName(t *testing.T) { + var args struct { + ShortOnly string `arg:"-s,--"` + EnvOnly string `arg:"--,env"` + } + setenv(t, "ENVONLY", "TestVal") + err := parse("-s TestVal2", &args) + assert.NoError(t, err) + assert.Equal(t, "TestVal", args.EnvOnly) + assert.Equal(t, "TestVal2", args.ShortOnly) +} + func TestCaseSensitive(t *testing.T) { var args struct { Lower bool `arg:"-v"` diff --git a/usage.go b/usage.go index a741570..fc8b09a 100644 --- a/usage.go +++ b/usage.go @@ -36,12 +36,15 @@ func (p *Parser) WriteUsage(w io.Writer) { // writeUsageForCommand writes usage information for the given subcommand func (p *Parser) writeUsageForCommand(w io.Writer, cmd *command) { - var positionals, options []*spec + var positionals, longOptions, shortOptions []*spec for _, spec := range cmd.specs { - if spec.positional { + switch { + case spec.positional: positionals = append(positionals, spec) - } else { - options = append(options, spec) + case spec.long != "": + longOptions = append(longOptions, spec) + case spec.short != "": + shortOptions = append(shortOptions, spec) } } @@ -64,7 +67,19 @@ func (p *Parser) writeUsageForCommand(w io.Writer, cmd *command) { } // write the option component of the usage message - for _, spec := range options { + for _, spec := range shortOptions { + // prefix with a space + fmt.Fprint(w, " ") + if !spec.required { + fmt.Fprint(w, "[") + } + fmt.Fprint(w, synopsis(spec, "-"+spec.short)) + if !spec.required { + fmt.Fprint(w, "]") + } + } + + for _, spec := range longOptions { // prefix with a space fmt.Fprint(w, " ") if !spec.required { @@ -215,10 +230,14 @@ func (p *Parser) writeHelpForCommand(w io.Writer, cmd *command) { } func (p *Parser) printOption(w io.Writer, spec *spec) { - left := synopsis(spec, "--"+spec.long) - if spec.short != "" { - left += ", " + synopsis(spec, "-"+spec.short) + text := make([]string, 0, 2) + if spec.long != "" { + text = append(text, synopsis(spec, "--"+spec.long)) } + if spec.short != "" { + text = append(text, synopsis(spec, "-"+spec.short)) + } + left := strings.Join(text, ", ") printTwoCols(w, left, spec.help, spec.defaultVal, spec.env) } diff --git a/usage_test.go b/usage_test.go index 5d379a1..de40ebd 100644 --- a/usage_test.go +++ b/usage_test.go @@ -309,3 +309,22 @@ Global options: p.WriteHelp(&help) assert.Equal(t, expectedHelp, help.String()) } + +func TestUsageWithOptionalLongNames(t *testing.T) { + expectedHelp := `Usage: example [-a PLACEHOLDER] -b SHORTONLY2 + +Options: + -a PLACEHOLDER some help [default: some val] + -b SHORTONLY2 some help2 + --help, -h display this help and exit +` + var args struct { + ShortOnly string `arg:"-a,--" help:"some help" default:"some val" placeholder:"PLACEHOLDER"` + ShortOnly2 string `arg:"-b,--,required" help:"some help2"` + } + p, err := NewParser(Config{Program: "example"}, &args) + assert.NoError(t, err) + var help bytes.Buffer + p.WriteHelp(&help) + assert.Equal(t, expectedHelp, help.String()) +}