Optional long arguments

This commit is contained in:
Andrew Morozko 2020-12-20 02:54:03 +03:00
parent b91c03d2c6
commit faebd3e0f2
4 changed files with 69 additions and 13 deletions

View File

@ -48,6 +48,7 @@ func (p path) Child(f reflect.StructField) path {
type spec struct { type spec struct {
dest path dest path
typ reflect.Type typ reflect.Type
name string // canonical name for the option
long string long string
short string short string
multiple bool multiple bool
@ -280,6 +281,8 @@ func cmdFromStruct(name string, dest path, t reflect.Type) (*command, error) {
typ: field.Type, typ: field.Type,
} }
spec.name = spec.long
help, exists := field.Tag.Lookup("help") help, exists := field.Tag.Lookup("help")
if exists { if exists {
spec.help = help 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)) errs = append(errs, fmt.Sprintf("%s.%s: too many hyphens", t.Name(), field.Name))
case strings.HasPrefix(key, "--"): case strings.HasPrefix(key, "--"):
spec.long = key[2:] spec.long = key[2:]
if spec.long != "" {
spec.name = spec.long
}
case strings.HasPrefix(key, "-"): case strings.HasPrefix(key, "-"):
if len(key) != 2 { if len(key) != 2 {
errs = append(errs, fmt.Sprintf("%s.%s: short arguments must be one character only", 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 { if hasPlaceholder {
spec.placeholder = placeholder spec.placeholder = placeholder
} else { } 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 // 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 { if spec.multiple {
err := setSlice(p.val(spec.dest), positionals, true) err := setSlice(p.val(spec.dest), positionals, true)
if err != nil { 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 positionals = nil
} else { } else {
err := scalar.ParseValue(p.val(spec.dest), positionals[0]) err := scalar.ParseValue(p.val(spec.dest), positionals[0])
if err != nil { 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:] positionals = positionals[1:]
} }
@ -638,8 +644,8 @@ func (p *Parser) process(args []string) error {
continue continue
} }
name := spec.long name := spec.name
if !spec.positional { if spec.long != "" && !spec.positional {
name = "--" + spec.long name = "--" + spec.long
} }

View File

@ -231,6 +231,18 @@ func TestPlaceholder(t *testing.T) {
assert.NoError(t, err) 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) { func TestCaseSensitive(t *testing.T) {
var args struct { var args struct {
Lower bool `arg:"-v"` Lower bool `arg:"-v"`

View File

@ -36,12 +36,15 @@ func (p *Parser) WriteUsage(w io.Writer) {
// writeUsageForCommand writes usage information for the given subcommand // writeUsageForCommand writes usage information for the given subcommand
func (p *Parser) writeUsageForCommand(w io.Writer, cmd *command) { func (p *Parser) writeUsageForCommand(w io.Writer, cmd *command) {
var positionals, options []*spec var positionals, longOptions, shortOptions []*spec
for _, spec := range cmd.specs { for _, spec := range cmd.specs {
if spec.positional { switch {
case spec.positional:
positionals = append(positionals, spec) positionals = append(positionals, spec)
} else { case spec.long != "":
options = append(options, spec) 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 // 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 // prefix with a space
fmt.Fprint(w, " ") fmt.Fprint(w, " ")
if !spec.required { 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) { func (p *Parser) printOption(w io.Writer, spec *spec) {
left := synopsis(spec, "--"+spec.long) text := make([]string, 0, 2)
if spec.short != "" { if spec.long != "" {
left += ", " + synopsis(spec, "-"+spec.short) 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) printTwoCols(w, left, spec.help, spec.defaultVal, spec.env)
} }

View File

@ -309,3 +309,22 @@ Global options:
p.WriteHelp(&help) p.WriteHelp(&help)
assert.Equal(t, expectedHelp, help.String()) 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())
}