Merge pull request #137 from alexflint/optional-long
Optional long names
This commit is contained in:
commit
f4eb7f3a58
10
README.md
10
README.md
|
@ -244,21 +244,23 @@ someprogram 4.3.0
|
|||
|
||||
```go
|
||||
var args struct {
|
||||
Short string `arg:"-s"`
|
||||
Long string `arg:"--custom-long-option"`
|
||||
ShortAndLong string `arg:"-x,--my-option"`
|
||||
Short string `arg:"-s"`
|
||||
Long string `arg:"--custom-long-option"`
|
||||
ShortAndLong string `arg:"-x,--my-option"`
|
||||
OnlyShort string `arg:"-o,--"`
|
||||
}
|
||||
arg.MustParse(&args)
|
||||
```
|
||||
|
||||
```shell
|
||||
$ ./example --help
|
||||
Usage: [--short SHORT] [--custom-long-option CUSTOM-LONG-OPTION] [--my-option MY-OPTION]
|
||||
Usage: example [-o ONLYSHORT] [--short SHORT] [--custom-long-option CUSTOM-LONG-OPTION] [--my-option MY-OPTION]
|
||||
|
||||
Options:
|
||||
--short SHORT, -s SHORT
|
||||
--custom-long-option CUSTOM-LONG-OPTION
|
||||
--my-option MY-OPTION, -x MY-OPTION
|
||||
-o ONLYSHORT
|
||||
--help, -h display this help and exit
|
||||
```
|
||||
|
||||
|
|
26
parse.go
26
parse.go
|
@ -47,9 +47,9 @@ func (p path) Child(f reflect.StructField) path {
|
|||
// spec represents a command line option
|
||||
type spec struct {
|
||||
dest path
|
||||
typ reflect.Type
|
||||
long string
|
||||
short string
|
||||
field reflect.StructField // the struct field from which this option was created
|
||||
long string // the --long form for this option, or empty if none
|
||||
short string // the -s short form for this option, or empty if none
|
||||
multiple bool
|
||||
required bool
|
||||
positional bool
|
||||
|
@ -275,9 +275,9 @@ func cmdFromStruct(name string, dest path, t reflect.Type) (*command, error) {
|
|||
// duplicate the entire path to avoid slice overwrites
|
||||
subdest := dest.Child(field)
|
||||
spec := spec{
|
||||
dest: subdest,
|
||||
long: strings.ToLower(field.Name),
|
||||
typ: field.Type,
|
||||
dest: subdest,
|
||||
field: field,
|
||||
long: strings.ToLower(field.Name),
|
||||
}
|
||||
|
||||
help, exists := field.Tag.Lookup("help")
|
||||
|
@ -363,8 +363,10 @@ func cmdFromStruct(name string, dest path, t reflect.Type) (*command, error) {
|
|||
placeholder, hasPlaceholder := field.Tag.Lookup("placeholder")
|
||||
if hasPlaceholder {
|
||||
spec.placeholder = placeholder
|
||||
} else {
|
||||
} else if spec.long != "" {
|
||||
spec.placeholder = strings.ToUpper(spec.long)
|
||||
} else {
|
||||
spec.placeholder = strings.ToUpper(spec.field.Name)
|
||||
}
|
||||
|
||||
// Check whether this field is supported. It's good to do this here rather than
|
||||
|
@ -592,7 +594,7 @@ func (p *Parser) process(args []string) error {
|
|||
if i+1 == len(args) {
|
||||
return fmt.Errorf("missing value for %s", arg)
|
||||
}
|
||||
if !nextIsNumeric(spec.typ, args[i+1]) && isFlag(args[i+1]) {
|
||||
if !nextIsNumeric(spec.field.Type, args[i+1]) && isFlag(args[i+1]) {
|
||||
return fmt.Errorf("missing value for %s", arg)
|
||||
}
|
||||
value = args[i+1]
|
||||
|
@ -617,13 +619,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.field.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.field.Name, err)
|
||||
}
|
||||
positionals = positionals[1:]
|
||||
}
|
||||
|
@ -638,8 +640,8 @@ func (p *Parser) process(args []string) error {
|
|||
continue
|
||||
}
|
||||
|
||||
name := spec.long
|
||||
if !spec.positional {
|
||||
name := strings.ToLower(spec.field.Name)
|
||||
if spec.long != "" && !spec.positional {
|
||||
name = "--" + spec.long
|
||||
}
|
||||
|
||||
|
|
|
@ -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"`
|
||||
|
|
58
usage.go
58
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 {
|
||||
|
@ -144,12 +159,15 @@ func (p *Parser) WriteHelp(w io.Writer) {
|
|||
|
||||
// writeHelp writes the usage string for the given subcommand
|
||||
func (p *Parser) writeHelpForCommand(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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -166,10 +184,13 @@ func (p *Parser) writeHelpForCommand(w io.Writer, cmd *command) {
|
|||
}
|
||||
}
|
||||
|
||||
// write the list of options
|
||||
if len(options) > 0 || cmd.parent == nil {
|
||||
// write the list of options with the short-only ones first to match the usage string
|
||||
if len(shortOptions)+len(longOptions) > 0 || cmd.parent == nil {
|
||||
fmt.Fprint(w, "\nOptions:\n")
|
||||
for _, spec := range options {
|
||||
for _, spec := range shortOptions {
|
||||
p.printOption(w, spec)
|
||||
}
|
||||
for _, spec := range longOptions {
|
||||
p.printOption(w, spec)
|
||||
}
|
||||
}
|
||||
|
@ -215,11 +236,16 @@ 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)
|
||||
ways := make([]string, 0, 2)
|
||||
if spec.long != "" {
|
||||
ways = append(ways, synopsis(spec, "--"+spec.long))
|
||||
}
|
||||
if spec.short != "" {
|
||||
ways = append(ways, synopsis(spec, "-"+spec.short))
|
||||
}
|
||||
if len(ways) > 0 {
|
||||
printTwoCols(w, strings.Join(ways, ", "), spec.help, spec.defaultVal, spec.env)
|
||||
}
|
||||
printTwoCols(w, left, spec.help, spec.defaultVal, spec.env)
|
||||
}
|
||||
|
||||
func synopsis(spec *spec, form string) string {
|
||||
|
|
|
@ -309,3 +309,61 @@ Global options:
|
|||
p.WriteHelp(&help)
|
||||
assert.Equal(t, expectedHelp, help.String())
|
||||
}
|
||||
|
||||
func TestUsageWithoutLongNames(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())
|
||||
}
|
||||
|
||||
func TestUsageWithShortFirst(t *testing.T) {
|
||||
expectedHelp := `Usage: example [-c CAT] [--dog DOG]
|
||||
|
||||
Options:
|
||||
-c CAT
|
||||
--dog DOG
|
||||
--help, -h display this help and exit
|
||||
`
|
||||
var args struct {
|
||||
Dog string
|
||||
Cat string `arg:"-c,--"`
|
||||
}
|
||||
p, err := NewParser(Config{Program: "example"}, &args)
|
||||
assert.NoError(t, err)
|
||||
var help bytes.Buffer
|
||||
p.WriteHelp(&help)
|
||||
assert.Equal(t, expectedHelp, help.String())
|
||||
}
|
||||
|
||||
func TestUsageWithEnvOptions(t *testing.T) {
|
||||
expectedHelp := `Usage: example [-s SHORT]
|
||||
|
||||
Options:
|
||||
-s SHORT [env: SHORT]
|
||||
--help, -h display this help and exit
|
||||
`
|
||||
var args struct {
|
||||
Short string `arg:"--,-s,env"`
|
||||
EnvOnly string `arg:"--,env"`
|
||||
EnvOnlyOverriden string `arg:"--,env:CUSTOM"`
|
||||
}
|
||||
|
||||
p, err := NewParser(Config{Program: "example"}, &args)
|
||||
assert.NoError(t, err)
|
||||
var help bytes.Buffer
|
||||
p.WriteHelp(&help)
|
||||
assert.Equal(t, expectedHelp, help.String())
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue