Merge pull request #163 from pierrec/master
Option new methods
diff --git a/.travis.yml b/.travis.yml
index 3165f00..c47256e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,12 +1,14 @@
language: go
+go:
+ - 1.6
+
install:
# go-flags
- go get -d -v ./...
- go build -v ./...
# linting
- - go get golang.org/x/tools/cmd/vet
- go get github.com/golang/lint
- go install github.com/golang/lint/golint
diff --git a/arg.go b/arg.go
index d160644..8ec6204 100644
--- a/arg.go
+++ b/arg.go
@@ -12,9 +12,12 @@
// A description of the positional argument (used in the help)
Description string
- // Whether a positional argument is required
+ // The minimal number of required positional arguments
Required int
+ // The maximum number of required positional arguments
+ RequiredMaximum int
+
value reflect.Value
tag multiTag
}
diff --git a/arg_test.go b/arg_test.go
index 117e90e..c7c0a61 100644
--- a/arg_test.go
+++ b/arg_test.go
@@ -131,3 +131,33 @@
assertString(t, opts.Positional.Rest[1], "rest2")
assertString(t, opts.Positional.Rest[2], "rest3")
}
+
+func TestPositionalRequiredRestRangeFail(t *testing.T) {
+ var opts = struct {
+ Value bool `short:"v"`
+
+ Positional struct {
+ Rest []string `required:"1-2"`
+ } `positional-args:"yes"`
+ }{}
+
+ p := NewParser(&opts, None)
+ _, err := p.ParseArgs([]string{"rest1", "rest2", "rest3"})
+
+ assertError(t, err, ErrRequired, "the required argument `Rest (at most 2 arguments, but got 3)` was not provided")
+}
+
+func TestPositionalRequiredRestRangeEmptyFail(t *testing.T) {
+ var opts = struct {
+ Value bool `short:"v"`
+
+ Positional struct {
+ Rest []string `required:"0-0"`
+ } `positional-args:"yes"`
+ }{}
+
+ p := NewParser(&opts, None)
+ _, err := p.ParseArgs([]string{"some", "thing"})
+
+ assertError(t, err, ErrRequired, "the required argument `Rest (zero arguments)` was not provided")
+}
diff --git a/command.go b/command.go
index a30f560..2662843 100644
--- a/command.go
+++ b/command.go
@@ -112,9 +112,9 @@
return nil
}
-// Find an option that is part of the command, or any of its
-// parent commands, by matching its long name
-// (including the option namespace).
+// FindOptionByLongName finds an option that is part of the command, or any of
+// its parent commands, by matching its long name (including the option
+// namespace).
func (c *Command) FindOptionByLongName(longName string) (option *Option) {
for option == nil && c != nil {
option = c.Group.FindOptionByLongName(longName)
@@ -125,9 +125,9 @@
return option
}
-// Find an option that is part of the command, or any of its
-// parent commands, by matching its long name
-// (including the option namespace).
+// FindOptionByShortName finds an option that is part of the command, or any of
+// its parent commands, by matching its long name (including the option
+// namespace).
func (c *Command) FindOptionByShortName(shortName rune) (option *Option) {
for option == nil && c != nil {
option = c.Group.FindOptionByShortName(shortName)
@@ -181,22 +181,36 @@
name = field.Name
}
- var required int
+ required := -1
+ requiredMaximum := -1
sreq := m.Get("required")
if sreq != "" {
required = 1
- if preq, err := strconv.ParseInt(sreq, 10, 32); err == nil {
- required = int(preq)
+ rng := strings.SplitN(sreq, "-", 2)
+
+ if len(rng) > 1 {
+ if preq, err := strconv.ParseInt(rng[0], 10, 32); err == nil {
+ required = int(preq)
+ }
+
+ if preq, err := strconv.ParseInt(rng[1], 10, 32); err == nil {
+ requiredMaximum = int(preq)
+ }
+ } else {
+ if preq, err := strconv.ParseInt(sreq, 10, 32); err == nil {
+ required = int(preq)
+ }
}
}
arg := &Arg{
- Name: name,
- Description: m.Get("description"),
- Required: required,
+ Name: name,
+ Description: m.Get("description"),
+ Required: required,
+ RequiredMaximum: requiredMaximum,
value: realval.Field(i),
tag: m,
diff --git a/command_test.go b/command_test.go
index 72d397d..dc04b66 100644
--- a/command_test.go
+++ b/command_test.go
@@ -430,17 +430,31 @@
func TestDefaultOnCommand(t *testing.T) {
var opts = struct {
Command struct {
- G bool `short:"g" default:"true"`
+ G string `short:"g" default:"value"`
} `command:"cmd"`
}{}
assertParseSuccess(t, &opts, "cmd")
- if !opts.Command.G {
- t.Errorf("Expected G to be true")
+ if opts.Command.G != "value" {
+ t.Errorf("Expected G to be \"value\"")
}
}
+func TestAfterNonCommand(t *testing.T) {
+ var opts = struct {
+ Value bool `short:"v"`
+
+ Cmd1 struct {
+ } `command:"remove"`
+
+ Cmd2 struct {
+ } `command:"add"`
+ }{}
+
+ assertParseFail(t, ErrUnknownCommand, "Unknown command `nocmd'. Please specify one command of: add or remove", &opts, "nocmd", "remove")
+}
+
func TestSubcommandsOptional(t *testing.T) {
var opts = struct {
Value bool `short:"v"`
@@ -467,17 +481,41 @@
}
}
+func TestSubcommandsOptionalAfterNonCommand(t *testing.T) {
+ var opts = struct {
+ Value bool `short:"v"`
+
+ Cmd1 struct {
+ } `command:"remove"`
+
+ Cmd2 struct {
+ } `command:"add"`
+ }{}
+
+ p := NewParser(&opts, None)
+ p.SubcommandsOptional = true
+
+ retargs, err := p.ParseArgs([]string{"nocmd", "remove"})
+
+ if err != nil {
+ t.Fatalf("Unexpected error: %v", err)
+ return
+ }
+
+ assertStringArray(t, retargs, []string{"nocmd", "remove"})
+}
+
func TestCommandAlias(t *testing.T) {
var opts = struct {
Command struct {
- G bool `short:"g" default:"true"`
+ G string `short:"g" default:"value"`
} `command:"cmd" alias:"cm"`
}{}
assertParseSuccess(t, &opts, "cm")
- if !opts.Command.G {
- t.Errorf("Expected G to be true")
+ if opts.Command.G != "value" {
+ t.Errorf("Expected G to be \"value\"")
}
}
diff --git a/completion.go b/completion.go
index 894f1d6..708fa9e 100644
--- a/completion.go
+++ b/completion.go
@@ -77,7 +77,7 @@
n := make([]Completion, 0, len(names))
for k, opt := range names {
- if strings.HasPrefix(k, match) {
+ if strings.HasPrefix(k, match) && !opt.Hidden {
n = append(n, Completion{
Item: prefix + k,
Description: opt.Description,
diff --git a/completion_test.go b/completion_test.go
index f440fd7..72ee158 100644
--- a/completion_test.go
+++ b/completion_test.go
@@ -40,6 +40,7 @@
Debug bool `short:"d" long:"debug" description:"Enable debug"`
Version bool `long:"version" description:"Show version"`
Required bool `long:"required" required:"true" description:"This is required"`
+ Hidden bool `long:"hidden" hidden:"true" description:"This is hidden"`
AddCommand struct {
Positional struct {
diff --git a/error.go b/error.go
index 2f27aee..05528d8 100644
--- a/error.go
+++ b/error.go
@@ -55,6 +55,9 @@
// ErrInvalidChoice indicates an invalid option value which only allows
// a certain number of choices.
ErrInvalidChoice
+
+ // ErrInvalidTag indicates an invalid tag or invalid use of an existing tag
+ ErrInvalidTag
)
func (e ErrorType) String() string {
@@ -87,6 +90,8 @@
return "unknown command"
case ErrInvalidChoice:
return "invalid choice"
+ case ErrInvalidTag:
+ return "invalid tag"
}
return "unrecognized error type"
diff --git a/example_test.go b/example_test.go
index f7be2bb..4321ed8 100644
--- a/example_test.go
+++ b/example_test.go
@@ -41,7 +41,7 @@
// Example of positional arguments
Args struct {
- Id string
+ ID string
Num int
Rest []string
} `positional-args:"yes" required:"yes"`
@@ -92,7 +92,7 @@
fmt.Printf("PtrSlice: [%v %v]\n", *opts.PtrSlice[0], *opts.PtrSlice[1])
fmt.Printf("IntMap: [a:%v b:%v]\n", opts.IntMap["a"], opts.IntMap["b"])
fmt.Printf("Filename: %v\n", opts.Filename)
- fmt.Printf("Args.Id: %s\n", opts.Args.Id)
+ fmt.Printf("Args.ID: %s\n", opts.Args.ID)
fmt.Printf("Args.Num: %d\n", opts.Args.Num)
fmt.Printf("Args.Rest: %v\n", opts.Args.Rest)
@@ -104,7 +104,7 @@
// PtrSlice: [hello world]
// IntMap: [a:1 b:5]
// Filename: hello.go
- // Args.Id: id
+ // Args.ID: id
// Args.Num: 10
// Args.Rest: [remaining1 remaining2]
}
diff --git a/group.go b/group.go
index debb8de..b1ef6ed 100644
--- a/group.go
+++ b/group.go
@@ -110,16 +110,16 @@
return option
}
-// Find an option that is part of the group, or any of its subgroups,
-// by matching its long name (including the option namespace).
+// FindOptionByLongName finds an option that is part of the group, or any of its
+// subgroups, by matching its long name (including the option namespace).
func (g *Group) FindOptionByLongName(longName string) *Option {
return g.findOption(func(option *Option) bool {
return option.LongNameWithNamespace() == longName
})
}
-// Find an option that is part of the group, or any of its subgroups,
-// by matching its short name.
+// FindOptionByShortName finds an option that is part of the group, or any of
+// its subgroups, by matching its short name.
func (g *Group) FindOptionByShortName(shortName rune) *Option {
return g.findOption(func(option *Option) bool {
return option.ShortName == shortName
@@ -276,6 +276,12 @@
tag: mtag,
}
+ if option.isBool() && option.Default != nil {
+ return newErrorf(ErrInvalidTag,
+ "boolean flag `%s' may not have default values, they always default to `false' and can only be turned on",
+ option.shortAndLongName())
+ }
+
g.options = append(g.options, option)
}
diff --git a/help.go b/help.go
index c0b808d..1b0c2c6 100644
--- a/help.go
+++ b/help.go
@@ -412,22 +412,41 @@
}
})
- if len(c.args) > 0 {
+ var args []*Arg
+ for _, arg := range c.args {
+ if arg.Description != "" {
+ args = append(args, arg)
+ }
+ }
+
+ if len(args) > 0 {
if c == p.Command {
fmt.Fprintf(wr, "\nArguments:\n")
} else {
fmt.Fprintf(wr, "\n[%s command arguments]\n", c.Name)
}
- maxlen := aligninfo.descriptionStart()
+ descStart := aligninfo.descriptionStart() + paddingBeforeOption
- for _, arg := range c.args {
- prefix := strings.Repeat(" ", paddingBeforeOption)
- fmt.Fprintf(wr, "%s%s", prefix, arg.Name)
+ for _, arg := range args {
+ argPrefix := strings.Repeat(" ", paddingBeforeOption)
+ argPrefix += arg.Name
if len(arg.Description) > 0 {
- align := strings.Repeat(" ", maxlen-len(arg.Name)-1)
- fmt.Fprintf(wr, ":%s%s", align, arg.Description)
+ argPrefix += ":"
+ wr.WriteString(argPrefix)
+
+ // Space between "arg:" and the description start
+ descPadding := strings.Repeat(" ", descStart-len(argPrefix))
+ // How much space the description gets before wrapping
+ descWidth := aligninfo.terminalColumns - 1 - descStart
+ // Whitespace to which we can indent new description lines
+ descPrefix := strings.Repeat(" ", descStart)
+
+ wr.WriteString(descPadding)
+ wr.WriteString(wrapText(arg.Description, descWidth, descPrefix))
+ } else {
+ wr.WriteString(argPrefix)
}
fmt.Fprintln(wr)
diff --git a/help_test.go b/help_test.go
index 33d21bf..1248ebe 100644
--- a/help_test.go
+++ b/help_test.go
@@ -53,8 +53,9 @@
} `command:"hidden-command" description:"A hidden command" hidden:"yes"`
Args struct {
- Filename string `positional-arg-name:"filename" description:"A filename"`
- Number int `positional-arg-name:"num" description:"A number"`
+ Filename string `positional-arg-name:"filename" description:"A filename with a long description to trigger line wrapping"`
+ Number int `positional-arg-name:"num" description:"A number"`
+ HiddenInHelp float32 `positional-arg-name:"hidden-in-help" required:"yes"`
} `positional-args:"yes"`
}
@@ -84,7 +85,8 @@
if runtime.GOOS == "windows" {
expected = `Usage:
- TestHelp [OPTIONS] [filename] [num] <command>
+ TestHelp [OPTIONS] [filename] [num] [hidden-in-help] <command>
+
Application Options:
/v, /verbose Show verbose debug information
@@ -129,7 +131,7 @@
`
} else {
expected = `Usage:
- TestHelp [OPTIONS] [filename] [num] <command>
+ TestHelp [OPTIONS] [filename] [num] [hidden-in-help] <command>
Application Options:
-v, --verbose Show verbose debug information
@@ -165,7 +167,8 @@
-h, --help Show this help message
Arguments:
- filename: A filename
+ filename: A filename with a long description
+ to trigger line wrapping
num: A number
Available commands:
@@ -213,6 +216,8 @@
.SH DESCRIPTION
This is a somewhat \fBlonger\fP description of what this does
.SH OPTIONS
+.SS Application Options
+The application options
.TP
\fB\fB\-v\fR, \fB\-\-verbose\fR\fP
Show verbose debug information
@@ -245,15 +250,18 @@
.TP
\fB\fB\-\-opt-with-choices\fR \fIchoice\fR\fP
Option with choices
+.SS Other Options
.TP
\fB\fB\-s\fR <default: \fI"some", "value"\fR>\fP
A slice of strings
.TP
\fB\fB\-\-intmap\fR <default: \fI"a:1"\fR>\fP
A map from string to int
+.SS Subgroup
.TP
\fB\fB\-\-sip.opt\fR\fP
This is a subgroup option
+.SS Subsubgroup
.TP
\fB\fB\-\-sip.sap.opt\fR\fP
This is a subsubgroup option
diff --git a/ini.go b/ini.go
index a6d6c27..16e27c0 100644
--- a/ini.go
+++ b/ini.go
@@ -145,7 +145,7 @@
return i.parse(ini)
}
-// WriteFile writes the flags as ini format into a file. See WriteIni
+// WriteFile writes the flags as ini format into a file. See Write
// for more information. The returned error occurs when the specified file
// could not be opened for writing.
func (i *IniParser) WriteFile(filename string, options IniOptions) error {
diff --git a/ini_test.go b/ini_test.go
index a033db9..1d25d45 100644
--- a/ini_test.go
+++ b/ini_test.go
@@ -21,7 +21,7 @@
expected string
}{
{
- []string{"-vv", "--intmap=a:2", "--intmap", "b:3", "filename", "0", "command"},
+ []string{"-vv", "--intmap=a:2", "--intmap", "b:3", "filename", "0", "3.14", "command"},
IniDefault,
`[Application Options]
; Show verbose debug information
@@ -42,7 +42,7 @@
`,
},
{
- []string{"-vv", "--intmap=a:2", "--intmap", "b:3", "filename", "0", "command"},
+ []string{"-vv", "--intmap=a:2", "--intmap", "b:3", "filename", "0", "3.14", "command"},
IniDefault | IniIncludeDefaults,
`[Application Options]
; Show verbose debug information
@@ -104,7 +104,7 @@
`,
},
{
- []string{"filename", "0", "command"},
+ []string{"filename", "0", "3.14", "command"},
IniDefault | IniIncludeDefaults | IniCommentDefaults,
`[Application Options]
; Show verbose debug information
@@ -164,7 +164,7 @@
`,
},
{
- []string{"--default=New value", "--default-array=New value", "--default-map=new:value", "filename", "0", "command"},
+ []string{"--default=New value", "--default-array=New value", "--default-map=new:value", "filename", "0", "3.14", "command"},
IniDefault | IniIncludeDefaults | IniCommentDefaults,
`[Application Options]
; Show verbose debug information
diff --git a/man.go b/man.go
index 8e4a8b7..0cb114e 100644
--- a/man.go
+++ b/man.go
@@ -38,10 +38,21 @@
func writeManPageOptions(wr io.Writer, grp *Group) {
grp.eachGroup(func(group *Group) {
- if group.Hidden {
+ if group.Hidden || len(group.options) == 0 {
return
}
+ // If the parent (grp) has any subgroups, display their descriptions as
+ // subsection headers similar to the output of --help.
+ if group.ShortDescription != "" && len(grp.groups) > 0 {
+ fmt.Fprintf(wr, ".SS %s\n", group.ShortDescription)
+
+ if group.LongDescription != "" {
+ formatForMan(wr, group.LongDescription)
+ fmt.Fprintln(wr, "")
+ }
+ }
+
for _, opt := range group.options {
if !opt.canCli() || opt.Hidden {
continue
diff --git a/marshal_test.go b/marshal_test.go
index 59c9cce..4cfe865 100644
--- a/marshal_test.go
+++ b/marshal_test.go
@@ -5,13 +5,13 @@
"testing"
)
-type marshalled bool
+type marshalled string
func (m *marshalled) UnmarshalFlag(value string) error {
if value == "yes" {
- *m = true
+ *m = "true"
} else if value == "no" {
- *m = false
+ *m = "false"
} else {
return fmt.Errorf("`%s' is not a valid value, please specify `yes' or `no'", value)
}
@@ -20,7 +20,7 @@
}
func (m marshalled) MarshalFlag() (string, error) {
- if m {
+ if m == "true" {
return "yes", nil
}
@@ -42,8 +42,8 @@
assertStringArray(t, ret, []string{})
- if !opts.Value {
- t.Errorf("Expected Value to be true")
+ if opts.Value != "true" {
+ t.Errorf("Expected Value to be \"true\"")
}
}
@@ -56,8 +56,8 @@
assertStringArray(t, ret, []string{})
- if !opts.Value {
- t.Errorf("Expected Value to be true")
+ if opts.Value != "true" {
+ t.Errorf("Expected Value to be \"true\"")
}
}
@@ -70,8 +70,8 @@
assertStringArray(t, ret, []string{})
- if !opts.Value {
- t.Errorf("Expected Value to be true")
+ if opts.Value != "true" {
+ t.Errorf("Expected Value to be \"true\"")
}
}
@@ -83,6 +83,28 @@
assertParseFail(t, ErrMarshal, fmt.Sprintf("invalid argument for flag `%cv' (expected flags.marshalled): `invalid' is not a valid value, please specify `yes' or `no'", defaultShortOptDelimiter), &opts, "-vinvalid")
}
+func TestUnmarshalPositionalError(t *testing.T) {
+ var opts = struct {
+ Args struct {
+ Value marshalled
+ } `positional-args:"yes"`
+ }{}
+
+ parser := NewParser(&opts, Default&^PrintErrors)
+ _, err := parser.ParseArgs([]string{"invalid"})
+
+ msg := "`invalid' is not a valid value, please specify `yes' or `no'"
+
+ if err == nil {
+ assertFatalf(t, "Expected error: %s", msg)
+ return
+ }
+
+ if err.Error() != msg {
+ assertErrorf(t, "Expected error message %#v, but got %#v", msg, err.Error())
+ }
+}
+
func TestMarshalError(t *testing.T) {
var opts = struct {
Value marshalledError `short:"v"`
diff --git a/option.go b/option.go
index ad28e7f..3635df3 100644
--- a/option.go
+++ b/option.go
@@ -1,6 +1,7 @@
package flags
import (
+ "bytes"
"fmt"
"reflect"
"strings"
@@ -416,3 +417,22 @@
option.defaultLiteral = def
}
+
+func (option *Option) shortAndLongName() string {
+ ret := &bytes.Buffer{}
+
+ if option.ShortName != 0 {
+ ret.WriteRune(defaultShortOptDelimiter)
+ ret.WriteRune(option.ShortName)
+ }
+
+ if len(option.LongName) != 0 {
+ if option.ShortName != 0 {
+ ret.WriteRune('/')
+ }
+
+ ret.WriteString(option.LongName)
+ }
+
+ return ret.String()
+}
diff --git a/parser.go b/parser.go
index 7064553..6477daa 100644
--- a/parser.go
+++ b/parser.go
@@ -42,6 +42,16 @@
// You can override this default behavior by specifying a custom CompletionHandler.
CompletionHandler func(items []Completion)
+ // CommandHandler is a function that gets called to handle execution of a
+ // command. By default, the command will simply be executed. This can be
+ // overridden to perform certain actions (such as applying global flags)
+ // just before the command is executed. Note that if you override the
+ // handler it is your responsibility to call the command.Execute function.
+ //
+ // The command passed into CommandHandler may be nil in case there is no
+ // command to be executed when parsing has finished.
+ CommandHandler func(command Commander, args []string) error
+
internalError error
}
@@ -299,7 +309,13 @@
} else if len(s.command.commands) != 0 && !s.command.SubcommandsOptional {
reterr = s.estimateCommand()
} else if cmd, ok := s.command.data.(Commander); ok {
- reterr = cmd.Execute(s.retargs)
+ if p.CommandHandler != nil {
+ reterr = p.CommandHandler(cmd, s.retargs)
+ } else {
+ reterr = cmd.Execute(s.retargs)
+ }
+ } else if p.CommandHandler != nil {
+ reterr = p.CommandHandler(nil, s.retargs)
}
if reterr != nil {
@@ -362,7 +378,7 @@
var reqnames []string
for _, arg := range p.positional {
- argRequired := (!arg.isRemaining() && p.command.ArgsRequired) || arg.Required != 0
+ argRequired := (!arg.isRemaining() && p.command.ArgsRequired) || arg.Required != -1 || arg.RequiredMaximum != -1
if !argRequired {
continue
@@ -379,6 +395,20 @@
}
reqnames = append(reqnames, "`"+arg.Name+" (at least "+fmt.Sprintf("%d", arg.Required)+" "+arguments+")`")
+ } else if arg.RequiredMaximum != -1 && arg.value.Len() > arg.RequiredMaximum {
+ if arg.RequiredMaximum == 0 {
+ reqnames = append(reqnames, "`"+arg.Name+" (zero arguments)`")
+ } else {
+ var arguments string
+
+ if arg.RequiredMaximum > 1 {
+ arguments = "arguments, but got " + fmt.Sprintf("%d", arg.value.Len())
+ } else {
+ arguments = "argument"
+ }
+
+ reqnames = append(reqnames, "`"+arg.Name+" (at most "+fmt.Sprintf("%d", arg.RequiredMaximum)+" "+arguments+")`")
+ }
}
} else {
reqnames = append(reqnames, "`"+arg.Name+"`")
@@ -586,6 +616,7 @@
arg := p.positional[0]
if err := convert(args[0], arg.value, arg.tag); err != nil {
+ p.err = err
return err
}
@@ -605,10 +636,19 @@
return s.addArgs(s.arg)
}
- if cmd := s.lookup.commands[s.arg]; cmd != nil {
- s.command.Active = cmd
- cmd.fillParseState(s)
- } else if (p.Options & PassAfterNonOption) != None {
+ if len(s.command.commands) > 0 && len(s.retargs) == 0 {
+ if cmd := s.lookup.commands[s.arg]; cmd != nil {
+ s.command.Active = cmd
+ cmd.fillParseState(s)
+
+ return nil
+ } else if !s.command.SubcommandsOptional {
+ s.addArgs(s.arg)
+ return newErrorf(ErrUnknownCommand, "Unknown command `%s'", s.arg)
+ }
+ }
+
+ if (p.Options & PassAfterNonOption) != None {
// If PassAfterNonOption is set then all remaining arguments
// are considered positional
if err := s.addArgs(s.arg); err != nil {
diff --git a/parser_test.go b/parser_test.go
index b57dbee..9f527b7 100644
--- a/parser_test.go
+++ b/parser_test.go
@@ -4,6 +4,7 @@
"fmt"
"os"
"reflect"
+ "runtime"
"strconv"
"strings"
"testing"
@@ -17,7 +18,7 @@
Float64 float64 `long:"f"`
Float64Default float64 `long:"fd" default:"-3.14"`
- NumericFlag bool `short:"3" default:"false"`
+ NumericFlag bool `short:"3"`
String string `long:"str"`
StringDefault string `long:"strd" default:"abc"`
@@ -132,6 +133,18 @@
}
}
+func TestNoDefaultsForBools(t *testing.T) {
+ var opts struct {
+ DefaultBool bool `short:"d" default:"true"`
+ }
+
+ if runtime.GOOS == "windows" {
+ assertParseFail(t, ErrInvalidTag, "boolean flag `/d' may not have default values, they always default to `false' and can only be turned on", &opts)
+ } else {
+ assertParseFail(t, ErrInvalidTag, "boolean flag `-d' may not have default values, they always default to `false' and can only be turned on", &opts)
+ }
+}
+
func TestUnquoting(t *testing.T) {
var tests = []struct {
arg string
@@ -202,28 +215,33 @@
}
}
-// envRestorer keeps a copy of a set of env variables and can restore the env from them
-type envRestorer struct {
+// EnvRestorer keeps a copy of a set of env variables and can restore the env from them
+type EnvRestorer struct {
env map[string]string
}
-func (r *envRestorer) Restore() {
+func (r *EnvRestorer) Restore() {
os.Clearenv()
+
for k, v := range r.env {
os.Setenv(k, v)
}
}
// EnvSnapshot returns a snapshot of the currently set env variables
-func EnvSnapshot() *envRestorer {
- r := envRestorer{make(map[string]string)}
+func EnvSnapshot() *EnvRestorer {
+ r := EnvRestorer{make(map[string]string)}
+
for _, kv := range os.Environ() {
parts := strings.SplitN(kv, "=", 2)
+
if len(parts) != 2 {
panic("got a weird env variable: " + kv)
}
+
r.env[parts[0]] = parts[1]
}
+
return &r
}
@@ -481,7 +499,93 @@
}
assertParseSuccess(t, &opts, "-v")
+
if !opts.V {
t.Errorf("Expected V to be true")
}
}
+
+type command struct {
+}
+
+func (c *command) Execute(args []string) error {
+ return nil
+}
+
+func TestCommandHandlerNoCommand(t *testing.T) {
+ var opts = struct {
+ Value bool `short:"v"`
+ }{}
+
+ parser := NewParser(&opts, Default&^PrintErrors)
+
+ var executedCommand Commander
+ var executedArgs []string
+
+ executed := false
+
+ parser.CommandHandler = func(command Commander, args []string) error {
+ executed = true
+
+ executedCommand = command
+ executedArgs = args
+
+ return nil
+ }
+
+ _, err := parser.ParseArgs([]string{"arg1", "arg2"})
+
+ if err != nil {
+ t.Fatalf("Unexpected parse error: %s", err)
+ }
+
+ if !executed {
+ t.Errorf("Expected command handler to be executed")
+ }
+
+ if executedCommand != nil {
+ t.Errorf("Did not exect an executed command")
+ }
+
+ assertStringArray(t, executedArgs, []string{"arg1", "arg2"})
+}
+
+func TestCommandHandler(t *testing.T) {
+ var opts = struct {
+ Value bool `short:"v"`
+
+ Command command `command:"cmd"`
+ }{}
+
+ parser := NewParser(&opts, Default&^PrintErrors)
+
+ var executedCommand Commander
+ var executedArgs []string
+
+ executed := false
+
+ parser.CommandHandler = func(command Commander, args []string) error {
+ executed = true
+
+ executedCommand = command
+ executedArgs = args
+
+ return nil
+ }
+
+ _, err := parser.ParseArgs([]string{"cmd", "arg1", "arg2"})
+
+ if err != nil {
+ t.Fatalf("Unexpected parse error: %s", err)
+ }
+
+ if !executed {
+ t.Errorf("Expected command handler to be executed")
+ }
+
+ if executedCommand == nil {
+ t.Errorf("Expected command handler to be executed")
+ }
+
+ assertStringArray(t, executedArgs, []string{"arg1", "arg2"})
+}