summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md10
-rw-r--r--parse.go26
-rw-r--r--parse_test.go12
-rw-r--r--usage.go56
-rw-r--r--usage_test.go58
5 files changed, 131 insertions, 31 deletions
diff --git a/README.md b/README.md
index a9555b9..da69469 100644
--- a/README.md
+++ b/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
```
diff --git a/parse.go b/parse.go
index 0c65397..b7d159d 100644
--- a/parse.go
+++ b/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
}
diff --git a/parse_test.go b/parse_test.go
index a0334c7..ce3068e 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 776ac03..cbbb021 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 {
@@ -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)
+ ways := make([]string, 0, 2)
+ if spec.long != "" {
+ ways = append(ways, synopsis(spec, "--"+spec.long))
+ }
if spec.short != "" {
- left += ", " + synopsis(spec, "-"+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 {
diff --git a/usage_test.go b/usage_test.go
index 5d379a1..6dee402 100644
--- a/usage_test.go
+++ b/usage_test.go
@@ -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())
+}