From 64fe9ec048c057ee55fe67cc73ffefe0936ff3ac Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Thu, 15 Sep 2016 22:29:49 +0200 Subject: [PATCH] Update github.com/jessevdk/go-flags --- src/cmds/restic/cmd_check.go | 4 +- src/cmds/restic/cmd_mount.go | 2 +- src/cmds/restic/global.go | 4 +- vendor/manifest | 4 +- .../github.com/jessevdk/go-flags/README.md | 148 +++--- .../src/github.com/jessevdk/go-flags/arg.go | 6 + .../github.com/jessevdk/go-flags/arg_test.go | 110 +++++ .../github.com/jessevdk/go-flags/command.go | 349 +++++++++++++ .../jessevdk/go-flags/command_private.go | 271 ---------- .../jessevdk/go-flags/command_test.go | 192 +++++++- .../jessevdk/go-flags/completion.go | 16 +- .../jessevdk/go-flags/completion_test.go | 6 + .../github.com/jessevdk/go-flags/convert.go | 36 -- .../jessevdk/go-flags/convert_test.go | 16 - .../src/github.com/jessevdk/go-flags/error.go | 11 + .../jessevdk/go-flags/example_test.go | 6 +- .../src/github.com/jessevdk/go-flags/flags.go | 18 +- .../src/github.com/jessevdk/go-flags/group.go | 294 +++++++++++ .../jessevdk/go-flags/group_private.go | 254 ---------- .../jessevdk/go-flags/group_test.go | 68 +++ .../src/github.com/jessevdk/go-flags/help.go | 153 ++++-- .../github.com/jessevdk/go-flags/help_test.go | 252 ++++++++-- .../src/github.com/jessevdk/go-flags/ini.go | 463 +++++++++++++++++- .../jessevdk/go-flags/ini_private.go | 452 ----------------- .../github.com/jessevdk/go-flags/ini_test.go | 262 +++++++++- .../src/github.com/jessevdk/go-flags/man.go | 27 +- .../jessevdk/go-flags/marshal_test.go | 42 +- .../github.com/jessevdk/go-flags/option.go | 298 ++++++++++- .../jessevdk/go-flags/option_private.go | 182 ------- .../jessevdk/go-flags/optstyle_other.go | 2 +- .../jessevdk/go-flags/optstyle_windows.go | 2 + .../github.com/jessevdk/go-flags/parser.go | 443 ++++++++++++++++- .../jessevdk/go-flags/parser_private.go | 340 ------------- .../jessevdk/go-flags/parser_test.go | 201 +++++++- 34 files changed, 3143 insertions(+), 1791 deletions(-) delete mode 100644 vendor/src/github.com/jessevdk/go-flags/command_private.go delete mode 100644 vendor/src/github.com/jessevdk/go-flags/group_private.go delete mode 100644 vendor/src/github.com/jessevdk/go-flags/ini_private.go delete mode 100644 vendor/src/github.com/jessevdk/go-flags/option_private.go delete mode 100644 vendor/src/github.com/jessevdk/go-flags/parser_private.go diff --git a/src/cmds/restic/cmd_check.go b/src/cmds/restic/cmd_check.go index cd2fc409b..01da5b09c 100644 --- a/src/cmds/restic/cmd_check.go +++ b/src/cmds/restic/cmd_check.go @@ -13,8 +13,8 @@ import ( ) type CmdCheck struct { - ReadData bool `long:"read-data" default:"false" description:"Read data blobs"` - CheckUnused bool `long:"check-unused" default:"false" description:"Check for unused blobs"` + ReadData bool `long:"read-data" description:"Read data blobs"` + CheckUnused bool `long:"check-unused" description:"Check for unused blobs"` global *GlobalOptions } diff --git a/src/cmds/restic/cmd_mount.go b/src/cmds/restic/cmd_mount.go index 2f41b01d0..1cae2e74e 100644 --- a/src/cmds/restic/cmd_mount.go +++ b/src/cmds/restic/cmd_mount.go @@ -17,7 +17,7 @@ import ( ) type CmdMount struct { - Root bool `long:"owner-root" description:"use 'root' as the owner of files and dirs" default:"false"` + Root bool `long:"owner-root" description:"use 'root' as the owner of files and dirs"` global *GlobalOptions } diff --git a/src/cmds/restic/global.go b/src/cmds/restic/global.go index b2a19d30e..98fa4736f 100644 --- a/src/cmds/restic/global.go +++ b/src/cmds/restic/global.go @@ -32,8 +32,8 @@ type GlobalOptions struct { Repo string `short:"r" long:"repo" description:"Repository directory to backup to/restore from"` PasswordFile string `short:"p" long:"password-file" description:"Read the repository password from a file"` CacheDir string ` long:"cache-dir" description:"Directory to use as a local cache"` - Quiet bool `short:"q" long:"quiet" default:"false" description:"Do not output comprehensive progress report"` - NoLock bool ` long:"no-lock" default:"false" description:"Do not lock the repo, this allows some operations on read-only repos."` + Quiet bool `short:"q" long:"quiet" description:"Do not output comprehensive progress report"` + NoLock bool ` long:"no-lock" description:"Do not lock the repo, this allows some operations on read-only repos."` Options []string `short:"o" long:"option" description:"Specify options in the form 'foo.key=value'"` password string diff --git a/vendor/manifest b/vendor/manifest index e44436756..3bfb53c76 100644 --- a/vendor/manifest +++ b/vendor/manifest @@ -16,8 +16,8 @@ { "importpath": "github.com/jessevdk/go-flags", "repository": "https://github.com/jessevdk/go-flags", - "revision": "1b89bf73cd2c3a911d7b2a279ab085c4a18cf539", - "branch": "HEAD" + "revision": "4cc2832a6e6d1d3b815e2b9d544b2a4dfb3ce8fa", + "branch": "master" }, { "importpath": "github.com/kr/fs", diff --git a/vendor/src/github.com/jessevdk/go-flags/README.md b/vendor/src/github.com/jessevdk/go-flags/README.md index b6faef6d3..9378b760b 100644 --- a/vendor/src/github.com/jessevdk/go-flags/README.md +++ b/vendor/src/github.com/jessevdk/go-flags/README.md @@ -33,9 +33,11 @@ The flags package uses structs, reflection and struct field tags to allow users to specify command line options. This results in very simple and concise specification of your application options. For example: - type Options struct { - Verbose []bool `short:"v" long:"verbose" description:"Show verbose debug information"` - } +```go +type Options struct { + Verbose []bool `short:"v" long:"verbose" description:"Show verbose debug information"` +} +``` This specifies one option with a short name -v and a long name --verbose. When either -v or --verbose is found on the command line, a 'true' value @@ -44,88 +46,90 @@ resulting value of Verbose will be {[true, true, true]}. Example: -------- - var opts struct { - // Slice of bool will append 'true' each time the option - // is encountered (can be set multiple times, like -vvv) - Verbose []bool `short:"v" long:"verbose" description:"Show verbose debug information"` +```go +var opts struct { + // Slice of bool will append 'true' each time the option + // is encountered (can be set multiple times, like -vvv) + Verbose []bool `short:"v" long:"verbose" description:"Show verbose debug information"` - // Example of automatic marshalling to desired type (uint) - Offset uint `long:"offset" description:"Offset"` + // Example of automatic marshalling to desired type (uint) + Offset uint `long:"offset" description:"Offset"` - // Example of a callback, called each time the option is found. - Call func(string) `short:"c" description:"Call phone number"` + // Example of a callback, called each time the option is found. + Call func(string) `short:"c" description:"Call phone number"` - // Example of a required flag - Name string `short:"n" long:"name" description:"A name" required:"true"` + // Example of a required flag + Name string `short:"n" long:"name" description:"A name" required:"true"` - // Example of a value name - File string `short:"f" long:"file" description:"A file" value-name:"FILE"` + // Example of a value name + File string `short:"f" long:"file" description:"A file" value-name:"FILE"` - // Example of a pointer - Ptr *int `short:"p" description:"A pointer to an integer"` + // Example of a pointer + Ptr *int `short:"p" description:"A pointer to an integer"` - // Example of a slice of strings - StringSlice []string `short:"s" description:"A slice of strings"` + // Example of a slice of strings + StringSlice []string `short:"s" description:"A slice of strings"` - // Example of a slice of pointers - PtrSlice []*string `long:"ptrslice" description:"A slice of pointers to string"` + // Example of a slice of pointers + PtrSlice []*string `long:"ptrslice" description:"A slice of pointers to string"` - // Example of a map - IntMap map[string]int `long:"intmap" description:"A map from string to int"` - } + // Example of a map + IntMap map[string]int `long:"intmap" description:"A map from string to int"` +} - // Callback which will invoke callto: to call a number. - // Note that this works just on OS X (and probably only with - // Skype) but it shows the idea. - opts.Call = func(num string) { - cmd := exec.Command("open", "callto:"+num) - cmd.Start() - cmd.Process.Release() - } +// Callback which will invoke callto: to call a number. +// Note that this works just on OS X (and probably only with +// Skype) but it shows the idea. +opts.Call = func(num string) { + cmd := exec.Command("open", "callto:"+num) + cmd.Start() + cmd.Process.Release() +} - // Make some fake arguments to parse. - args := []string{ - "-vv", - "--offset=5", - "-n", "Me", - "-p", "3", - "-s", "hello", - "-s", "world", - "--ptrslice", "hello", - "--ptrslice", "world", - "--intmap", "a:1", - "--intmap", "b:5", - "arg1", - "arg2", - "arg3", - } +// Make some fake arguments to parse. +args := []string{ + "-vv", + "--offset=5", + "-n", "Me", + "-p", "3", + "-s", "hello", + "-s", "world", + "--ptrslice", "hello", + "--ptrslice", "world", + "--intmap", "a:1", + "--intmap", "b:5", + "arg1", + "arg2", + "arg3", +} - // Parse flags from `args'. Note that here we use flags.ParseArgs for - // the sake of making a working example. Normally, you would simply use - // flags.Parse(&opts) which uses os.Args - args, err := flags.ParseArgs(&opts, args) +// Parse flags from `args'. Note that here we use flags.ParseArgs for +// the sake of making a working example. Normally, you would simply use +// flags.Parse(&opts) which uses os.Args +args, err := flags.ParseArgs(&opts, args) - if err != nil { - panic(err) - os.Exit(1) - } +if err != nil { + panic(err) + os.Exit(1) +} - fmt.Printf("Verbosity: %v\n", opts.Verbose) - fmt.Printf("Offset: %d\n", opts.Offset) - fmt.Printf("Name: %s\n", opts.Name) - fmt.Printf("Ptr: %d\n", *opts.Ptr) - fmt.Printf("StringSlice: %v\n", opts.StringSlice) - 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("Remaining args: %s\n", strings.Join(args, " ")) +fmt.Printf("Verbosity: %v\n", opts.Verbose) +fmt.Printf("Offset: %d\n", opts.Offset) +fmt.Printf("Name: %s\n", opts.Name) +fmt.Printf("Ptr: %d\n", *opts.Ptr) +fmt.Printf("StringSlice: %v\n", opts.StringSlice) +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("Remaining args: %s\n", strings.Join(args, " ")) - // Output: Verbosity: [true true] - // Offset: 5 - // Name: Me - // Ptr: 3 - // StringSlice: [hello world] - // PtrSlice: [hello world] - // IntMap: [a:1 b:5] - // Remaining args: arg1 arg2 arg3 +// Output: Verbosity: [true true] +// Offset: 5 +// Name: Me +// Ptr: 3 +// StringSlice: [hello world] +// PtrSlice: [hello world] +// IntMap: [a:1 b:5] +// Remaining args: arg1 arg2 arg3 +``` More information can be found in the godocs: diff --git a/vendor/src/github.com/jessevdk/go-flags/arg.go b/vendor/src/github.com/jessevdk/go-flags/arg.go index fd8db9c77..8ec62048f 100644 --- a/vendor/src/github.com/jessevdk/go-flags/arg.go +++ b/vendor/src/github.com/jessevdk/go-flags/arg.go @@ -12,6 +12,12 @@ type Arg struct { // A description of the positional argument (used in the help) Description string + // 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/vendor/src/github.com/jessevdk/go-flags/arg_test.go b/vendor/src/github.com/jessevdk/go-flags/arg_test.go index faea28093..c7c0a612e 100644 --- a/vendor/src/github.com/jessevdk/go-flags/arg_test.go +++ b/vendor/src/github.com/jessevdk/go-flags/arg_test.go @@ -51,3 +51,113 @@ func TestPositionalRequired(t *testing.T) { assertError(t, err, ErrRequired, "the required argument `Filename` was not provided") } + +func TestPositionalRequiredRest1Fail(t *testing.T) { + var opts = struct { + Value bool `short:"v"` + + Positional struct { + Rest []string `required:"yes"` + } `positional-args:"yes"` + }{} + + p := NewParser(&opts, None) + _, err := p.ParseArgs([]string{}) + + assertError(t, err, ErrRequired, "the required argument `Rest (at least 1 argument)` was not provided") +} + +func TestPositionalRequiredRest1Pass(t *testing.T) { + var opts = struct { + Value bool `short:"v"` + + Positional struct { + Rest []string `required:"yes"` + } `positional-args:"yes"` + }{} + + p := NewParser(&opts, None) + _, err := p.ParseArgs([]string{"rest1"}) + + if err != nil { + t.Fatalf("Unexpected error: %v", err) + return + } + + if len(opts.Positional.Rest) != 1 { + t.Fatalf("Expected 1 positional rest argument") + } + + assertString(t, opts.Positional.Rest[0], "rest1") +} + +func TestPositionalRequiredRest2Fail(t *testing.T) { + var opts = struct { + Value bool `short:"v"` + + Positional struct { + Rest []string `required:"2"` + } `positional-args:"yes"` + }{} + + p := NewParser(&opts, None) + _, err := p.ParseArgs([]string{"rest1"}) + + assertError(t, err, ErrRequired, "the required argument `Rest (at least 2 arguments, but got only 1)` was not provided") +} + +func TestPositionalRequiredRest2Pass(t *testing.T) { + var opts = struct { + Value bool `short:"v"` + + Positional struct { + Rest []string `required:"2"` + } `positional-args:"yes"` + }{} + + p := NewParser(&opts, None) + _, err := p.ParseArgs([]string{"rest1", "rest2", "rest3"}) + + if err != nil { + t.Fatalf("Unexpected error: %v", err) + return + } + + if len(opts.Positional.Rest) != 3 { + t.Fatalf("Expected 3 positional rest argument") + } + + assertString(t, opts.Positional.Rest[0], "rest1") + 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/vendor/src/github.com/jessevdk/go-flags/command.go b/vendor/src/github.com/jessevdk/go-flags/command.go index 13332ae33..26628436c 100644 --- a/vendor/src/github.com/jessevdk/go-flags/command.go +++ b/vendor/src/github.com/jessevdk/go-flags/command.go @@ -1,5 +1,13 @@ package flags +import ( + "reflect" + "sort" + "strconv" + "strings" + "unsafe" +) + // Command represents an application command. Commands can be added to the // parser (which itself is a command) and are selected/executed when its name // is specified on the command line. The Command type embeds a Group and @@ -47,6 +55,13 @@ type Usage interface { Usage() string } +type lookup struct { + shortNames map[string]*Option + longNames map[string]*Option + + commands map[string]*Command +} + // AddCommand adds a new command to the parser with the given name and data. The // data needs to be a pointer to a struct from which the fields indicate which // options are in the command. The provided data can implement the Command and @@ -97,6 +112,32 @@ func (c *Command) Find(name string) *Command { return nil } +// 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) + + c, _ = c.parent.(*Command) + } + + return option +} + +// 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) + + c, _ = c.parent.(*Command) + } + + return option +} + // Args returns a list of positional arguments associated with this command. func (c *Command) Args() []*Arg { ret := make([]*Arg, len(c.args)) @@ -104,3 +145,311 @@ func (c *Command) Args() []*Arg { return ret } + +func newCommand(name string, shortDescription string, longDescription string, data interface{}) *Command { + return &Command{ + Group: newGroup(shortDescription, longDescription, data), + Name: name, + } +} + +func (c *Command) scanSubcommandHandler(parentg *Group) scanHandler { + f := func(realval reflect.Value, sfield *reflect.StructField) (bool, error) { + mtag := newMultiTag(string(sfield.Tag)) + + if err := mtag.Parse(); err != nil { + return true, err + } + + positional := mtag.Get("positional-args") + + if len(positional) != 0 { + stype := realval.Type() + + for i := 0; i < stype.NumField(); i++ { + field := stype.Field(i) + + m := newMultiTag((string(field.Tag))) + + if err := m.Parse(); err != nil { + return true, err + } + + name := m.Get("positional-arg-name") + + if len(name) == 0 { + name = field.Name + } + + required := -1 + requiredMaximum := -1 + + sreq := m.Get("required") + + if sreq != "" { + required = 1 + + 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, + RequiredMaximum: requiredMaximum, + + value: realval.Field(i), + tag: m, + } + + c.args = append(c.args, arg) + + if len(mtag.Get("required")) != 0 { + c.ArgsRequired = true + } + } + + return true, nil + } + + subcommand := mtag.Get("command") + + if len(subcommand) != 0 { + ptrval := reflect.NewAt(realval.Type(), unsafe.Pointer(realval.UnsafeAddr())) + + shortDescription := mtag.Get("description") + longDescription := mtag.Get("long-description") + subcommandsOptional := mtag.Get("subcommands-optional") + aliases := mtag.GetMany("alias") + + subc, err := c.AddCommand(subcommand, shortDescription, longDescription, ptrval.Interface()) + if err != nil { + return true, err + } + + subc.Hidden = mtag.Get("hidden") != "" + + if len(subcommandsOptional) > 0 { + subc.SubcommandsOptional = true + } + + if len(aliases) > 0 { + subc.Aliases = aliases + } + + return true, nil + } + + return parentg.scanSubGroupHandler(realval, sfield) + } + + return f +} + +func (c *Command) scan() error { + return c.scanType(c.scanSubcommandHandler(c.Group)) +} + +func (c *Command) eachOption(f func(*Command, *Group, *Option)) { + c.eachCommand(func(c *Command) { + c.eachGroup(func(g *Group) { + for _, option := range g.options { + f(c, g, option) + } + }) + }, true) +} + +func (c *Command) eachCommand(f func(*Command), recurse bool) { + f(c) + + for _, cc := range c.commands { + if recurse { + cc.eachCommand(f, true) + } else { + f(cc) + } + } +} + +func (c *Command) eachActiveGroup(f func(cc *Command, g *Group)) { + c.eachGroup(func(g *Group) { + f(c, g) + }) + + if c.Active != nil { + c.Active.eachActiveGroup(f) + } +} + +func (c *Command) addHelpGroups(showHelp func() error) { + if !c.hasBuiltinHelpGroup { + c.addHelpGroup(showHelp) + c.hasBuiltinHelpGroup = true + } + + for _, cc := range c.commands { + cc.addHelpGroups(showHelp) + } +} + +func (c *Command) makeLookup() lookup { + ret := lookup{ + shortNames: make(map[string]*Option), + longNames: make(map[string]*Option), + commands: make(map[string]*Command), + } + + parent := c.parent + + var parents []*Command + + for parent != nil { + if cmd, ok := parent.(*Command); ok { + parents = append(parents, cmd) + parent = cmd.parent + } else { + parent = nil + } + } + + for i := len(parents) - 1; i >= 0; i-- { + parents[i].fillLookup(&ret, true) + } + + c.fillLookup(&ret, false) + return ret +} + +func (c *Command) fillLookup(ret *lookup, onlyOptions bool) { + c.eachGroup(func(g *Group) { + for _, option := range g.options { + if option.ShortName != 0 { + ret.shortNames[string(option.ShortName)] = option + } + + if len(option.LongName) > 0 { + ret.longNames[option.LongNameWithNamespace()] = option + } + } + }) + + if onlyOptions { + return + } + + for _, subcommand := range c.commands { + ret.commands[subcommand.Name] = subcommand + + for _, a := range subcommand.Aliases { + ret.commands[a] = subcommand + } + } +} + +func (c *Command) groupByName(name string) *Group { + if grp := c.Group.groupByName(name); grp != nil { + return grp + } + + for _, subc := range c.commands { + prefix := subc.Name + "." + + if strings.HasPrefix(name, prefix) { + if grp := subc.groupByName(name[len(prefix):]); grp != nil { + return grp + } + } else if name == subc.Name { + return subc.Group + } + } + + return nil +} + +type commandList []*Command + +func (c commandList) Less(i, j int) bool { + return c[i].Name < c[j].Name +} + +func (c commandList) Len() int { + return len(c) +} + +func (c commandList) Swap(i, j int) { + c[i], c[j] = c[j], c[i] +} + +func (c *Command) sortedVisibleCommands() []*Command { + ret := commandList(c.visibleCommands()) + sort.Sort(ret) + + return []*Command(ret) +} + +func (c *Command) visibleCommands() []*Command { + ret := make([]*Command, 0, len(c.commands)) + + for _, cmd := range c.commands { + if !cmd.Hidden { + ret = append(ret, cmd) + } + } + + return ret +} + +func (c *Command) match(name string) bool { + if c.Name == name { + return true + } + + for _, v := range c.Aliases { + if v == name { + return true + } + } + + return false +} + +func (c *Command) hasCliOptions() bool { + ret := false + + c.eachGroup(func(g *Group) { + if g.isBuiltinHelp { + return + } + + for _, opt := range g.options { + if opt.canCli() { + ret = true + } + } + }) + + return ret +} + +func (c *Command) fillParseState(s *parseState) { + s.positional = make([]*Arg, len(c.args)) + copy(s.positional, c.args) + + s.lookup = c.makeLookup() + s.command = c +} diff --git a/vendor/src/github.com/jessevdk/go-flags/command_private.go b/vendor/src/github.com/jessevdk/go-flags/command_private.go deleted file mode 100644 index 82ce7930e..000000000 --- a/vendor/src/github.com/jessevdk/go-flags/command_private.go +++ /dev/null @@ -1,271 +0,0 @@ -package flags - -import ( - "reflect" - "sort" - "strings" - "unsafe" -) - -type lookup struct { - shortNames map[string]*Option - longNames map[string]*Option - - commands map[string]*Command -} - -func newCommand(name string, shortDescription string, longDescription string, data interface{}) *Command { - return &Command{ - Group: newGroup(shortDescription, longDescription, data), - Name: name, - } -} - -func (c *Command) scanSubcommandHandler(parentg *Group) scanHandler { - f := func(realval reflect.Value, sfield *reflect.StructField) (bool, error) { - mtag := newMultiTag(string(sfield.Tag)) - - if err := mtag.Parse(); err != nil { - return true, err - } - - positional := mtag.Get("positional-args") - - if len(positional) != 0 { - stype := realval.Type() - - for i := 0; i < stype.NumField(); i++ { - field := stype.Field(i) - - m := newMultiTag((string(field.Tag))) - - if err := m.Parse(); err != nil { - return true, err - } - - name := m.Get("positional-arg-name") - - if len(name) == 0 { - name = field.Name - } - - arg := &Arg{ - Name: name, - Description: m.Get("description"), - - value: realval.Field(i), - tag: m, - } - - c.args = append(c.args, arg) - - if len(mtag.Get("required")) != 0 { - c.ArgsRequired = true - } - } - - return true, nil - } - - subcommand := mtag.Get("command") - - if len(subcommand) != 0 { - ptrval := reflect.NewAt(realval.Type(), unsafe.Pointer(realval.UnsafeAddr())) - - shortDescription := mtag.Get("description") - longDescription := mtag.Get("long-description") - subcommandsOptional := mtag.Get("subcommands-optional") - aliases := mtag.GetMany("alias") - - subc, err := c.AddCommand(subcommand, shortDescription, longDescription, ptrval.Interface()) - - if err != nil { - return true, err - } - - if len(subcommandsOptional) > 0 { - subc.SubcommandsOptional = true - } - - if len(aliases) > 0 { - subc.Aliases = aliases - } - - return true, nil - } - - return parentg.scanSubGroupHandler(realval, sfield) - } - - return f -} - -func (c *Command) scan() error { - return c.scanType(c.scanSubcommandHandler(c.Group)) -} - -func (c *Command) eachCommand(f func(*Command), recurse bool) { - f(c) - - for _, cc := range c.commands { - if recurse { - cc.eachCommand(f, true) - } else { - f(cc) - } - } -} - -func (c *Command) eachActiveGroup(f func(cc *Command, g *Group)) { - c.eachGroup(func(g *Group) { - f(c, g) - }) - - if c.Active != nil { - c.Active.eachActiveGroup(f) - } -} - -func (c *Command) addHelpGroups(showHelp func() error) { - if !c.hasBuiltinHelpGroup { - c.addHelpGroup(showHelp) - c.hasBuiltinHelpGroup = true - } - - for _, cc := range c.commands { - cc.addHelpGroups(showHelp) - } -} - -func (c *Command) makeLookup() lookup { - ret := lookup{ - shortNames: make(map[string]*Option), - longNames: make(map[string]*Option), - commands: make(map[string]*Command), - } - - parent := c.parent - - for parent != nil { - if cmd, ok := parent.(*Command); ok { - cmd.fillLookup(&ret, true) - } - - if grp, ok := parent.(*Group); ok { - parent = grp - } else { - parent = nil - } - } - - c.fillLookup(&ret, false) - return ret -} - -func (c *Command) fillLookup(ret *lookup, onlyOptions bool) { - c.eachGroup(func(g *Group) { - for _, option := range g.options { - if option.ShortName != 0 { - ret.shortNames[string(option.ShortName)] = option - } - - if len(option.LongName) > 0 { - ret.longNames[option.LongNameWithNamespace()] = option - } - } - }) - - if onlyOptions { - return - } - - for _, subcommand := range c.commands { - ret.commands[subcommand.Name] = subcommand - - for _, a := range subcommand.Aliases { - ret.commands[a] = subcommand - } - } -} - -func (c *Command) groupByName(name string) *Group { - if grp := c.Group.groupByName(name); grp != nil { - return grp - } - - for _, subc := range c.commands { - prefix := subc.Name + "." - - if strings.HasPrefix(name, prefix) { - if grp := subc.groupByName(name[len(prefix):]); grp != nil { - return grp - } - } else if name == subc.Name { - return subc.Group - } - } - - return nil -} - -type commandList []*Command - -func (c commandList) Less(i, j int) bool { - return c[i].Name < c[j].Name -} - -func (c commandList) Len() int { - return len(c) -} - -func (c commandList) Swap(i, j int) { - c[i], c[j] = c[j], c[i] -} - -func (c *Command) sortedCommands() []*Command { - ret := make(commandList, len(c.commands)) - copy(ret, c.commands) - - sort.Sort(ret) - return []*Command(ret) -} - -func (c *Command) match(name string) bool { - if c.Name == name { - return true - } - - for _, v := range c.Aliases { - if v == name { - return true - } - } - - return false -} - -func (c *Command) hasCliOptions() bool { - ret := false - - c.eachGroup(func(g *Group) { - if g.isBuiltinHelp { - return - } - - for _, opt := range g.options { - if opt.canCli() { - ret = true - } - } - }) - - return ret -} - -func (c *Command) fillParseState(s *parseState) { - s.positional = make([]*Arg, len(c.args)) - copy(s.positional, c.args) - - s.lookup = c.makeLookup() - s.command = c -} diff --git a/vendor/src/github.com/jessevdk/go-flags/command_test.go b/vendor/src/github.com/jessevdk/go-flags/command_test.go index 1d904ae74..dc04b66c6 100644 --- a/vendor/src/github.com/jessevdk/go-flags/command_test.go +++ b/vendor/src/github.com/jessevdk/go-flags/command_test.go @@ -106,6 +106,34 @@ func TestCommandFlagOrder2(t *testing.T) { } } +func TestCommandFlagOrderSub(t *testing.T) { + var opts = struct { + Value bool `short:"v"` + + Command struct { + G bool `short:"g"` + + SubCommand struct { + B bool `short:"b"` + } `command:"sub"` + } `command:"cmd"` + }{} + + assertParseSuccess(t, &opts, "cmd", "sub", "-v", "-g", "-b") + + if !opts.Value { + t.Errorf("Expected Value to be true") + } + + if !opts.Command.G { + t.Errorf("Expected Command.G to be true") + } + + if !opts.Command.SubCommand.B { + t.Errorf("Expected Command.SubCommand.B to be true") + } +} + func TestCommandFlagOverride1(t *testing.T) { var opts = struct { Value bool `short:"v"` @@ -146,6 +174,58 @@ func TestCommandFlagOverride2(t *testing.T) { } } +func TestCommandFlagOverrideSub(t *testing.T) { + var opts = struct { + Value bool `short:"v"` + + Command struct { + Value bool `short:"v"` + + SubCommand struct { + Value bool `short:"v"` + } `command:"sub"` + } `command:"cmd"` + }{} + + assertParseSuccess(t, &opts, "cmd", "sub", "-v") + + if opts.Value { + t.Errorf("Expected Value to be false") + } + + if opts.Command.Value { + t.Errorf("Expected Command.Value to be false") + } + + if !opts.Command.SubCommand.Value { + t.Errorf("Expected Command.Value to be true") + } +} + +func TestCommandFlagOverrideSub2(t *testing.T) { + var opts = struct { + Value bool `short:"v"` + + Command struct { + Value bool `short:"v"` + + SubCommand struct { + G bool `short:"g"` + } `command:"sub"` + } `command:"cmd"` + }{} + + assertParseSuccess(t, &opts, "cmd", "sub", "-v") + + if opts.Value { + t.Errorf("Expected Value to be false") + } + + if !opts.Command.Value { + t.Errorf("Expected Command.Value to be true") + } +} + func TestCommandEstimate(t *testing.T) { var opts = struct { Value bool `short:"v"` @@ -350,17 +430,31 @@ func TestRequiredAllOnCommand(t *testing.T) { 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"` @@ -387,16 +481,102 @@ func TestSubcommandsOptional(t *testing.T) { } } +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\"") + } +} + +func TestSubCommandFindOptionByLongFlag(t *testing.T) { + var opts struct { + Testing bool `long:"testing" description:"Testing"` + } + + var cmd struct { + Other bool `long:"other" description:"Other"` + } + + p := NewParser(&opts, Default) + c, _ := p.AddCommand("command", "Short", "Long", &cmd) + + opt := c.FindOptionByLongName("other") + + if opt == nil { + t.Errorf("Expected option, but found none") + } + + assertString(t, opt.LongName, "other") + + opt = c.FindOptionByLongName("testing") + + if opt == nil { + t.Errorf("Expected option, but found none") + } + + assertString(t, opt.LongName, "testing") +} + +func TestSubCommandFindOptionByShortFlag(t *testing.T) { + var opts struct { + Testing bool `short:"t" description:"Testing"` + } + + var cmd struct { + Other bool `short:"o" description:"Other"` + } + + p := NewParser(&opts, Default) + c, _ := p.AddCommand("command", "Short", "Long", &cmd) + + opt := c.FindOptionByShortName('o') + + if opt == nil { + t.Errorf("Expected option, but found none") + } + + if opt.ShortName != 'o' { + t.Errorf("Expected 'o', but got %v", opt.ShortName) + } + + opt = c.FindOptionByShortName('t') + + if opt == nil { + t.Errorf("Expected option, but found none") + } + + if opt.ShortName != 't' { + t.Errorf("Expected 'o', but got %v", opt.ShortName) } } diff --git a/vendor/src/github.com/jessevdk/go-flags/completion.go b/vendor/src/github.com/jessevdk/go-flags/completion.go index d0adfe031..708fa9e92 100644 --- a/vendor/src/github.com/jessevdk/go-flags/completion.go +++ b/vendor/src/github.com/jessevdk/go-flags/completion.go @@ -43,8 +43,6 @@ type Completer interface { type completion struct { parser *Parser - - ShowDescriptions bool } // Filename is a string alias which provides filename completion. @@ -79,7 +77,7 @@ func (c *completion) completeOptionNames(names map[string]*Option, prefix string 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, @@ -275,19 +273,17 @@ func (c *completion) complete(args []string) []Completion { return ret } -func (c *completion) execute(args []string) { - ret := c.complete(args) - - if c.ShowDescriptions && len(ret) > 1 { +func (c *completion) print(items []Completion, showDescriptions bool) { + if showDescriptions && len(items) > 1 { maxl := 0 - for _, v := range ret { + for _, v := range items { if len(v.Item) > maxl { maxl = len(v.Item) } } - for _, v := range ret { + for _, v := range items { fmt.Printf("%s", v.Item) if len(v.Description) > 0 { @@ -297,7 +293,7 @@ func (c *completion) execute(args []string) { fmt.Printf("\n") } } else { - for _, v := range ret { + for _, v := range items { fmt.Println(v.Item) } } diff --git a/vendor/src/github.com/jessevdk/go-flags/completion_test.go b/vendor/src/github.com/jessevdk/go-flags/completion_test.go index 2d5a97f59..72ee15808 100644 --- a/vendor/src/github.com/jessevdk/go-flags/completion_test.go +++ b/vendor/src/github.com/jessevdk/go-flags/completion_test.go @@ -40,6 +40,7 @@ var completionTestOptions struct { 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 { @@ -268,6 +269,11 @@ func TestParserCompletion(t *testing.T) { p := NewParser(&completionTestOptions, None) + p.CompletionHandler = func(items []Completion) { + comp := &completion{parser: p} + comp.print(items, test.ShowDescriptions) + } + _, err := p.ParseArgs(test.Args) w.Close() diff --git a/vendor/src/github.com/jessevdk/go-flags/convert.go b/vendor/src/github.com/jessevdk/go-flags/convert.go index 118573736..938c3ac1c 100644 --- a/vendor/src/github.com/jessevdk/go-flags/convert.go +++ b/vendor/src/github.com/jessevdk/go-flags/convert.go @@ -339,39 +339,3 @@ func unquoteIfPossible(s string) (string, error) { return strconv.Unquote(s) } - -func wrapText(s string, l int, prefix string) string { - // Basic text wrapping of s at spaces to fit in l - var ret string - - s = strings.TrimSpace(s) - - for len(s) > l { - // Try to split on space - suffix := "" - - pos := strings.LastIndex(s[:l], " ") - - if pos < 0 { - pos = l - 1 - suffix = "-\n" - } - - if len(ret) != 0 { - ret += "\n" + prefix - } - - ret += strings.TrimSpace(s[:pos]) + suffix - s = strings.TrimSpace(s[pos:]) - } - - if len(s) > 0 { - if len(ret) != 0 { - ret += "\n" + prefix - } - - return ret + s - } - - return ret -} diff --git a/vendor/src/github.com/jessevdk/go-flags/convert_test.go b/vendor/src/github.com/jessevdk/go-flags/convert_test.go index 0de0eea7a..ef131dc8d 100644 --- a/vendor/src/github.com/jessevdk/go-flags/convert_test.go +++ b/vendor/src/github.com/jessevdk/go-flags/convert_test.go @@ -157,19 +157,3 @@ func TestConvertToStringInvalidUintBase(t *testing.T) { assertError(t, err, ErrMarshal, "strconv.ParseInt: parsing \"no\": invalid syntax") } - -func TestWrapText(t *testing.T) { - s := "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." - - got := wrapText(s, 60, " ") - expected := `Lorem ipsum dolor sit amet, consectetur adipisicing elit, - sed do eiusmod tempor incididunt ut labore et dolore magna - aliqua. Ut enim ad minim veniam, quis nostrud exercitation - ullamco laboris nisi ut aliquip ex ea commodo consequat. - Duis aute irure dolor in reprehenderit in voluptate velit - esse cillum dolore eu fugiat nulla pariatur. Excepteur sint - occaecat cupidatat non proident, sunt in culpa qui officia - deserunt mollit anim id est laborum.` - - assertDiff(t, got, expected, "wrapped text") -} diff --git a/vendor/src/github.com/jessevdk/go-flags/error.go b/vendor/src/github.com/jessevdk/go-flags/error.go index fce9d3121..05528d8d2 100644 --- a/vendor/src/github.com/jessevdk/go-flags/error.go +++ b/vendor/src/github.com/jessevdk/go-flags/error.go @@ -51,6 +51,13 @@ const ( // ErrUnknownCommand indicates that an unknown command was specified. ErrUnknownCommand + + // 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 { @@ -81,6 +88,10 @@ func (e ErrorType) String() string { return "command required" case ErrUnknownCommand: return "unknown command" + case ErrInvalidChoice: + return "invalid choice" + case ErrInvalidTag: + return "invalid tag" } return "unrecognized error type" diff --git a/vendor/src/github.com/jessevdk/go-flags/example_test.go b/vendor/src/github.com/jessevdk/go-flags/example_test.go index f7be2bb14..4321ed8e9 100644 --- a/vendor/src/github.com/jessevdk/go-flags/example_test.go +++ b/vendor/src/github.com/jessevdk/go-flags/example_test.go @@ -41,7 +41,7 @@ func Example() { // Example of positional arguments Args struct { - Id string + ID string Num int Rest []string } `positional-args:"yes" required:"yes"` @@ -92,7 +92,7 @@ func Example() { 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 @@ func Example() { // 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/vendor/src/github.com/jessevdk/go-flags/flags.go b/vendor/src/github.com/jessevdk/go-flags/flags.go index 4ac67931a..9c815e0f2 100644 --- a/vendor/src/github.com/jessevdk/go-flags/flags.go +++ b/vendor/src/github.com/jessevdk/go-flags/flags.go @@ -35,6 +35,8 @@ Additional features specific to Windows: Options with long names (/verbose) Windows-style options with arguments use a colon as the delimiter Modify generated help message with Windows-style / options + Windows style options can be disabled at build time using the "forceposix" + build tag Basic usage @@ -84,7 +86,9 @@ The following is a list of tags for struct fields supported by go-flags: displayed in generated man pages (optional) no-flag: if non-empty this field is ignored as an option (optional) - optional: whether an argument of the option is optional (optional) + optional: whether an argument of the option is optional. When an + argument is optional it can only be specified using + --option=argument (optional) optional-value: the value of an optional option when the option occurs without an argument. This tag can be specified multiple times in the case of maps or slices (optional) @@ -104,6 +108,9 @@ The following is a list of tags for struct fields supported by go-flags: slices and maps (optional) value-name: the name of the argument value (to be shown in the help) (optional) + choice: limits the values for an option to a set of values. + This tag can be specified mltiple times (optional) + hidden: the option is not visible in the help or man page. base: a base (radix) used to convert strings to integer values, the default base is 10 (i.e. decimal) (optional) @@ -133,12 +140,17 @@ The following is a list of tags for struct fields supported by go-flags: then all remaining arguments will be added to it. Positional arguments are optional by default, unless the "required" tag is specified together - with the "positional-args" tag (optional) + with the "positional-args" tag. The "required" tag + can also be set on the individual rest argument + fields, to require only the first N positional + arguments. If the "required" tag is set on the + rest arguments slice, then its value determines + the minimum amount of rest arguments that needs to + be provided (e.g. `required:"2"`) (optional) positional-arg-name: used on a field in a positional argument struct; name of the positional argument placeholder to be shown in the help (optional) - Either the `short:` tag or the `long:` must be specified to make the field eligible as an option. diff --git a/vendor/src/github.com/jessevdk/go-flags/group.go b/vendor/src/github.com/jessevdk/go-flags/group.go index 8b609a3af..b1ef6ed62 100644 --- a/vendor/src/github.com/jessevdk/go-flags/group.go +++ b/vendor/src/github.com/jessevdk/go-flags/group.go @@ -6,7 +6,10 @@ package flags import ( "errors" + "reflect" "strings" + "unicode/utf8" + "unsafe" ) // ErrNotPointerToStruct indicates that a provided data container is not @@ -32,6 +35,9 @@ type Group struct { // The namespace of the group Namespace string + // If true, the group is not displayed in the help or man page + Hidden bool + // The parent of the group or nil if it has no parent parent interface{} @@ -47,6 +53,8 @@ type Group struct { data interface{} } +type scanHandler func(reflect.Value, *reflect.StructField) (bool, error) + // AddGroup adds a new group to the command with the given name and data. The // data needs to be a pointer to a struct from which the fields indicate which // options are in the group. @@ -89,3 +97,289 @@ func (g *Group) Find(shortDescription string) *Group { return ret } + +func (g *Group) findOption(matcher func(*Option) bool) (option *Option) { + g.eachGroup(func(g *Group) { + for _, opt := range g.options { + if option == nil && matcher(opt) { + option = opt + } + } + }) + + return option +} + +// 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 + }) +} + +// 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 + }) +} + +func newGroup(shortDescription string, longDescription string, data interface{}) *Group { + return &Group{ + ShortDescription: shortDescription, + LongDescription: longDescription, + + data: data, + } +} + +func (g *Group) optionByName(name string, namematch func(*Option, string) bool) *Option { + prio := 0 + var retopt *Option + + g.eachGroup(func(g *Group) { + for _, opt := range g.options { + if namematch != nil && namematch(opt, name) && prio < 4 { + retopt = opt + prio = 4 + } + + if name == opt.field.Name && prio < 3 { + retopt = opt + prio = 3 + } + + if name == opt.LongNameWithNamespace() && prio < 2 { + retopt = opt + prio = 2 + } + + if opt.ShortName != 0 && name == string(opt.ShortName) && prio < 1 { + retopt = opt + prio = 1 + } + } + }) + + return retopt +} + +func (g *Group) eachGroup(f func(*Group)) { + f(g) + + for _, gg := range g.groups { + gg.eachGroup(f) + } +} + +func (g *Group) scanStruct(realval reflect.Value, sfield *reflect.StructField, handler scanHandler) error { + stype := realval.Type() + + if sfield != nil { + if ok, err := handler(realval, sfield); err != nil { + return err + } else if ok { + return nil + } + } + + for i := 0; i < stype.NumField(); i++ { + field := stype.Field(i) + + // PkgName is set only for non-exported fields, which we ignore + if field.PkgPath != "" && !field.Anonymous { + continue + } + + mtag := newMultiTag(string(field.Tag)) + + if err := mtag.Parse(); err != nil { + return err + } + + // Skip fields with the no-flag tag + if mtag.Get("no-flag") != "" { + continue + } + + // Dive deep into structs or pointers to structs + kind := field.Type.Kind() + fld := realval.Field(i) + + if kind == reflect.Struct { + if err := g.scanStruct(fld, &field, handler); err != nil { + return err + } + } else if kind == reflect.Ptr && field.Type.Elem().Kind() == reflect.Struct { + if fld.IsNil() { + fld.Set(reflect.New(fld.Type().Elem())) + } + + if err := g.scanStruct(reflect.Indirect(fld), &field, handler); err != nil { + return err + } + } + + longname := mtag.Get("long") + shortname := mtag.Get("short") + + // Need at least either a short or long name + if longname == "" && shortname == "" && mtag.Get("ini-name") == "" { + continue + } + + short := rune(0) + rc := utf8.RuneCountInString(shortname) + + if rc > 1 { + return newErrorf(ErrShortNameTooLong, + "short names can only be 1 character long, not `%s'", + shortname) + + } else if rc == 1 { + short, _ = utf8.DecodeRuneInString(shortname) + } + + description := mtag.Get("description") + def := mtag.GetMany("default") + + optionalValue := mtag.GetMany("optional-value") + valueName := mtag.Get("value-name") + defaultMask := mtag.Get("default-mask") + + optional := (mtag.Get("optional") != "") + required := (mtag.Get("required") != "") + choices := mtag.GetMany("choice") + hidden := (mtag.Get("hidden") != "") + + option := &Option{ + Description: description, + ShortName: short, + LongName: longname, + Default: def, + EnvDefaultKey: mtag.Get("env"), + EnvDefaultDelim: mtag.Get("env-delim"), + OptionalArgument: optional, + OptionalValue: optionalValue, + Required: required, + ValueName: valueName, + DefaultMask: defaultMask, + Choices: choices, + Hidden: hidden, + + group: g, + + field: field, + value: realval.Field(i), + 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) + } + + return nil +} + +func (g *Group) checkForDuplicateFlags() *Error { + shortNames := make(map[rune]*Option) + longNames := make(map[string]*Option) + + var duplicateError *Error + + g.eachGroup(func(g *Group) { + for _, option := range g.options { + if option.LongName != "" { + longName := option.LongNameWithNamespace() + + if otherOption, ok := longNames[longName]; ok { + duplicateError = newErrorf(ErrDuplicatedFlag, "option `%s' uses the same long name as option `%s'", option, otherOption) + return + } + longNames[longName] = option + } + if option.ShortName != 0 { + if otherOption, ok := shortNames[option.ShortName]; ok { + duplicateError = newErrorf(ErrDuplicatedFlag, "option `%s' uses the same short name as option `%s'", option, otherOption) + return + } + shortNames[option.ShortName] = option + } + } + }) + + return duplicateError +} + +func (g *Group) scanSubGroupHandler(realval reflect.Value, sfield *reflect.StructField) (bool, error) { + mtag := newMultiTag(string(sfield.Tag)) + + if err := mtag.Parse(); err != nil { + return true, err + } + + subgroup := mtag.Get("group") + + if len(subgroup) != 0 { + ptrval := reflect.NewAt(realval.Type(), unsafe.Pointer(realval.UnsafeAddr())) + description := mtag.Get("description") + + group, err := g.AddGroup(subgroup, description, ptrval.Interface()) + if err != nil { + return true, err + } + + group.Namespace = mtag.Get("namespace") + group.Hidden = mtag.Get("hidden") != "" + + return true, nil + } + + return false, nil +} + +func (g *Group) scanType(handler scanHandler) error { + // Get all the public fields in the data struct + ptrval := reflect.ValueOf(g.data) + + if ptrval.Type().Kind() != reflect.Ptr { + panic(ErrNotPointerToStruct) + } + + stype := ptrval.Type().Elem() + + if stype.Kind() != reflect.Struct { + panic(ErrNotPointerToStruct) + } + + realval := reflect.Indirect(ptrval) + + if err := g.scanStruct(realval, nil, handler); err != nil { + return err + } + + if err := g.checkForDuplicateFlags(); err != nil { + return err + } + + return nil +} + +func (g *Group) scan() error { + return g.scanType(g.scanSubGroupHandler) +} + +func (g *Group) groupByName(name string) *Group { + if len(name) == 0 { + return g + } + + return g.Find(name) +} diff --git a/vendor/src/github.com/jessevdk/go-flags/group_private.go b/vendor/src/github.com/jessevdk/go-flags/group_private.go deleted file mode 100644 index 15251ce39..000000000 --- a/vendor/src/github.com/jessevdk/go-flags/group_private.go +++ /dev/null @@ -1,254 +0,0 @@ -package flags - -import ( - "reflect" - "unicode/utf8" - "unsafe" -) - -type scanHandler func(reflect.Value, *reflect.StructField) (bool, error) - -func newGroup(shortDescription string, longDescription string, data interface{}) *Group { - return &Group{ - ShortDescription: shortDescription, - LongDescription: longDescription, - - data: data, - } -} - -func (g *Group) optionByName(name string, namematch func(*Option, string) bool) *Option { - prio := 0 - var retopt *Option - - for _, opt := range g.options { - if namematch != nil && namematch(opt, name) && prio < 4 { - retopt = opt - prio = 4 - } - - if name == opt.field.Name && prio < 3 { - retopt = opt - prio = 3 - } - - if name == opt.LongNameWithNamespace() && prio < 2 { - retopt = opt - prio = 2 - } - - if opt.ShortName != 0 && name == string(opt.ShortName) && prio < 1 { - retopt = opt - prio = 1 - } - } - - return retopt -} - -func (g *Group) eachGroup(f func(*Group)) { - f(g) - - for _, gg := range g.groups { - gg.eachGroup(f) - } -} - -func (g *Group) scanStruct(realval reflect.Value, sfield *reflect.StructField, handler scanHandler) error { - stype := realval.Type() - - if sfield != nil { - if ok, err := handler(realval, sfield); err != nil { - return err - } else if ok { - return nil - } - } - - for i := 0; i < stype.NumField(); i++ { - field := stype.Field(i) - - // PkgName is set only for non-exported fields, which we ignore - if field.PkgPath != "" { - continue - } - - mtag := newMultiTag(string(field.Tag)) - - if err := mtag.Parse(); err != nil { - return err - } - - // Skip fields with the no-flag tag - if mtag.Get("no-flag") != "" { - continue - } - - // Dive deep into structs or pointers to structs - kind := field.Type.Kind() - fld := realval.Field(i) - - if kind == reflect.Struct { - if err := g.scanStruct(fld, &field, handler); err != nil { - return err - } - } else if kind == reflect.Ptr && field.Type.Elem().Kind() == reflect.Struct { - if fld.IsNil() { - fld.Set(reflect.New(fld.Type().Elem())) - } - - if err := g.scanStruct(reflect.Indirect(fld), &field, handler); err != nil { - return err - } - } - - longname := mtag.Get("long") - shortname := mtag.Get("short") - - // Need at least either a short or long name - if longname == "" && shortname == "" && mtag.Get("ini-name") == "" { - continue - } - - short := rune(0) - rc := utf8.RuneCountInString(shortname) - - if rc > 1 { - return newErrorf(ErrShortNameTooLong, - "short names can only be 1 character long, not `%s'", - shortname) - - } else if rc == 1 { - short, _ = utf8.DecodeRuneInString(shortname) - } - - description := mtag.Get("description") - def := mtag.GetMany("default") - - optionalValue := mtag.GetMany("optional-value") - valueName := mtag.Get("value-name") - defaultMask := mtag.Get("default-mask") - - optional := (mtag.Get("optional") != "") - required := (mtag.Get("required") != "") - - option := &Option{ - Description: description, - ShortName: short, - LongName: longname, - Default: def, - EnvDefaultKey: mtag.Get("env"), - EnvDefaultDelim: mtag.Get("env-delim"), - OptionalArgument: optional, - OptionalValue: optionalValue, - Required: required, - ValueName: valueName, - DefaultMask: defaultMask, - - group: g, - - field: field, - value: realval.Field(i), - tag: mtag, - } - - g.options = append(g.options, option) - } - - return nil -} - -func (g *Group) checkForDuplicateFlags() *Error { - shortNames := make(map[rune]*Option) - longNames := make(map[string]*Option) - - var duplicateError *Error - - g.eachGroup(func(g *Group) { - for _, option := range g.options { - if option.LongName != "" { - longName := option.LongNameWithNamespace() - - if otherOption, ok := longNames[longName]; ok { - duplicateError = newErrorf(ErrDuplicatedFlag, "option `%s' uses the same long name as option `%s'", option, otherOption) - return - } - longNames[longName] = option - } - if option.ShortName != 0 { - if otherOption, ok := shortNames[option.ShortName]; ok { - duplicateError = newErrorf(ErrDuplicatedFlag, "option `%s' uses the same short name as option `%s'", option, otherOption) - return - } - shortNames[option.ShortName] = option - } - } - }) - - return duplicateError -} - -func (g *Group) scanSubGroupHandler(realval reflect.Value, sfield *reflect.StructField) (bool, error) { - mtag := newMultiTag(string(sfield.Tag)) - - if err := mtag.Parse(); err != nil { - return true, err - } - - subgroup := mtag.Get("group") - - if len(subgroup) != 0 { - ptrval := reflect.NewAt(realval.Type(), unsafe.Pointer(realval.UnsafeAddr())) - description := mtag.Get("description") - - group, err := g.AddGroup(subgroup, description, ptrval.Interface()) - if err != nil { - return true, err - } - - group.Namespace = mtag.Get("namespace") - - return true, nil - } - - return false, nil -} - -func (g *Group) scanType(handler scanHandler) error { - // Get all the public fields in the data struct - ptrval := reflect.ValueOf(g.data) - - if ptrval.Type().Kind() != reflect.Ptr { - panic(ErrNotPointerToStruct) - } - - stype := ptrval.Type().Elem() - - if stype.Kind() != reflect.Struct { - panic(ErrNotPointerToStruct) - } - - realval := reflect.Indirect(ptrval) - - if err := g.scanStruct(realval, nil, handler); err != nil { - return err - } - - if err := g.checkForDuplicateFlags(); err != nil { - return err - } - - return nil -} - -func (g *Group) scan() error { - return g.scanType(g.scanSubGroupHandler) -} - -func (g *Group) groupByName(name string) *Group { - if len(name) == 0 { - return g - } - - return g.Find(name) -} diff --git a/vendor/src/github.com/jessevdk/go-flags/group_test.go b/vendor/src/github.com/jessevdk/go-flags/group_test.go index b5ed9d492..18cd6c173 100644 --- a/vendor/src/github.com/jessevdk/go-flags/group_test.go +++ b/vendor/src/github.com/jessevdk/go-flags/group_test.go @@ -185,3 +185,71 @@ func TestDuplicateLongFlags(t *testing.T) { } } } + +func TestFindOptionByLongFlag(t *testing.T) { + var opts struct { + Testing bool `long:"testing" description:"Testing"` + } + + p := NewParser(&opts, Default) + opt := p.FindOptionByLongName("testing") + + if opt == nil { + t.Errorf("Expected option, but found none") + } + + assertString(t, opt.LongName, "testing") +} + +func TestFindOptionByShortFlag(t *testing.T) { + var opts struct { + Testing bool `short:"t" description:"Testing"` + } + + p := NewParser(&opts, Default) + opt := p.FindOptionByShortName('t') + + if opt == nil { + t.Errorf("Expected option, but found none") + } + + if opt.ShortName != 't' { + t.Errorf("Expected 't', but got %v", opt.ShortName) + } +} + +func TestFindOptionByLongFlagInSubGroup(t *testing.T) { + var opts struct { + Group struct { + Testing bool `long:"testing" description:"Testing"` + } `group:"sub-group"` + } + + p := NewParser(&opts, Default) + opt := p.FindOptionByLongName("testing") + + if opt == nil { + t.Errorf("Expected option, but found none") + } + + assertString(t, opt.LongName, "testing") +} + +func TestFindOptionByShortFlagInSubGroup(t *testing.T) { + var opts struct { + Group struct { + Testing bool `short:"t" description:"Testing"` + } `group:"sub-group"` + } + + p := NewParser(&opts, Default) + opt := p.FindOptionByShortName('t') + + if opt == nil { + t.Errorf("Expected option, but found none") + } + + if opt.ShortName != 't' { + t.Errorf("Expected 't', but got %v", opt.ShortName) + } +} diff --git a/vendor/src/github.com/jessevdk/go-flags/help.go b/vendor/src/github.com/jessevdk/go-flags/help.go index e26fcd01c..1b0c2c606 100644 --- a/vendor/src/github.com/jessevdk/go-flags/help.go +++ b/vendor/src/github.com/jessevdk/go-flags/help.go @@ -9,7 +9,6 @@ import ( "bytes" "fmt" "io" - "reflect" "runtime" "strings" "unicode/utf8" @@ -92,13 +91,71 @@ func (p *Parser) getAlignmentInfo() alignmentInfo { ret.hasValueName = true } - ret.updateLen(info.LongNameWithNamespace()+info.ValueName, c != p.Command) + l := info.LongNameWithNamespace() + info.ValueName + + if len(info.Choices) != 0 { + l += "[" + strings.Join(info.Choices, "|") + "]" + } + + ret.updateLen(l, c != p.Command) } }) return ret } +func wrapText(s string, l int, prefix string) string { + var ret string + + // Basic text wrapping of s at spaces to fit in l + lines := strings.Split(s, "\n") + + for _, line := range lines { + var retline string + + line = strings.TrimSpace(line) + + for len(line) > l { + // Try to split on space + suffix := "" + + pos := strings.LastIndex(line[:l], " ") + + if pos < 0 { + pos = l - 1 + suffix = "-\n" + } + + if len(retline) != 0 { + retline += "\n" + prefix + } + + retline += strings.TrimSpace(line[:pos]) + suffix + line = strings.TrimSpace(line[pos:]) + } + + if len(line) > 0 { + if len(retline) != 0 { + retline += "\n" + prefix + } + + retline += line + } + + if len(ret) > 0 { + ret += "\n" + + if len(retline) > 0 { + ret += prefix + } + } + + ret += retline + } + + return ret +} + func (p *Parser) writeHelpOption(writer *bufio.Writer, option *Option, info alignmentInfo) { line := &bytes.Buffer{} @@ -108,6 +165,10 @@ func (p *Parser) writeHelpOption(writer *bufio.Writer, option *Option, info alig prefix += 4 } + if option.Hidden { + return + } + line.WriteString(strings.Repeat(" ", prefix)) if option.ShortName != 0 { @@ -136,6 +197,10 @@ func (p *Parser) writeHelpOption(writer *bufio.Writer, option *Option, info alig if len(option.ValueName) > 0 { line.WriteString(option.ValueName) } + + if len(option.Choices) > 0 { + line.WriteString("[" + strings.Join(option.Choices, "|") + "]") + } } written := line.Len() @@ -145,39 +210,12 @@ func (p *Parser) writeHelpOption(writer *bufio.Writer, option *Option, info alig dw := descstart - written writer.WriteString(strings.Repeat(" ", dw)) - def := "" - defs := option.Default + var def string - if len(option.DefaultMask) != 0 { - if option.DefaultMask != "-" { - def = option.DefaultMask - } - } else if len(defs) == 0 && option.canArgument() { - var showdef bool - - switch option.field.Type.Kind() { - case reflect.Func, reflect.Ptr: - showdef = !option.value.IsNil() - case reflect.Slice, reflect.String, reflect.Array: - showdef = option.value.Len() > 0 - case reflect.Map: - showdef = !option.value.IsNil() && option.value.Len() > 0 - default: - zeroval := reflect.Zero(option.field.Type) - showdef = !reflect.DeepEqual(zeroval.Interface(), option.value.Interface()) - } - - if showdef { - def, _ = convertToString(option.value, option.tag) - } - } else if len(defs) != 0 { - l := len(defs) - 1 - - for i := 0; i < l; i++ { - def += quoteIfNeeded(defs[i]) + ", " - } - - def += quoteIfNeeded(defs[l]) + if len(option.DefaultMask) != 0 && option.DefaultMask != "-" { + def = option.DefaultMask + } else { + def = option.defaultLiteral } var envDef string @@ -194,7 +232,7 @@ func (p *Parser) writeHelpOption(writer *bufio.Writer, option *Option, info alig var desc string if def != "" { - desc = fmt.Sprintf("%s (%v)%s", option.Description, def, envDef) + desc = fmt.Sprintf("%s (default: %v)%s", option.Description, def, envDef) } else { desc = option.Description + envDef } @@ -302,10 +340,12 @@ func (p *Parser) WriteHelp(writer io.Writer) { co, cc = "<", ">" } - if len(allcmd.commands) > 3 { + visibleCommands := allcmd.visibleCommands() + + if len(visibleCommands) > 3 { fmt.Fprintf(wr, " %scommand%s", co, cc) } else { - subcommands := allcmd.sortedCommands() + subcommands := allcmd.sortedVisibleCommands() names := make([]string, len(subcommands)) for i, subc := range subcommands { @@ -342,12 +382,12 @@ func (p *Parser) WriteHelp(writer io.Writer) { // Skip built-in help group for all commands except the top-level // parser - if grp.isBuiltinHelp && c != p.Command { + if grp.Hidden || (grp.isBuiltinHelp && c != p.Command) { return } for _, info := range grp.options { - if !info.canCli() { + if !info.canCli() || info.Hidden { continue } @@ -372,22 +412,41 @@ func (p *Parser) WriteHelp(writer io.Writer) { } }) - 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) @@ -397,7 +456,7 @@ func (p *Parser) WriteHelp(writer io.Writer) { c = c.Active } - scommands := cmd.sortedCommands() + scommands := cmd.sortedVisibleCommands() if len(scommands) > 0 { maxnamelen := maxCommandLength(scommands) diff --git a/vendor/src/github.com/jessevdk/go-flags/help_test.go b/vendor/src/github.com/jessevdk/go-flags/help_test.go index e10f4b6ba..1248ebe2d 100644 --- a/vendor/src/github.com/jessevdk/go-flags/help_test.go +++ b/vendor/src/github.com/jessevdk/go-flags/help_test.go @@ -21,6 +21,8 @@ type helpOptions struct { EnvDefault1 string `long:"env-default1" default:"Some value" env:"ENV_DEFAULT" description:"Test env-default1 value"` EnvDefault2 string `long:"env-default2" env:"ENV_DEFAULT" description:"Test env-default2 value"` OptionWithArgName string `long:"opt-with-arg-name" value-name:"something" description:"Option with named argument"` + OptionWithChoices string `long:"opt-with-choices" value-name:"choice" choice:"dog" choice:"cat" description:"Option with choices"` + Hidden string `long:"hidden" description:"Hidden option" hidden:"yes"` OnlyIni string `ini-name:"only-ini" description:"Option only available in ini"` @@ -29,8 +31,13 @@ type helpOptions struct { IntMap map[string]int `long:"intmap" default:"a:1" description:"A map from string to int" ini-name:"int-map"` } `group:"Other Options"` + HiddenGroup struct { + InsideHiddenGroup string `long:"inside-hidden-group" description:"Inside hidden group"` + } `group:"Hidden group" hidden:"yes"` + Group struct { - Opt string `long:"opt" description:"This is a subgroup option"` + Opt string `long:"opt" description:"This is a subgroup option"` + HiddenInsideGroup string `long:"hidden-inside-group" description:"Hidden inside group" hidden:"yes"` Group struct { Opt string `long:"opt" description:"This is a subsubgroup option"` @@ -41,9 +48,14 @@ type helpOptions struct { ExtraVerbose []bool `long:"extra-verbose" description:"Use for extra verbosity"` } `command:"command" alias:"cm" alias:"cmd" description:"A command"` + HiddenCommand struct { + ExtraVerbose []bool `long:"extra-verbose" description:"Use for extra verbosity"` + } `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"` } @@ -73,76 +85,91 @@ func TestHelp(t *testing.T) { if runtime.GOOS == "windows" { expected = `Usage: - TestHelp [OPTIONS] [filename] [num] + TestHelp [OPTIONS] [filename] [num] [hidden-in-help] + Application Options: - /v, /verbose Show verbose debug information - /c: Call phone number - /ptrslice: A slice of pointers to string + /v, /verbose Show verbose debug information + /c: Call phone number + /ptrslice: A slice of pointers to string /empty-description - /default: Test default value ("Some\nvalue") - /default-array: Test default array value (Some value, "Other\tvalue") - /default-map: Testdefault map value (some:value, another:value) - /env-default1: Test env-default1 value (Some value) [%ENV_DEFAULT%] - /env-default2: Test env-default2 value [%ENV_DEFAULT%] - /opt-with-arg-name:something Option with named argument + /default: Test default value (default: + "Some\nvalue") + /default-array: Test default array value (default: + Some value, "Other\tvalue") + /default-map: Testdefault map value (default: + some:value, another:value) + /env-default1: Test env-default1 value (default: + Some value) [%ENV_DEFAULT%] + /env-default2: Test env-default2 value + [%ENV_DEFAULT%] + /opt-with-arg-name:something Option with named argument + /opt-with-choices:choice[dog|cat] Option with choices Other Options: - /s: A slice of strings (some, value) - /intmap: A map from string to int (a:1) + /s: A slice of strings (default: some, + value) + /intmap: A map from string to int (default: + a:1) Subgroup: - /sip.opt: This is a subgroup option + /sip.opt: This is a subgroup option Subsubgroup: - /sip.sap.opt: This is a subsubgroup option + /sip.sap.opt: This is a subsubgroup option Help Options: - /? Show this help message - /h, /help Show this help message + /? Show this help message + /h, /help Show this help message Arguments: - filename: A filename - num: A number + filename: A filename + num: A number Available commands: command A command (aliases: cm, cmd) ` } else { expected = `Usage: - TestHelp [OPTIONS] [filename] [num] + TestHelp [OPTIONS] [filename] [num] [hidden-in-help] Application Options: - -v, --verbose Show verbose debug information - -c= Call phone number - --ptrslice= A slice of pointers to string + -v, --verbose Show verbose debug information + -c= Call phone number + --ptrslice= A slice of pointers to string --empty-description - --default= Test default value ("Some\nvalue") - --default-array= Test default array value (Some value, - "Other\tvalue") - --default-map= Testdefault map value (some:value, - another:value) - --env-default1= Test env-default1 value (Some value) - [$ENV_DEFAULT] - --env-default2= Test env-default2 value [$ENV_DEFAULT] - --opt-with-arg-name=something Option with named argument + --default= Test default value (default: + "Some\nvalue") + --default-array= Test default array value (default: + Some value, "Other\tvalue") + --default-map= Testdefault map value (default: + some:value, another:value) + --env-default1= Test env-default1 value (default: + Some value) [$ENV_DEFAULT] + --env-default2= Test env-default2 value + [$ENV_DEFAULT] + --opt-with-arg-name=something Option with named argument + --opt-with-choices=choice[dog|cat] Option with choices Other Options: - -s= A slice of strings (some, value) - --intmap= A map from string to int (a:1) + -s= A slice of strings (default: some, + value) + --intmap= A map from string to int (default: + a:1) Subgroup: - --sip.opt= This is a subgroup option + --sip.opt= This is a subgroup option Subsubgroup: - --sip.sap.opt= This is a subsubgroup option + --sip.sap.opt= This is a subsubgroup option Help Options: - -h, --help Show this help message + -h, --help Show this help message Arguments: - filename: A filename - num: A number + filename: A filename with a long description + to trigger line wrapping + num: A number Available commands: command A command (aliases: cm, cmd) @@ -189,6 +216,8 @@ TestMan \- Test manpage generation .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 @@ -219,14 +248,20 @@ Test env-default2 value \fB\fB\-\-opt-with-arg-name\fR \fIsomething\fR\fP Option with named argument .TP +\fB\fB\-\-opt-with-choices\fR \fIchoice\fR\fP +Option with choices +.SS Other Options +.TP \fB\fB\-s\fR \fP A slice of strings .TP \fB\fB\-\-intmap\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 @@ -237,7 +272,7 @@ A command Longer \fBcommand\fP description \fBUsage\fP: TestMan [OPTIONS] command [command-OPTIONS] - +.TP \fBAliases\fP: cm, cmd @@ -298,3 +333,136 @@ Help Options: assertDiff(t, e.Message, expected, "help message") } } + +func TestHelpDefaults(t *testing.T) { + var expected string + + if runtime.GOOS == "windows" { + expected = `Usage: + TestHelpDefaults [OPTIONS] + +Application Options: + /with-default: With default (default: default-value) + /without-default: Without default + /with-programmatic-default: With programmatic default (default: + default-value) + +Help Options: + /? Show this help message + /h, /help Show this help message +` + } else { + expected = `Usage: + TestHelpDefaults [OPTIONS] + +Application Options: + --with-default= With default (default: default-value) + --without-default= Without default + --with-programmatic-default= With programmatic default (default: + default-value) + +Help Options: + -h, --help Show this help message +` + } + + tests := []struct { + Args []string + Output string + }{ + { + Args: []string{"-h"}, + Output: expected, + }, + { + Args: []string{"--with-default", "other-value", "--with-programmatic-default", "other-value", "-h"}, + Output: expected, + }, + } + + for _, test := range tests { + var opts struct { + WithDefault string `long:"with-default" default:"default-value" description:"With default"` + WithoutDefault string `long:"without-default" description:"Without default"` + WithProgrammaticDefault string `long:"with-programmatic-default" description:"With programmatic default"` + } + + opts.WithProgrammaticDefault = "default-value" + + p := NewNamedParser("TestHelpDefaults", HelpFlag) + p.AddGroup("Application Options", "The application options", &opts) + + _, err := p.ParseArgs(test.Args) + + if err == nil { + t.Fatalf("Expected help error") + } + + if e, ok := err.(*Error); !ok { + t.Fatalf("Expected flags.Error, but got %T", err) + } else { + if e.Type != ErrHelp { + t.Errorf("Expected flags.ErrHelp type, but got %s", e.Type) + } + + assertDiff(t, e.Message, test.Output, "help message") + } + } +} + +func TestHelpRestArgs(t *testing.T) { + opts := struct { + Verbose bool `short:"v"` + }{} + + p := NewNamedParser("TestHelpDefaults", HelpFlag) + p.AddGroup("Application Options", "The application options", &opts) + + retargs, err := p.ParseArgs([]string{"-h", "-v", "rest"}) + + if err == nil { + t.Fatalf("Expected help error") + } + + assertStringArray(t, retargs, []string{"-v", "rest"}) +} + +func TestWrapText(t *testing.T) { + s := "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." + + got := wrapText(s, 60, " ") + expected := `Lorem ipsum dolor sit amet, consectetur adipisicing elit, + sed do eiusmod tempor incididunt ut labore et dolore magna + aliqua. Ut enim ad minim veniam, quis nostrud exercitation + ullamco laboris nisi ut aliquip ex ea commodo consequat. + Duis aute irure dolor in reprehenderit in voluptate velit + esse cillum dolore eu fugiat nulla pariatur. Excepteur sint + occaecat cupidatat non proident, sunt in culpa qui officia + deserunt mollit anim id est laborum.` + + assertDiff(t, got, expected, "wrapped text") +} + +func TestWrapParagraph(t *testing.T) { + s := "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n\n" + s += "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n\n" + s += "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n\n" + s += "Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n" + + got := wrapText(s, 60, " ") + expected := `Lorem ipsum dolor sit amet, consectetur adipisicing elit, + sed do eiusmod tempor incididunt ut labore et dolore magna + aliqua. + + Ut enim ad minim veniam, quis nostrud exercitation ullamco + laboris nisi ut aliquip ex ea commodo consequat. + + Duis aute irure dolor in reprehenderit in voluptate velit + esse cillum dolore eu fugiat nulla pariatur. + + Excepteur sint occaecat cupidatat non proident, sunt in + culpa qui officia deserunt mollit anim id est laborum. +` + + assertDiff(t, got, expected, "wrapped paragraph") +} diff --git a/vendor/src/github.com/jessevdk/go-flags/ini.go b/vendor/src/github.com/jessevdk/go-flags/ini.go index 722505225..16e27c0b2 100644 --- a/vendor/src/github.com/jessevdk/go-flags/ini.go +++ b/vendor/src/github.com/jessevdk/go-flags/ini.go @@ -1,8 +1,14 @@ package flags import ( + "bufio" "fmt" "io" + "os" + "reflect" + "sort" + "strconv" + "strings" ) // IniError contains location information on where an error occured. @@ -52,9 +58,25 @@ const ( // IniParser is a utility to read and write flags options from and to ini // formatted strings. type IniParser struct { + ParseAsDefaults bool // override default flags + parser *Parser } +type iniValue struct { + Name string + Value string + Quoted bool + LineNumber uint +} + +type iniSection []iniValue + +type ini struct { + File string + Sections map[string]iniSection +} + // NewIniParser creates a new ini parser for a given Parser. func NewIniParser(p *Parser) *IniParser { return &IniParser{ @@ -123,7 +145,7 @@ func (i *IniParser) Parse(reader io.Reader) error { 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 { @@ -138,3 +160,442 @@ func (i *IniParser) WriteFile(filename string, options IniOptions) error { func (i *IniParser) Write(writer io.Writer, options IniOptions) { writeIni(i, writer, options) } + +func readFullLine(reader *bufio.Reader) (string, error) { + var line []byte + + for { + l, more, err := reader.ReadLine() + + if err != nil { + return "", err + } + + if line == nil && !more { + return string(l), nil + } + + line = append(line, l...) + + if !more { + break + } + } + + return string(line), nil +} + +func optionIniName(option *Option) string { + name := option.tag.Get("_read-ini-name") + + if len(name) != 0 { + return name + } + + name = option.tag.Get("ini-name") + + if len(name) != 0 { + return name + } + + return option.field.Name +} + +func writeGroupIni(cmd *Command, group *Group, namespace string, writer io.Writer, options IniOptions) { + var sname string + + if len(namespace) != 0 { + sname = namespace + } + + if cmd.Group != group && len(group.ShortDescription) != 0 { + if len(sname) != 0 { + sname += "." + } + + sname += group.ShortDescription + } + + sectionwritten := false + comments := (options & IniIncludeComments) != IniNone + + for _, option := range group.options { + if option.isFunc() || option.Hidden { + continue + } + + if len(option.tag.Get("no-ini")) != 0 { + continue + } + + val := option.value + + if (options&IniIncludeDefaults) == IniNone && option.valueIsDefault() { + continue + } + + if !sectionwritten { + fmt.Fprintf(writer, "[%s]\n", sname) + sectionwritten = true + } + + if comments && len(option.Description) != 0 { + fmt.Fprintf(writer, "; %s\n", option.Description) + } + + oname := optionIniName(option) + + commentOption := (options&(IniIncludeDefaults|IniCommentDefaults)) == IniIncludeDefaults|IniCommentDefaults && option.valueIsDefault() + + kind := val.Type().Kind() + switch kind { + case reflect.Slice: + kind = val.Type().Elem().Kind() + + if val.Len() == 0 { + writeOption(writer, oname, kind, "", "", true, option.iniQuote) + } else { + for idx := 0; idx < val.Len(); idx++ { + v, _ := convertToString(val.Index(idx), option.tag) + + writeOption(writer, oname, kind, "", v, commentOption, option.iniQuote) + } + } + case reflect.Map: + kind = val.Type().Elem().Kind() + + if val.Len() == 0 { + writeOption(writer, oname, kind, "", "", true, option.iniQuote) + } else { + mkeys := val.MapKeys() + keys := make([]string, len(val.MapKeys())) + kkmap := make(map[string]reflect.Value) + + for i, k := range mkeys { + keys[i], _ = convertToString(k, option.tag) + kkmap[keys[i]] = k + } + + sort.Strings(keys) + + for _, k := range keys { + v, _ := convertToString(val.MapIndex(kkmap[k]), option.tag) + + writeOption(writer, oname, kind, k, v, commentOption, option.iniQuote) + } + } + default: + v, _ := convertToString(val, option.tag) + + writeOption(writer, oname, kind, "", v, commentOption, option.iniQuote) + } + + if comments { + fmt.Fprintln(writer) + } + } + + if sectionwritten && !comments { + fmt.Fprintln(writer) + } +} + +func writeOption(writer io.Writer, optionName string, optionType reflect.Kind, optionKey string, optionValue string, commentOption bool, forceQuote bool) { + if forceQuote || (optionType == reflect.String && !isPrint(optionValue)) { + optionValue = strconv.Quote(optionValue) + } + + comment := "" + if commentOption { + comment = "; " + } + + fmt.Fprintf(writer, "%s%s =", comment, optionName) + + if optionKey != "" { + fmt.Fprintf(writer, " %s:%s", optionKey, optionValue) + } else if optionValue != "" { + fmt.Fprintf(writer, " %s", optionValue) + } + + fmt.Fprintln(writer) +} + +func writeCommandIni(command *Command, namespace string, writer io.Writer, options IniOptions) { + command.eachGroup(func(group *Group) { + if !group.Hidden { + writeGroupIni(command, group, namespace, writer, options) + } + }) + + for _, c := range command.commands { + var nns string + + if c.Hidden { + continue + } + + if len(namespace) != 0 { + nns = c.Name + "." + nns + } else { + nns = c.Name + } + + writeCommandIni(c, nns, writer, options) + } +} + +func writeIni(parser *IniParser, writer io.Writer, options IniOptions) { + writeCommandIni(parser.parser.Command, "", writer, options) +} + +func writeIniToFile(parser *IniParser, filename string, options IniOptions) error { + file, err := os.Create(filename) + + if err != nil { + return err + } + + defer file.Close() + + writeIni(parser, file, options) + + return nil +} + +func readIniFromFile(filename string) (*ini, error) { + file, err := os.Open(filename) + + if err != nil { + return nil, err + } + + defer file.Close() + + return readIni(file, filename) +} + +func readIni(contents io.Reader, filename string) (*ini, error) { + ret := &ini{ + File: filename, + Sections: make(map[string]iniSection), + } + + reader := bufio.NewReader(contents) + + // Empty global section + section := make(iniSection, 0, 10) + sectionname := "" + + ret.Sections[sectionname] = section + + var lineno uint + + for { + line, err := readFullLine(reader) + + if err == io.EOF { + break + } else if err != nil { + return nil, err + } + + lineno++ + line = strings.TrimSpace(line) + + // Skip empty lines and lines starting with ; (comments) + if len(line) == 0 || line[0] == ';' || line[0] == '#' { + continue + } + + if line[0] == '[' { + if line[0] != '[' || line[len(line)-1] != ']' { + return nil, &IniError{ + Message: "malformed section header", + File: filename, + LineNumber: lineno, + } + } + + name := strings.TrimSpace(line[1 : len(line)-1]) + + if len(name) == 0 { + return nil, &IniError{ + Message: "empty section name", + File: filename, + LineNumber: lineno, + } + } + + sectionname = name + section = ret.Sections[name] + + if section == nil { + section = make(iniSection, 0, 10) + ret.Sections[name] = section + } + + continue + } + + // Parse option here + keyval := strings.SplitN(line, "=", 2) + + if len(keyval) != 2 { + return nil, &IniError{ + Message: fmt.Sprintf("malformed key=value (%s)", line), + File: filename, + LineNumber: lineno, + } + } + + name := strings.TrimSpace(keyval[0]) + value := strings.TrimSpace(keyval[1]) + quoted := false + + if len(value) != 0 && value[0] == '"' { + if v, err := strconv.Unquote(value); err == nil { + value = v + + quoted = true + } else { + return nil, &IniError{ + Message: err.Error(), + File: filename, + LineNumber: lineno, + } + } + } + + section = append(section, iniValue{ + Name: name, + Value: value, + Quoted: quoted, + LineNumber: lineno, + }) + + ret.Sections[sectionname] = section + } + + return ret, nil +} + +func (i *IniParser) matchingGroups(name string) []*Group { + if len(name) == 0 { + var ret []*Group + + i.parser.eachGroup(func(g *Group) { + ret = append(ret, g) + }) + + return ret + } + + g := i.parser.groupByName(name) + + if g != nil { + return []*Group{g} + } + + return nil +} + +func (i *IniParser) parse(ini *ini) error { + p := i.parser + + var quotesLookup = make(map[*Option]bool) + + for name, section := range ini.Sections { + groups := i.matchingGroups(name) + + if len(groups) == 0 { + return newErrorf(ErrUnknownGroup, "could not find option group `%s'", name) + } + + for _, inival := range section { + var opt *Option + + for _, group := range groups { + opt = group.optionByName(inival.Name, func(o *Option, n string) bool { + return strings.ToLower(o.tag.Get("ini-name")) == strings.ToLower(n) + }) + + if opt != nil && len(opt.tag.Get("no-ini")) != 0 { + opt = nil + } + + if opt != nil { + break + } + } + + if opt == nil { + if (p.Options & IgnoreUnknown) == None { + return &IniError{ + Message: fmt.Sprintf("unknown option: %s", inival.Name), + File: ini.File, + LineNumber: inival.LineNumber, + } + } + + continue + } + + // ini value is ignored if override is set and + // value was previously set from non default + if i.ParseAsDefaults && !opt.isSetDefault { + continue + } + + pval := &inival.Value + + if !opt.canArgument() && len(inival.Value) == 0 { + pval = nil + } else { + if opt.value.Type().Kind() == reflect.Map { + parts := strings.SplitN(inival.Value, ":", 2) + + // only handle unquoting + if len(parts) == 2 && parts[1][0] == '"' { + if v, err := strconv.Unquote(parts[1]); err == nil { + parts[1] = v + + inival.Quoted = true + } else { + return &IniError{ + Message: err.Error(), + File: ini.File, + LineNumber: inival.LineNumber, + } + } + + s := parts[0] + ":" + parts[1] + + pval = &s + } + } + } + + if err := opt.set(pval); err != nil { + return &IniError{ + Message: err.Error(), + File: ini.File, + LineNumber: inival.LineNumber, + } + } + + // either all INI values are quoted or only values who need quoting + if _, ok := quotesLookup[opt]; !inival.Quoted || !ok { + quotesLookup[opt] = inival.Quoted + } + + opt.tag.Set("_read-ini-name", inival.Name) + } + } + + for opt, quoted := range quotesLookup { + opt.iniQuote = quoted + } + + return nil +} diff --git a/vendor/src/github.com/jessevdk/go-flags/ini_private.go b/vendor/src/github.com/jessevdk/go-flags/ini_private.go deleted file mode 100644 index 887aa7679..000000000 --- a/vendor/src/github.com/jessevdk/go-flags/ini_private.go +++ /dev/null @@ -1,452 +0,0 @@ -package flags - -import ( - "bufio" - "fmt" - "io" - "os" - "reflect" - "sort" - "strconv" - "strings" -) - -type iniValue struct { - Name string - Value string - Quoted bool - LineNumber uint -} - -type iniSection []iniValue -type ini struct { - File string - Sections map[string]iniSection -} - -func readFullLine(reader *bufio.Reader) (string, error) { - var line []byte - - for { - l, more, err := reader.ReadLine() - - if err != nil { - return "", err - } - - if line == nil && !more { - return string(l), nil - } - - line = append(line, l...) - - if !more { - break - } - } - - return string(line), nil -} - -func optionIniName(option *Option) string { - name := option.tag.Get("_read-ini-name") - - if len(name) != 0 { - return name - } - - name = option.tag.Get("ini-name") - - if len(name) != 0 { - return name - } - - return option.field.Name -} - -func writeGroupIni(cmd *Command, group *Group, namespace string, writer io.Writer, options IniOptions) { - var sname string - - if len(namespace) != 0 { - sname = namespace - } - - if cmd.Group != group && len(group.ShortDescription) != 0 { - if len(sname) != 0 { - sname += "." - } - - sname += group.ShortDescription - } - - sectionwritten := false - comments := (options & IniIncludeComments) != IniNone - - for _, option := range group.options { - if option.isFunc() { - continue - } - - if len(option.tag.Get("no-ini")) != 0 { - continue - } - - val := option.value - - if (options&IniIncludeDefaults) == IniNone && option.valueIsDefault() { - continue - } - - if !sectionwritten { - fmt.Fprintf(writer, "[%s]\n", sname) - sectionwritten = true - } - - if comments && len(option.Description) != 0 { - fmt.Fprintf(writer, "; %s\n", option.Description) - } - - oname := optionIniName(option) - - commentOption := (options&(IniIncludeDefaults|IniCommentDefaults)) == IniIncludeDefaults|IniCommentDefaults && option.valueIsDefault() - - kind := val.Type().Kind() - switch kind { - case reflect.Slice: - kind = val.Type().Elem().Kind() - - if val.Len() == 0 { - writeOption(writer, oname, kind, "", "", true, option.iniQuote) - } else { - for idx := 0; idx < val.Len(); idx++ { - v, _ := convertToString(val.Index(idx), option.tag) - - writeOption(writer, oname, kind, "", v, commentOption, option.iniQuote) - } - } - case reflect.Map: - kind = val.Type().Elem().Kind() - - if val.Len() == 0 { - writeOption(writer, oname, kind, "", "", true, option.iniQuote) - } else { - mkeys := val.MapKeys() - keys := make([]string, len(val.MapKeys())) - kkmap := make(map[string]reflect.Value) - - for i, k := range mkeys { - keys[i], _ = convertToString(k, option.tag) - kkmap[keys[i]] = k - } - - sort.Strings(keys) - - for _, k := range keys { - v, _ := convertToString(val.MapIndex(kkmap[k]), option.tag) - - writeOption(writer, oname, kind, k, v, commentOption, option.iniQuote) - } - } - default: - v, _ := convertToString(val, option.tag) - - writeOption(writer, oname, kind, "", v, commentOption, option.iniQuote) - } - - if comments { - fmt.Fprintln(writer) - } - } - - if sectionwritten && !comments { - fmt.Fprintln(writer) - } -} - -func writeOption(writer io.Writer, optionName string, optionType reflect.Kind, optionKey string, optionValue string, commentOption bool, forceQuote bool) { - if forceQuote || (optionType == reflect.String && !isPrint(optionValue)) { - optionValue = strconv.Quote(optionValue) - } - - comment := "" - if commentOption { - comment = "; " - } - - fmt.Fprintf(writer, "%s%s =", comment, optionName) - - if optionKey != "" { - fmt.Fprintf(writer, " %s:%s", optionKey, optionValue) - } else if optionValue != "" { - fmt.Fprintf(writer, " %s", optionValue) - } - - fmt.Fprintln(writer) -} - -func writeCommandIni(command *Command, namespace string, writer io.Writer, options IniOptions) { - command.eachGroup(func(group *Group) { - writeGroupIni(command, group, namespace, writer, options) - }) - - for _, c := range command.commands { - var nns string - - if len(namespace) != 0 { - nns = c.Name + "." + nns - } else { - nns = c.Name - } - - writeCommandIni(c, nns, writer, options) - } -} - -func writeIni(parser *IniParser, writer io.Writer, options IniOptions) { - writeCommandIni(parser.parser.Command, "", writer, options) -} - -func writeIniToFile(parser *IniParser, filename string, options IniOptions) error { - file, err := os.Create(filename) - - if err != nil { - return err - } - - defer file.Close() - - writeIni(parser, file, options) - - return nil -} - -func readIniFromFile(filename string) (*ini, error) { - file, err := os.Open(filename) - - if err != nil { - return nil, err - } - - defer file.Close() - - return readIni(file, filename) -} - -func readIni(contents io.Reader, filename string) (*ini, error) { - ret := &ini{ - File: filename, - Sections: make(map[string]iniSection), - } - - reader := bufio.NewReader(contents) - - // Empty global section - section := make(iniSection, 0, 10) - sectionname := "" - - ret.Sections[sectionname] = section - - var lineno uint - - for { - line, err := readFullLine(reader) - - if err == io.EOF { - break - } else if err != nil { - return nil, err - } - - lineno++ - line = strings.TrimSpace(line) - - // Skip empty lines and lines starting with ; (comments) - if len(line) == 0 || line[0] == ';' || line[0] == '#' { - continue - } - - if line[0] == '[' { - if line[0] != '[' || line[len(line)-1] != ']' { - return nil, &IniError{ - Message: "malformed section header", - File: filename, - LineNumber: lineno, - } - } - - name := strings.TrimSpace(line[1 : len(line)-1]) - - if len(name) == 0 { - return nil, &IniError{ - Message: "empty section name", - File: filename, - LineNumber: lineno, - } - } - - sectionname = name - section = ret.Sections[name] - - if section == nil { - section = make(iniSection, 0, 10) - ret.Sections[name] = section - } - - continue - } - - // Parse option here - keyval := strings.SplitN(line, "=", 2) - - if len(keyval) != 2 { - return nil, &IniError{ - Message: fmt.Sprintf("malformed key=value (%s)", line), - File: filename, - LineNumber: lineno, - } - } - - name := strings.TrimSpace(keyval[0]) - value := strings.TrimSpace(keyval[1]) - quoted := false - - if len(value) != 0 && value[0] == '"' { - if v, err := strconv.Unquote(value); err == nil { - value = v - - quoted = true - } else { - return nil, &IniError{ - Message: err.Error(), - File: filename, - LineNumber: lineno, - } - } - } - - section = append(section, iniValue{ - Name: name, - Value: value, - Quoted: quoted, - LineNumber: lineno, - }) - - ret.Sections[sectionname] = section - } - - return ret, nil -} - -func (i *IniParser) matchingGroups(name string) []*Group { - if len(name) == 0 { - var ret []*Group - - i.parser.eachGroup(func(g *Group) { - ret = append(ret, g) - }) - - return ret - } - - g := i.parser.groupByName(name) - - if g != nil { - return []*Group{g} - } - - return nil -} - -func (i *IniParser) parse(ini *ini) error { - p := i.parser - - var quotesLookup = make(map[*Option]bool) - - for name, section := range ini.Sections { - groups := i.matchingGroups(name) - - if len(groups) == 0 { - return newErrorf(ErrUnknownGroup, "could not find option group `%s'", name) - } - - for _, inival := range section { - var opt *Option - - for _, group := range groups { - opt = group.optionByName(inival.Name, func(o *Option, n string) bool { - return strings.ToLower(o.tag.Get("ini-name")) == strings.ToLower(n) - }) - - if opt != nil && len(opt.tag.Get("no-ini")) != 0 { - opt = nil - } - - if opt != nil { - break - } - } - - if opt == nil { - if (p.Options & IgnoreUnknown) == None { - return &IniError{ - Message: fmt.Sprintf("unknown option: %s", inival.Name), - File: ini.File, - LineNumber: inival.LineNumber, - } - } - - continue - } - - pval := &inival.Value - - if !opt.canArgument() && len(inival.Value) == 0 { - pval = nil - } else { - if opt.value.Type().Kind() == reflect.Map { - parts := strings.SplitN(inival.Value, ":", 2) - - // only handle unquoting - if len(parts) == 2 && parts[1][0] == '"' { - if v, err := strconv.Unquote(parts[1]); err == nil { - parts[1] = v - - inival.Quoted = true - } else { - return &IniError{ - Message: err.Error(), - File: ini.File, - LineNumber: inival.LineNumber, - } - } - - s := parts[0] + ":" + parts[1] - - pval = &s - } - } - } - - if err := opt.set(pval); err != nil { - return &IniError{ - Message: err.Error(), - File: ini.File, - LineNumber: inival.LineNumber, - } - } - - // either all INI values are quoted or only values who need quoting - if _, ok := quotesLookup[opt]; !inival.Quoted || !ok { - quotesLookup[opt] = inival.Quoted - } - - opt.tag.Set("_read-ini-name", inival.Name) - } - } - - for opt, quoted := range quotesLookup { - opt.iniQuote = quoted - } - - return nil -} diff --git a/vendor/src/github.com/jessevdk/go-flags/ini_test.go b/vendor/src/github.com/jessevdk/go-flags/ini_test.go index 215b75795..1d25d4563 100644 --- a/vendor/src/github.com/jessevdk/go-flags/ini_test.go +++ b/vendor/src/github.com/jessevdk/go-flags/ini_test.go @@ -21,7 +21,7 @@ func TestWriteIni(t *testing.T) { 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 @@ int-map = b:3 `, }, { - []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 @@ -74,6 +74,9 @@ EnvDefault2 = env-def ; Option with named argument OptionWithArgName = +; Option with choices +OptionWithChoices = + ; Option only available in ini only-ini = @@ -101,7 +104,7 @@ Opt = `, }, { - []string{"filename", "0", "command"}, + []string{"filename", "0", "3.14", "command"}, IniDefault | IniIncludeDefaults | IniCommentDefaults, `[Application Options] ; Show verbose debug information @@ -132,6 +135,9 @@ EnvDefault2 = env-def ; Option with named argument ; OptionWithArgName = +; Option with choices +; OptionWithChoices = + ; Option only available in ini ; only-ini = @@ -158,7 +164,7 @@ EnvDefault2 = env-def `, }, { - []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 @@ -187,6 +193,9 @@ EnvDefault2 = env-def ; Option with named argument ; OptionWithArgName = +; Option with choices +; OptionWithChoices = + ; Option only available in ini ; only-ini = @@ -239,6 +248,92 @@ EnvDefault2 = env-def } } +func TestReadIni_flagEquivalent(t *testing.T) { + type options struct { + Opt1 bool `long:"opt1"` + + Group1 struct { + Opt2 bool `long:"opt2"` + } `group:"group1"` + + Group2 struct { + Opt3 bool `long:"opt3"` + } `group:"group2" namespace:"ns1"` + + Cmd1 struct { + Opt4 bool `long:"opt4"` + Opt5 bool `long:"foo.opt5"` + + Group1 struct { + Opt6 bool `long:"opt6"` + Opt7 bool `long:"foo.opt7"` + } `group:"group1"` + + Group2 struct { + Opt8 bool `long:"opt8"` + } `group:"group2" namespace:"ns1"` + } `command:"cmd1"` + } + + a := ` +opt1=true + +[group1] +opt2=true + +[group2] +ns1.opt3=true + +[cmd1] +opt4=true +foo.opt5=true + +[cmd1.group1] +opt6=true +foo.opt7=true + +[cmd1.group2] +ns1.opt8=true +` + b := ` +opt1=true +opt2=true +ns1.opt3=true + +[cmd1] +opt4=true +foo.opt5=true +opt6=true +foo.opt7=true +ns1.opt8=true +` + + parse := func(readIni string) (opts options, writeIni string) { + p := NewNamedParser("TestIni", Default) + p.AddGroup("Application Options", "The application options", &opts) + + inip := NewIniParser(p) + err := inip.Parse(strings.NewReader(readIni)) + + if err != nil { + t.Fatalf("Unexpected error: %s\n\nFile:\n%s", err, readIni) + } + + var b bytes.Buffer + inip.Write(&b, Default) + + return opts, b.String() + } + + aOpt, aIni := parse(a) + bOpt, bIni := parse(b) + + assertDiff(t, aIni, bIni, "") + if !reflect.DeepEqual(aOpt, bOpt) { + t.Errorf("not equal") + } +} + func TestReadIni(t *testing.T) { var opts helpOptions @@ -663,6 +758,94 @@ func TestIniParse(t *testing.T) { } } +func TestIniCliOverrides(t *testing.T) { + file, err := ioutil.TempFile("", "") + + if err != nil { + t.Fatalf("Cannot create temporary file: %s", err) + } + + defer os.Remove(file.Name()) + + _, err = file.WriteString("values = 123\n") + _, err = file.WriteString("values = 456\n") + + if err != nil { + t.Fatalf("Cannot write to temporary file: %s", err) + } + + file.Close() + + var opts struct { + Values []int `long:"values"` + } + + p := NewParser(&opts, Default) + err = NewIniParser(p).ParseFile(file.Name()) + + if err != nil { + t.Fatalf("Could not parse ini: %s", err) + } + + _, err = p.ParseArgs([]string{"--values", "111", "--values", "222"}) + + if err != nil { + t.Fatalf("Failed to parse arguments: %s", err) + } + + if len(opts.Values) != 2 { + t.Fatalf("Expected Values to contain two elements, but got %d", len(opts.Values)) + } + + if opts.Values[0] != 111 { + t.Fatalf("Expected Values[0] to be 111, but got '%d'", opts.Values[0]) + } + + if opts.Values[1] != 222 { + t.Fatalf("Expected Values[1] to be 222, but got '%d'", opts.Values[1]) + } +} + +func TestIniOverrides(t *testing.T) { + file, err := ioutil.TempFile("", "") + + if err != nil { + t.Fatalf("Cannot create temporary file: %s", err) + } + + defer os.Remove(file.Name()) + + _, err = file.WriteString("value-with-default = \"ini-value\"\n") + _, err = file.WriteString("value-with-default-override-cli = \"ini-value\"\n") + + if err != nil { + t.Fatalf("Cannot write to temporary file: %s", err) + } + + file.Close() + + var opts struct { + ValueWithDefault string `long:"value-with-default" default:"value"` + ValueWithDefaultOverrideCli string `long:"value-with-default-override-cli" default:"value"` + } + + p := NewParser(&opts, Default) + err = NewIniParser(p).ParseFile(file.Name()) + + if err != nil { + t.Fatalf("Could not parse ini: %s", err) + } + + _, err = p.ParseArgs([]string{"--value-with-default-override-cli", "cli-value"}) + + if err != nil { + t.Fatalf("Failed to parse arguments: %s", err) + } + + assertString(t, opts.ValueWithDefault, "ini-value") + assertString(t, opts.ValueWithDefaultOverrideCli, "cli-value") +} + func TestWriteFile(t *testing.T) { file, err := ioutil.TempFile("", "") if err != nil { @@ -765,3 +948,74 @@ func TestOverwriteRequiredOptions(t *testing.T) { } } } + +func TestIniOverwriteOptions(t *testing.T) { + var tests = []struct { + args []string + expected string + toggled bool + }{ + { + args: []string{}, + expected: "from default", + }, + { + args: []string{"--value", "from CLI"}, + expected: "from CLI", + }, + { + args: []string{"--config", "no file name"}, + expected: "from INI", + toggled: true, + }, + { + args: []string{"--value", "from CLI before", "--config", "no file name"}, + expected: "from CLI before", + toggled: true, + }, + { + args: []string{"--config", "no file name", "--value", "from CLI after"}, + expected: "from CLI after", + toggled: true, + }, + { + args: []string{"--toggle"}, + toggled: true, + expected: "from default", + }, + } + + for _, test := range tests { + var opts struct { + Config string `long:"config" no-ini:"true"` + Value string `long:"value" default:"from default"` + Toggle bool `long:"toggle"` + } + + p := NewParser(&opts, Default) + + _, err := p.ParseArgs(test.args) + if err != nil { + t.Fatalf("Unexpected error %s with args %+v", err, test.args) + } + + if opts.Config != "" { + inip := NewIniParser(p) + inip.ParseAsDefaults = true + + err = inip.Parse(bytes.NewBufferString("value = from INI\ntoggle = true")) + if err != nil { + t.Fatalf("Unexpected error %s with args %+v", err, test.args) + } + } + + if opts.Value != test.expected { + t.Fatalf("Expected Value to be \"%s\" but was \"%s\" with args %+v", test.expected, opts.Value, test.args) + } + + if opts.Toggle != test.toggled { + t.Fatalf("Expected Toggle to be \"%v\" but was \"%v\" with args %+v", test.toggled, opts.Toggle, test.args) + } + + } +} diff --git a/vendor/src/github.com/jessevdk/go-flags/man.go b/vendor/src/github.com/jessevdk/go-flags/man.go index 95347d07f..0cb114e74 100644 --- a/vendor/src/github.com/jessevdk/go-flags/man.go +++ b/vendor/src/github.com/jessevdk/go-flags/man.go @@ -38,8 +38,23 @@ func formatForMan(wr io.Writer, s string) { func writeManPageOptions(wr io.Writer, grp *Group) { grp.eachGroup(func(group *Group) { + 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() { + if !opt.canCli() || opt.Hidden { continue } @@ -91,11 +106,15 @@ func writeManPageOptions(wr io.Writer, grp *Group) { } func writeManPageSubcommands(wr io.Writer, name string, root *Command) { - commands := root.sortedCommands() + commands := root.sortedVisibleCommands() for _, c := range commands { var nn string + if c.Hidden { + continue + } + if len(name) != 0 { nn = name + " " + c.Name } else { @@ -141,7 +160,7 @@ func writeManPageCommand(wr io.Writer, name string, root *Command, command *Comm } if len(usage) > 0 { - fmt.Fprintf(wr, "\n\\fBUsage\\fP: %s %s\n\n", manQuote(pre), manQuote(usage)) + fmt.Fprintf(wr, "\n\\fBUsage\\fP: %s %s\n.TP\n", manQuote(pre), manQuote(usage)) } if len(command.Aliases) > 0 { @@ -178,7 +197,7 @@ func (p *Parser) WriteManPage(wr io.Writer) { writeManPageOptions(wr, p.Command.Group) - if len(p.commands) > 0 { + if len(p.visibleCommands()) > 0 { fmt.Fprintln(wr, ".SH COMMANDS") writeManPageSubcommands(wr, "", p.Command) diff --git a/vendor/src/github.com/jessevdk/go-flags/marshal_test.go b/vendor/src/github.com/jessevdk/go-flags/marshal_test.go index 59c9ccefb..4cfe86568 100644 --- a/vendor/src/github.com/jessevdk/go-flags/marshal_test.go +++ b/vendor/src/github.com/jessevdk/go-flags/marshal_test.go @@ -5,13 +5,13 @@ import ( "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) UnmarshalFlag(value string) error { } func (m marshalled) MarshalFlag() (string, error) { - if m { + if m == "true" { return "yes", nil } @@ -42,8 +42,8 @@ func TestUnmarshal(t *testing.T) { 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 @@ func TestUnmarshalDefault(t *testing.T) { 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 @@ func TestUnmarshalOptional(t *testing.T) { 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 @@ func TestUnmarshalError(t *testing.T) { 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/vendor/src/github.com/jessevdk/go-flags/option.go b/vendor/src/github.com/jessevdk/go-flags/option.go index 42c105921..17a21890d 100644 --- a/vendor/src/github.com/jessevdk/go-flags/option.go +++ b/vendor/src/github.com/jessevdk/go-flags/option.go @@ -1,8 +1,11 @@ package flags import ( + "bytes" "fmt" "reflect" + "strings" + "syscall" "unicode/utf8" ) @@ -59,6 +62,12 @@ type Option struct { // passwords. DefaultMask string + // If non empty, only a certain set of values is allowed for an option. + Choices []string + + // If true, the option is not displayed in the help or man page + Hidden bool + // The group which the option belongs to group *Group @@ -71,8 +80,12 @@ type Option struct { // Determines if the option will be always quoted in the INI output iniQuote bool - tag multiTag - isSet bool + tag multiTag + isSet bool + isSetDefault bool + preventDefault bool + + defaultLiteral string } // LongNameWithNamespace returns the option's long name with the group namespaces @@ -156,7 +169,288 @@ func (option *Option) Value() interface{} { return option.value.Interface() } +// Field returns the reflect struct field of the option. +func (option *Option) Field() reflect.StructField { + return option.field +} + // IsSet returns true if option has been set func (option *Option) IsSet() bool { return option.isSet } + +// Set the value of an option to the specified value. An error will be returned +// if the specified value could not be converted to the corresponding option +// value type. +func (option *Option) set(value *string) error { + kind := option.value.Type().Kind() + + if (kind == reflect.Map || kind == reflect.Slice) && !option.isSet { + option.empty() + } + + option.isSet = true + option.preventDefault = true + + if len(option.Choices) != 0 { + found := false + + for _, choice := range option.Choices { + if choice == *value { + found = true + break + } + } + + if !found { + allowed := strings.Join(option.Choices[0:len(option.Choices)-1], ", ") + + if len(option.Choices) > 1 { + allowed += " or " + option.Choices[len(option.Choices)-1] + } + + return newErrorf(ErrInvalidChoice, + "Invalid value `%s' for option `%s'. Allowed values are: %s", + *value, option, allowed) + } + } + + if option.isFunc() { + return option.call(value) + } else if value != nil { + return convert(*value, option.value, option.tag) + } + + return convert("", option.value, option.tag) +} + +func (option *Option) canCli() bool { + return option.ShortName != 0 || len(option.LongName) != 0 +} + +func (option *Option) canArgument() bool { + if u := option.isUnmarshaler(); u != nil { + return true + } + + return !option.isBool() +} + +func (option *Option) emptyValue() reflect.Value { + tp := option.value.Type() + + if tp.Kind() == reflect.Map { + return reflect.MakeMap(tp) + } + + return reflect.Zero(tp) +} + +func (option *Option) empty() { + if !option.isFunc() { + option.value.Set(option.emptyValue()) + } +} + +func (option *Option) clearDefault() { + usedDefault := option.Default + + if envKey := option.EnvDefaultKey; envKey != "" { + // os.Getenv() makes no distinction between undefined and + // empty values, so we use syscall.Getenv() + if value, ok := syscall.Getenv(envKey); ok { + if option.EnvDefaultDelim != "" { + usedDefault = strings.Split(value, + option.EnvDefaultDelim) + } else { + usedDefault = []string{value} + } + } + } + + option.isSetDefault = true + + if len(usedDefault) > 0 { + option.empty() + + for _, d := range usedDefault { + option.set(&d) + option.isSetDefault = true + } + } else { + tp := option.value.Type() + + switch tp.Kind() { + case reflect.Map: + if option.value.IsNil() { + option.empty() + } + case reflect.Slice: + if option.value.IsNil() { + option.empty() + } + } + } +} + +func (option *Option) valueIsDefault() bool { + // Check if the value of the option corresponds to its + // default value + emptyval := option.emptyValue() + + checkvalptr := reflect.New(emptyval.Type()) + checkval := reflect.Indirect(checkvalptr) + + checkval.Set(emptyval) + + if len(option.Default) != 0 { + for _, v := range option.Default { + convert(v, checkval, option.tag) + } + } + + return reflect.DeepEqual(option.value.Interface(), checkval.Interface()) +} + +func (option *Option) isUnmarshaler() Unmarshaler { + v := option.value + + for { + if !v.CanInterface() { + break + } + + i := v.Interface() + + if u, ok := i.(Unmarshaler); ok { + return u + } + + if !v.CanAddr() { + break + } + + v = v.Addr() + } + + return nil +} + +func (option *Option) isBool() bool { + tp := option.value.Type() + + for { + switch tp.Kind() { + case reflect.Slice, reflect.Ptr: + tp = tp.Elem() + case reflect.Bool: + return true + case reflect.Func: + return tp.NumIn() == 0 + default: + return false + } + } +} + +func (option *Option) isSignedNumber() bool { + tp := option.value.Type() + + for { + switch tp.Kind() { + case reflect.Slice, reflect.Ptr: + tp = tp.Elem() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Float32, reflect.Float64: + return true + default: + return false + } + } +} + +func (option *Option) isFunc() bool { + return option.value.Type().Kind() == reflect.Func +} + +func (option *Option) call(value *string) error { + var retval []reflect.Value + + if value == nil { + retval = option.value.Call(nil) + } else { + tp := option.value.Type().In(0) + + val := reflect.New(tp) + val = reflect.Indirect(val) + + if err := convert(*value, val, option.tag); err != nil { + return err + } + + retval = option.value.Call([]reflect.Value{val}) + } + + if len(retval) == 1 && retval[0].Type() == reflect.TypeOf((*error)(nil)).Elem() { + if retval[0].Interface() == nil { + return nil + } + + return retval[0].Interface().(error) + } + + return nil +} + +func (option *Option) updateDefaultLiteral() { + defs := option.Default + def := "" + + if len(defs) == 0 && option.canArgument() { + var showdef bool + + switch option.field.Type.Kind() { + case reflect.Func, reflect.Ptr: + showdef = !option.value.IsNil() + case reflect.Slice, reflect.String, reflect.Array: + showdef = option.value.Len() > 0 + case reflect.Map: + showdef = !option.value.IsNil() && option.value.Len() > 0 + default: + zeroval := reflect.Zero(option.field.Type) + showdef = !reflect.DeepEqual(zeroval.Interface(), option.value.Interface()) + } + + if showdef { + def, _ = convertToString(option.value, option.tag) + } + } else if len(defs) != 0 { + l := len(defs) - 1 + + for i := 0; i < l; i++ { + def += quoteIfNeeded(defs[i]) + ", " + } + + def += quoteIfNeeded(defs[l]) + } + + 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/vendor/src/github.com/jessevdk/go-flags/option_private.go b/vendor/src/github.com/jessevdk/go-flags/option_private.go deleted file mode 100644 index d36c84117..000000000 --- a/vendor/src/github.com/jessevdk/go-flags/option_private.go +++ /dev/null @@ -1,182 +0,0 @@ -package flags - -import ( - "reflect" - "strings" - "syscall" -) - -// Set the value of an option to the specified value. An error will be returned -// if the specified value could not be converted to the corresponding option -// value type. -func (option *Option) set(value *string) error { - option.isSet = true - - if option.isFunc() { - return option.call(value) - } else if value != nil { - return convert(*value, option.value, option.tag) - } - - return convert("", option.value, option.tag) -} - -func (option *Option) canCli() bool { - return option.ShortName != 0 || len(option.LongName) != 0 -} - -func (option *Option) canArgument() bool { - if u := option.isUnmarshaler(); u != nil { - return true - } - - return !option.isBool() -} - -func (option *Option) emptyValue() reflect.Value { - tp := option.value.Type() - - if tp.Kind() == reflect.Map { - return reflect.MakeMap(tp) - } - - return reflect.Zero(tp) -} - -func (option *Option) empty() { - if !option.isFunc() { - option.value.Set(option.emptyValue()) - } -} - -func (option *Option) clearDefault() { - usedDefault := option.Default - if envKey := option.EnvDefaultKey; envKey != "" { - // os.Getenv() makes no distinction between undefined and - // empty values, so we use syscall.Getenv() - if value, ok := syscall.Getenv(envKey); ok { - if option.EnvDefaultDelim != "" { - usedDefault = strings.Split(value, - option.EnvDefaultDelim) - } else { - usedDefault = []string{value} - } - } - } - - if len(usedDefault) > 0 { - option.empty() - - for _, d := range usedDefault { - option.set(&d) - } - } else { - tp := option.value.Type() - - switch tp.Kind() { - case reflect.Map: - if option.value.IsNil() { - option.empty() - } - case reflect.Slice: - if option.value.IsNil() { - option.empty() - } - } - } -} - -func (option *Option) valueIsDefault() bool { - // Check if the value of the option corresponds to its - // default value - emptyval := option.emptyValue() - - checkvalptr := reflect.New(emptyval.Type()) - checkval := reflect.Indirect(checkvalptr) - - checkval.Set(emptyval) - - if len(option.Default) != 0 { - for _, v := range option.Default { - convert(v, checkval, option.tag) - } - } - - return reflect.DeepEqual(option.value.Interface(), checkval.Interface()) -} - -func (option *Option) isUnmarshaler() Unmarshaler { - v := option.value - - for { - if !v.CanInterface() { - break - } - - i := v.Interface() - - if u, ok := i.(Unmarshaler); ok { - return u - } - - if !v.CanAddr() { - break - } - - v = v.Addr() - } - - return nil -} - -func (option *Option) isBool() bool { - tp := option.value.Type() - - for { - switch tp.Kind() { - case reflect.Bool: - return true - case reflect.Slice: - return (tp.Elem().Kind() == reflect.Bool) - case reflect.Func: - return tp.NumIn() == 0 - case reflect.Ptr: - tp = tp.Elem() - default: - return false - } - } -} - -func (option *Option) isFunc() bool { - return option.value.Type().Kind() == reflect.Func -} - -func (option *Option) call(value *string) error { - var retval []reflect.Value - - if value == nil { - retval = option.value.Call(nil) - } else { - tp := option.value.Type().In(0) - - val := reflect.New(tp) - val = reflect.Indirect(val) - - if err := convert(*value, val, option.tag); err != nil { - return err - } - - retval = option.value.Call([]reflect.Value{val}) - } - - if len(retval) == 1 && retval[0].Type() == reflect.TypeOf((*error)(nil)).Elem() { - if retval[0].Interface() == nil { - return nil - } - - return retval[0].Interface().(error) - } - - return nil -} diff --git a/vendor/src/github.com/jessevdk/go-flags/optstyle_other.go b/vendor/src/github.com/jessevdk/go-flags/optstyle_other.go index 29ca4b606..56dfdae12 100644 --- a/vendor/src/github.com/jessevdk/go-flags/optstyle_other.go +++ b/vendor/src/github.com/jessevdk/go-flags/optstyle_other.go @@ -1,4 +1,4 @@ -// +build !windows +// +build !windows forceposix package flags diff --git a/vendor/src/github.com/jessevdk/go-flags/optstyle_windows.go b/vendor/src/github.com/jessevdk/go-flags/optstyle_windows.go index a51de9cb2..f3f28aeef 100644 --- a/vendor/src/github.com/jessevdk/go-flags/optstyle_windows.go +++ b/vendor/src/github.com/jessevdk/go-flags/optstyle_windows.go @@ -1,3 +1,5 @@ +// +build !forceposix + package flags import ( diff --git a/vendor/src/github.com/jessevdk/go-flags/parser.go b/vendor/src/github.com/jessevdk/go-flags/parser.go index 6f45a0396..3a5562605 100644 --- a/vendor/src/github.com/jessevdk/go-flags/parser.go +++ b/vendor/src/github.com/jessevdk/go-flags/parser.go @@ -5,8 +5,13 @@ package flags import ( + "bytes" + "fmt" "os" "path" + "sort" + "strings" + "unicode/utf8" ) // A Parser provides command line option parsing. It can contain several @@ -32,6 +37,21 @@ type Parser struct { // or an error to indicate a parse failure. UnknownOptionHandler func(option string, arg SplitArgument, args []string) ([]string, error) + // CompletionHandler is a function gets called to handle the completion of + // items. By default, the items are printed and the application is exited. + // 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 } @@ -93,6 +113,17 @@ const ( Default = HelpFlag | PrintErrors | PassDoubleDash ) +type parseState struct { + arg string + args []string + retargs []string + positional []*Arg + err error + + command *Command + lookup lookup +} + // Parse is a convenience function to parse command line options with default // settings. The provided data is a pointer to a struct representing the // default option group (named "Application Options"). For more control, use @@ -162,14 +193,19 @@ func (p *Parser) Parse() ([]string, error) { // // When the common help group has been added (AddHelp) and either -h or --help // was specified in the command line arguments, a help message will be -// automatically printed. Furthermore, the special error type ErrHelp is returned. +// automatically printed if the PrintErrors option is enabled. +// Furthermore, the special error type ErrHelp is returned. // It is up to the caller to exit the program if so desired. func (p *Parser) ParseArgs(args []string) ([]string, error) { if p.internalError != nil { return nil, p.internalError } - p.clearIsSet() + p.eachOption(func(c *Command, g *Group, option *Option) { + option.isSet = false + option.isSetDefault = false + option.updateDefaultLiteral() + }) // Add built-in help group to all commands if necessary if (p.Options & HelpFlag) != None { @@ -180,13 +216,15 @@ func (p *Parser) ParseArgs(args []string) ([]string, error) { if len(compval) != 0 { comp := &completion{parser: p} + items := comp.complete(args) - if compval == "verbose" { - comp.ShowDescriptions = true + if p.CompletionHandler != nil { + p.CompletionHandler(items) + } else { + comp.print(items, compval == "verbose") + os.Exit(0) } - comp.execute(args) - return nil, nil } @@ -253,17 +291,13 @@ func (p *Parser) ParseArgs(args []string) ([]string, error) { } if s.err == nil { - p.eachCommand(func(c *Command) { - c.eachGroup(func(g *Group) { - for _, option := range g.options { - if option.isSet { - continue - } + p.eachOption(func(c *Command, g *Group, option *Option) { + if option.preventDefault { + return + } - option.clearDefault() - } - }) - }, true) + option.clearDefault() + }) s.checkRequired(p) } @@ -275,12 +309,385 @@ func (p *Parser) ParseArgs(args []string) ([]string, error) { } 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 { - return append([]string{s.arg}, s.args...), p.printError(reterr) + var retargs []string + + if ourErr, ok := reterr.(*Error); !ok || ourErr.Type != ErrHelp { + retargs = append([]string{s.arg}, s.args...) + } else { + retargs = s.args + } + + return retargs, p.printError(reterr) } return s.retargs, nil } + +func (p *parseState) eof() bool { + return len(p.args) == 0 +} + +func (p *parseState) pop() string { + if p.eof() { + return "" + } + + p.arg = p.args[0] + p.args = p.args[1:] + + return p.arg +} + +func (p *parseState) peek() string { + if p.eof() { + return "" + } + + return p.args[0] +} + +func (p *parseState) checkRequired(parser *Parser) error { + c := parser.Command + + var required []*Option + + for c != nil { + c.eachGroup(func(g *Group) { + for _, option := range g.options { + if !option.isSet && option.Required { + required = append(required, option) + } + } + }) + + c = c.Active + } + + if len(required) == 0 { + if len(p.positional) > 0 { + var reqnames []string + + for _, arg := range p.positional { + argRequired := (!arg.isRemaining() && p.command.ArgsRequired) || arg.Required != -1 || arg.RequiredMaximum != -1 + + if !argRequired { + continue + } + + if arg.isRemaining() { + if arg.value.Len() < arg.Required { + var arguments string + + if arg.Required > 1 { + arguments = "arguments, but got only " + fmt.Sprintf("%d", arg.value.Len()) + } else { + arguments = "argument" + } + + 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+"`") + } + } + + if len(reqnames) == 0 { + return nil + } + + var msg string + + if len(reqnames) == 1 { + msg = fmt.Sprintf("the required argument %s was not provided", reqnames[0]) + } else { + msg = fmt.Sprintf("the required arguments %s and %s were not provided", + strings.Join(reqnames[:len(reqnames)-1], ", "), reqnames[len(reqnames)-1]) + } + + p.err = newError(ErrRequired, msg) + return p.err + } + + return nil + } + + names := make([]string, 0, len(required)) + + for _, k := range required { + names = append(names, "`"+k.String()+"'") + } + + sort.Strings(names) + + var msg string + + if len(names) == 1 { + msg = fmt.Sprintf("the required flag %s was not specified", names[0]) + } else { + msg = fmt.Sprintf("the required flags %s and %s were not specified", + strings.Join(names[:len(names)-1], ", "), names[len(names)-1]) + } + + p.err = newError(ErrRequired, msg) + return p.err +} + +func (p *parseState) estimateCommand() error { + commands := p.command.sortedVisibleCommands() + cmdnames := make([]string, len(commands)) + + for i, v := range commands { + cmdnames[i] = v.Name + } + + var msg string + var errtype ErrorType + + if len(p.retargs) != 0 { + c, l := closestChoice(p.retargs[0], cmdnames) + msg = fmt.Sprintf("Unknown command `%s'", p.retargs[0]) + errtype = ErrUnknownCommand + + if float32(l)/float32(len(c)) < 0.5 { + msg = fmt.Sprintf("%s, did you mean `%s'?", msg, c) + } else if len(cmdnames) == 1 { + msg = fmt.Sprintf("%s. You should use the %s command", + msg, + cmdnames[0]) + } else { + msg = fmt.Sprintf("%s. Please specify one command of: %s or %s", + msg, + strings.Join(cmdnames[:len(cmdnames)-1], ", "), + cmdnames[len(cmdnames)-1]) + } + } else { + errtype = ErrCommandRequired + + if len(cmdnames) == 1 { + msg = fmt.Sprintf("Please specify the %s command", cmdnames[0]) + } else { + msg = fmt.Sprintf("Please specify one command of: %s or %s", + strings.Join(cmdnames[:len(cmdnames)-1], ", "), + cmdnames[len(cmdnames)-1]) + } + } + + return newError(errtype, msg) +} + +func (p *Parser) parseOption(s *parseState, name string, option *Option, canarg bool, argument *string) (err error) { + if !option.canArgument() { + if argument != nil { + return newErrorf(ErrNoArgumentForBool, "bool flag `%s' cannot have an argument", option) + } + + err = option.set(nil) + } else if argument != nil || (canarg && !s.eof()) { + var arg string + + if argument != nil { + arg = *argument + } else { + arg = s.pop() + + if argumentIsOption(arg) && !(option.isSignedNumber() && len(arg) > 1 && arg[0] == '-' && arg[1] >= '0' && arg[1] <= '9') { + return newErrorf(ErrExpectedArgument, "expected argument for flag `%s', but got option `%s'", option, arg) + } else if p.Options&PassDoubleDash != 0 && arg == "--" { + return newErrorf(ErrExpectedArgument, "expected argument for flag `%s', but got double dash `--'", option) + } + } + + if option.tag.Get("unquote") != "false" { + arg, err = unquoteIfPossible(arg) + } + + if err == nil { + err = option.set(&arg) + } + } else if option.OptionalArgument { + option.empty() + + for _, v := range option.OptionalValue { + err = option.set(&v) + + if err != nil { + break + } + } + } else { + err = newErrorf(ErrExpectedArgument, "expected argument for flag `%s'", option) + } + + if err != nil { + if _, ok := err.(*Error); !ok { + err = newErrorf(ErrMarshal, "invalid argument for flag `%s' (expected %s): %s", + option, + option.value.Type(), + err.Error()) + } + } + + return err +} + +func (p *Parser) parseLong(s *parseState, name string, argument *string) error { + if option := s.lookup.longNames[name]; option != nil { + // Only long options that are required can consume an argument + // from the argument list + canarg := !option.OptionalArgument + + return p.parseOption(s, name, option, canarg, argument) + } + + return newErrorf(ErrUnknownFlag, "unknown flag `%s'", name) +} + +func (p *Parser) splitShortConcatArg(s *parseState, optname string) (string, *string) { + c, n := utf8.DecodeRuneInString(optname) + + if n == len(optname) { + return optname, nil + } + + first := string(c) + + if option := s.lookup.shortNames[first]; option != nil && option.canArgument() { + arg := optname[n:] + return first, &arg + } + + return optname, nil +} + +func (p *Parser) parseShort(s *parseState, optname string, argument *string) error { + if argument == nil { + optname, argument = p.splitShortConcatArg(s, optname) + } + + for i, c := range optname { + shortname := string(c) + + if option := s.lookup.shortNames[shortname]; option != nil { + // Only the last short argument can consume an argument from + // the arguments list, and only if it's non optional + canarg := (i+utf8.RuneLen(c) == len(optname)) && !option.OptionalArgument + + if err := p.parseOption(s, shortname, option, canarg, argument); err != nil { + return err + } + } else { + return newErrorf(ErrUnknownFlag, "unknown flag `%s'", shortname) + } + + // Only the first option can have a concatted argument, so just + // clear argument here + argument = nil + } + + return nil +} + +func (p *parseState) addArgs(args ...string) error { + for len(p.positional) > 0 && len(args) > 0 { + arg := p.positional[0] + + if err := convert(args[0], arg.value, arg.tag); err != nil { + p.err = err + return err + } + + if !arg.isRemaining() { + p.positional = p.positional[1:] + } + + args = args[1:] + } + + p.retargs = append(p.retargs, args...) + return nil +} + +func (p *Parser) parseNonOption(s *parseState) error { + if len(s.positional) > 0 { + return s.addArgs(s.arg) + } + + 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 { + return err + } + + if err := s.addArgs(s.args...); err != nil { + return err + } + + s.args = []string{} + } else { + return s.addArgs(s.arg) + } + + return nil +} + +func (p *Parser) showBuiltinHelp() error { + var b bytes.Buffer + + p.WriteHelp(&b) + return newError(ErrHelp, b.String()) +} + +func (p *Parser) printError(err error) error { + if err != nil && (p.Options&PrintErrors) != None { + fmt.Fprintln(os.Stderr, err) + } + + return err +} + +func (p *Parser) clearIsSet() { + p.eachCommand(func(c *Command) { + c.eachGroup(func(g *Group) { + for _, option := range g.options { + option.isSet = false + } + }) + }, true) +} diff --git a/vendor/src/github.com/jessevdk/go-flags/parser_private.go b/vendor/src/github.com/jessevdk/go-flags/parser_private.go deleted file mode 100644 index 76be4a75f..000000000 --- a/vendor/src/github.com/jessevdk/go-flags/parser_private.go +++ /dev/null @@ -1,340 +0,0 @@ -package flags - -import ( - "bytes" - "fmt" - "os" - "sort" - "strings" - "unicode/utf8" -) - -type parseState struct { - arg string - args []string - retargs []string - positional []*Arg - err error - - command *Command - lookup lookup -} - -func (p *parseState) eof() bool { - return len(p.args) == 0 -} - -func (p *parseState) pop() string { - if p.eof() { - return "" - } - - p.arg = p.args[0] - p.args = p.args[1:] - - return p.arg -} - -func (p *parseState) peek() string { - if p.eof() { - return "" - } - - return p.args[0] -} - -func (p *parseState) checkRequired(parser *Parser) error { - c := parser.Command - - var required []*Option - - for c != nil { - c.eachGroup(func(g *Group) { - for _, option := range g.options { - if !option.isSet && option.Required { - required = append(required, option) - } - } - }) - - c = c.Active - } - - if len(required) == 0 { - if len(p.positional) > 0 && p.command.ArgsRequired { - var reqnames []string - - for _, arg := range p.positional { - if arg.isRemaining() { - break - } - - reqnames = append(reqnames, "`"+arg.Name+"`") - } - - if len(reqnames) == 0 { - return nil - } - - var msg string - - if len(reqnames) == 1 { - msg = fmt.Sprintf("the required argument %s was not provided", reqnames[0]) - } else { - msg = fmt.Sprintf("the required arguments %s and %s were not provided", - strings.Join(reqnames[:len(reqnames)-1], ", "), reqnames[len(reqnames)-1]) - } - - p.err = newError(ErrRequired, msg) - return p.err - } - - return nil - } - - names := make([]string, 0, len(required)) - - for _, k := range required { - names = append(names, "`"+k.String()+"'") - } - - sort.Strings(names) - - var msg string - - if len(names) == 1 { - msg = fmt.Sprintf("the required flag %s was not specified", names[0]) - } else { - msg = fmt.Sprintf("the required flags %s and %s were not specified", - strings.Join(names[:len(names)-1], ", "), names[len(names)-1]) - } - - p.err = newError(ErrRequired, msg) - return p.err -} - -func (p *parseState) estimateCommand() error { - commands := p.command.sortedCommands() - cmdnames := make([]string, len(commands)) - - for i, v := range commands { - cmdnames[i] = v.Name - } - - var msg string - var errtype ErrorType - - if len(p.retargs) != 0 { - c, l := closestChoice(p.retargs[0], cmdnames) - msg = fmt.Sprintf("Unknown command `%s'", p.retargs[0]) - errtype = ErrUnknownCommand - - if float32(l)/float32(len(c)) < 0.5 { - msg = fmt.Sprintf("%s, did you mean `%s'?", msg, c) - } else if len(cmdnames) == 1 { - msg = fmt.Sprintf("%s. You should use the %s command", - msg, - cmdnames[0]) - } else { - msg = fmt.Sprintf("%s. Please specify one command of: %s or %s", - msg, - strings.Join(cmdnames[:len(cmdnames)-1], ", "), - cmdnames[len(cmdnames)-1]) - } - } else { - errtype = ErrCommandRequired - - if len(cmdnames) == 1 { - msg = fmt.Sprintf("Please specify the %s command", cmdnames[0]) - } else { - msg = fmt.Sprintf("Please specify one command of: %s or %s", - strings.Join(cmdnames[:len(cmdnames)-1], ", "), - cmdnames[len(cmdnames)-1]) - } - } - - return newError(errtype, msg) -} - -func (p *Parser) parseOption(s *parseState, name string, option *Option, canarg bool, argument *string) (err error) { - if !option.canArgument() { - if argument != nil { - return newErrorf(ErrNoArgumentForBool, "bool flag `%s' cannot have an argument", option) - } - - err = option.set(nil) - } else if argument != nil || (canarg && !s.eof()) { - var arg string - - if argument != nil { - arg = *argument - } else { - arg = s.pop() - - if argumentIsOption(arg) { - return newErrorf(ErrExpectedArgument, "expected argument for flag `%s', but got option `%s'", option, arg) - } else if p.Options&PassDoubleDash != 0 && arg == "--" { - return newErrorf(ErrExpectedArgument, "expected argument for flag `%s', but got double dash `--'", option) - } - } - - if option.tag.Get("unquote") != "false" { - arg, err = unquoteIfPossible(arg) - } - - if err == nil { - err = option.set(&arg) - } - } else if option.OptionalArgument { - option.empty() - - for _, v := range option.OptionalValue { - err = option.set(&v) - - if err != nil { - break - } - } - } else { - err = newErrorf(ErrExpectedArgument, "expected argument for flag `%s'", option) - } - - if err != nil { - if _, ok := err.(*Error); !ok { - err = newErrorf(ErrMarshal, "invalid argument for flag `%s' (expected %s): %s", - option, - option.value.Type(), - err.Error()) - } - } - - return err -} - -func (p *Parser) parseLong(s *parseState, name string, argument *string) error { - if option := s.lookup.longNames[name]; option != nil { - // Only long options that are required can consume an argument - // from the argument list - canarg := !option.OptionalArgument - - return p.parseOption(s, name, option, canarg, argument) - } - - return newErrorf(ErrUnknownFlag, "unknown flag `%s'", name) -} - -func (p *Parser) splitShortConcatArg(s *parseState, optname string) (string, *string) { - c, n := utf8.DecodeRuneInString(optname) - - if n == len(optname) { - return optname, nil - } - - first := string(c) - - if option := s.lookup.shortNames[first]; option != nil && option.canArgument() { - arg := optname[n:] - return first, &arg - } - - return optname, nil -} - -func (p *Parser) parseShort(s *parseState, optname string, argument *string) error { - if argument == nil { - optname, argument = p.splitShortConcatArg(s, optname) - } - - for i, c := range optname { - shortname := string(c) - - if option := s.lookup.shortNames[shortname]; option != nil { - // Only the last short argument can consume an argument from - // the arguments list, and only if it's non optional - canarg := (i+utf8.RuneLen(c) == len(optname)) && !option.OptionalArgument - - if err := p.parseOption(s, shortname, option, canarg, argument); err != nil { - return err - } - } else { - return newErrorf(ErrUnknownFlag, "unknown flag `%s'", shortname) - } - - // Only the first option can have a concatted argument, so just - // clear argument here - argument = nil - } - - return nil -} - -func (p *parseState) addArgs(args ...string) error { - for len(p.positional) > 0 && len(args) > 0 { - arg := p.positional[0] - - if err := convert(args[0], arg.value, arg.tag); err != nil { - return err - } - - if !arg.isRemaining() { - p.positional = p.positional[1:] - } - - args = args[1:] - } - - p.retargs = append(p.retargs, args...) - return nil -} - -func (p *Parser) parseNonOption(s *parseState) error { - if len(s.positional) > 0 { - 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 PassAfterNonOption is set then all remaining arguments - // are considered positional - if err := s.addArgs(s.arg); err != nil { - return err - } - - if err := s.addArgs(s.args...); err != nil { - return err - } - - s.args = []string{} - } else { - return s.addArgs(s.arg) - } - - return nil -} - -func (p *Parser) showBuiltinHelp() error { - var b bytes.Buffer - - p.WriteHelp(&b) - return newError(ErrHelp, b.String()) -} - -func (p *Parser) printError(err error) error { - if err != nil && (p.Options&PrintErrors) != None { - fmt.Fprintln(os.Stderr, err) - } - - return err -} - -func (p *Parser) clearIsSet() { - p.eachCommand(func(c *Command) { - c.eachGroup(func(g *Group) { - for _, option := range g.options { - option.isSet = false - } - }) - }, true) -} diff --git a/vendor/src/github.com/jessevdk/go-flags/parser_test.go b/vendor/src/github.com/jessevdk/go-flags/parser_test.go index 579287262..374f21cc5 100644 --- a/vendor/src/github.com/jessevdk/go-flags/parser_test.go +++ b/vendor/src/github.com/jessevdk/go-flags/parser_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "reflect" + "runtime" "strconv" "strings" "testing" @@ -14,6 +15,11 @@ type defaultOptions struct { Int int `long:"i"` IntDefault int `long:"id" default:"1"` + Float64 float64 `long:"f"` + Float64Default float64 `long:"fd" default:"-3.14"` + + NumericFlag bool `short:"3"` + String string `long:"str"` StringDefault string `long:"strd" default:"abc"` StringNotUnquoted string `long:"strnot" unquote:"false"` @@ -41,6 +47,11 @@ func TestDefaults(t *testing.T) { Int: 0, IntDefault: 1, + Float64: 0.0, + Float64Default: -3.14, + + NumericFlag: false, + String: "", StringDefault: "abc", @@ -56,11 +67,16 @@ func TestDefaults(t *testing.T) { }, { msg: "non-zero value arguments, expecting overwritten arguments", - args: []string{"--i=3", "--id=3", "--str=def", "--strd=def", "--t=3ms", "--td=3ms", "--m=c:3", "--md=c:3", "--s=3", "--sd=3"}, + args: []string{"--i=3", "--id=3", "--f=-2.71", "--fd=2.71", "-3", "--str=def", "--strd=def", "--t=3ms", "--td=3ms", "--m=c:3", "--md=c:3", "--s=3", "--sd=3"}, expected: defaultOptions{ Int: 3, IntDefault: 3, + Float64: -2.71, + Float64Default: 2.71, + + NumericFlag: true, + String: "def", StringDefault: "def", @@ -76,11 +92,14 @@ func TestDefaults(t *testing.T) { }, { msg: "zero value arguments, expecting overwritten arguments", - args: []string{"--i=0", "--id=0", "--str", "", "--strd=\"\"", "--t=0ms", "--td=0s", "--m=:0", "--md=:0", "--s=0", "--sd=0"}, + args: []string{"--i=0", "--id=0", "--f=0", "--fd=0", "--str", "", "--strd=\"\"", "--t=0ms", "--td=0s", "--m=:0", "--md=:0", "--s=0", "--sd=0"}, expected: defaultOptions{ Int: 0, IntDefault: 0, + Float64: 0, + Float64Default: 0, + String: "", StringDefault: "", @@ -114,6 +133,18 @@ func TestDefaults(t *testing.T) { } } +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 @@ -184,28 +215,33 @@ func TestUnquoting(t *testing.T) { } } -// 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 } @@ -320,21 +356,21 @@ func TestOptionAsArgument(t *testing.T) { args: []string{"--string-slice", "foobar", "--string-slice", "-o"}, expectError: true, errType: ErrExpectedArgument, - errMsg: "expected argument for flag `--string-slice', but got option `-o'", + errMsg: "expected argument for flag `" + defaultLongOptDelimiter + "string-slice', but got option `-o'", }, { // long option must not be accepted as argument args: []string{"--string-slice", "foobar", "--string-slice", "--other-option"}, expectError: true, errType: ErrExpectedArgument, - errMsg: "expected argument for flag `--string-slice', but got option `--other-option'", + errMsg: "expected argument for flag `" + defaultLongOptDelimiter + "string-slice', but got option `--other-option'", }, { // long option must not be accepted as argument args: []string{"--string-slice", "--"}, expectError: true, errType: ErrExpectedArgument, - errMsg: "expected argument for flag `--string-slice', but got double dash `--'", + errMsg: "expected argument for flag `" + defaultLongOptDelimiter + "string-slice', but got double dash `--'", }, { // quoted and appended option should be accepted as argument (even if it looks like an option) @@ -344,13 +380,48 @@ func TestOptionAsArgument(t *testing.T) { // Accept any single character arguments including '-' args: []string{"--string-slice", "-"}, }, + { + // Do not accept arguments which start with '-' even if the next character is a digit + args: []string{"--string-slice", "-3.14"}, + expectError: true, + errType: ErrExpectedArgument, + errMsg: "expected argument for flag `" + defaultLongOptDelimiter + "string-slice', but got option `-3.14'", + }, + { + // Do not accept arguments which start with '-' if the next character is not a digit + args: []string{"--string-slice", "-character"}, + expectError: true, + errType: ErrExpectedArgument, + errMsg: "expected argument for flag `" + defaultLongOptDelimiter + "string-slice', but got option `-character'", + }, { args: []string{"-o", "-", "-"}, rest: []string{"-", "-"}, }, + { + // Accept arguments which start with '-' if the next character is a digit, for number options only + args: []string{"--int-slice", "-3"}, + }, + { + // Accept arguments which start with '-' if the next character is a digit, for number options only + args: []string{"--int16", "-3"}, + }, + { + // Accept arguments which start with '-' if the next character is a digit, for number options only + args: []string{"--float32", "-3.2"}, + }, + { + // Accept arguments which start with '-' if the next character is a digit, for number options only + args: []string{"--float32ptr", "-3.2"}, + }, } + var opts struct { StringSlice []string `long:"string-slice"` + IntSlice []int `long:"int-slice"` + Int16 int16 `long:"int16"` + Float32 float32 `long:"float32"` + Float32Ptr *float32 `long:"float32ptr"` OtherOption bool `long:"other-option" short:"o"` } @@ -429,3 +500,113 @@ func TestUnknownFlagHandler(t *testing.T) { assertErrorf(t, "Parser should have returned error, but returned nil") } } + +func TestChoices(t *testing.T) { + var opts struct { + Choice string `long:"choose" choice:"v1" choice:"v2"` + } + + assertParseFail(t, ErrInvalidChoice, "Invalid value `invalid' for option `"+defaultLongOptDelimiter+"choose'. Allowed values are: v1 or v2", &opts, "--choose", "invalid") + assertParseSuccess(t, &opts, "--choose", "v2") + assertString(t, opts.Choice, "v2") +} + +func TestEmbedded(t *testing.T) { + type embedded struct { + V bool `short:"v"` + } + var opts struct { + embedded + } + + 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"}) +}