diff -Nru golang-go-flags-0.0~git20141007/command_private.go golang-go-flags-0.0~git20150817/command_private.go --- golang-go-flags-0.0~git20141007/command_private.go 2014-10-03 09:24:45.000000000 +0000 +++ golang-go-flags-0.0~git20150817/command_private.go 2015-08-16 10:05:21.000000000 +0000 @@ -43,7 +43,7 @@ return true, err } - name := m.Get("name") + name := m.Get("positional-arg-name") if len(name) == 0 { name = field.Name @@ -144,6 +144,25 @@ 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 { @@ -156,6 +175,10 @@ } }) + if onlyOptions { + return + } + for _, subcommand := range c.commands { ret.commands[subcommand.Name] = subcommand @@ -163,8 +186,6 @@ ret.commands[a] = subcommand } } - - return ret } func (c *Command) groupByName(name string) *Group { diff -Nru golang-go-flags-0.0~git20141007/command_test.go golang-go-flags-0.0~git20150817/command_test.go --- golang-go-flags-0.0~git20141007/command_test.go 2014-10-03 09:24:45.000000000 +0000 +++ golang-go-flags-0.0~git20150817/command_test.go 2015-08-16 10:05:21.000000000 +0000 @@ -95,7 +95,55 @@ } `command:"cmd"` }{} - assertParseFail(t, ErrUnknownFlag, "unknown flag `v'", &opts, "cmd", "-v", "-g") + assertParseSuccess(t, &opts, "cmd", "-v", "-g") + + if !opts.Value { + t.Errorf("Expected Value to be true") + } + + if !opts.Command.G { + t.Errorf("Expected Command.G to be true") + } +} + +func TestCommandFlagOverride1(t *testing.T) { + var opts = struct { + Value bool `short:"v"` + + Command struct { + Value bool `short:"v"` + } `command:"cmd"` + }{} + + assertParseSuccess(t, &opts, "-v", "cmd") + + if !opts.Value { + t.Errorf("Expected Value to be true") + } + + if opts.Command.Value { + t.Errorf("Expected Command.Value to be false") + } +} + +func TestCommandFlagOverride2(t *testing.T) { + var opts = struct { + Value bool `short:"v"` + + Command struct { + Value bool `short:"v"` + } `command:"cmd"` + }{} + + assertParseSuccess(t, &opts, "cmd", "-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) { diff -Nru golang-go-flags-0.0~git20141007/completion.go golang-go-flags-0.0~git20150817/completion.go --- golang-go-flags-0.0~git20141007/completion.go 2014-10-03 09:24:45.000000000 +0000 +++ golang-go-flags-0.0~git20150817/completion.go 2015-08-16 10:05:21.000000000 +0000 @@ -234,7 +234,7 @@ if opt != nil { // Completion for the argument of 'opt' ret = c.completeValue(opt.value, "", lastarg) - } else if argumentIsOption(lastarg) { + } else if argumentStartsOption(lastarg) { // Complete the option prefix, optname, islong := stripOptionPrefix(lastarg) optname, split, argument := splitOption(prefix, optname, islong) diff -Nru golang-go-flags-0.0~git20141007/convert.go golang-go-flags-0.0~git20150817/convert.go --- golang-go-flags-0.0~git20141007/convert.go 2014-10-03 09:24:45.000000000 +0000 +++ golang-go-flags-0.0~git20150817/convert.go 2015-08-16 10:05:21.000000000 +0000 @@ -312,8 +312,28 @@ return s } +func quoteIfNeededV(s []string) []string { + ret := make([]string, len(s)) + + for i, v := range s { + ret[i] = quoteIfNeeded(v) + } + + return ret +} + +func quoteV(s []string) []string { + ret := make([]string, len(s)) + + for i, v := range s { + ret[i] = strconv.Quote(v) + } + + return ret +} + func unquoteIfPossible(s string) (string, error) { - if s[0] != '"' { + if len(s) == 0 || s[0] != '"' { return s, nil } diff -Nru golang-go-flags-0.0~git20141007/debian/changelog golang-go-flags-0.0~git20150817/debian/changelog --- golang-go-flags-0.0~git20141007/debian/changelog 2014-10-10 16:36:41.000000000 +0000 +++ golang-go-flags-0.0~git20150817/debian/changelog 2015-08-17 15:51:09.000000000 +0000 @@ -1,3 +1,9 @@ +golang-go-flags (0.0~git20150817-0ubuntu1) wily; urgency=low + + * New upstream release + + -- Michael Vogt Mon, 17 Aug 2015 17:51:09 +0200 + golang-go-flags (0.0~git20141007-1) unstable; urgency=medium * New upstream release diff -Nru golang-go-flags-0.0~git20141007/debian/control golang-go-flags-0.0~git20150817/debian/control --- golang-go-flags-0.0~git20141007/debian/control 2014-10-10 16:36:41.000000000 +0000 +++ golang-go-flags-0.0~git20150817/debian/control 2015-08-17 15:51:34.000000000 +0000 @@ -1,7 +1,8 @@ Source: golang-go-flags Section: devel Priority: extra -Maintainer: Sergio Schvezov +Maintainer: Ubuntu Developers +XSBC-Original-Maintainer: Sergio Schvezov Build-Depends: debhelper (>= 9), dh-golang, golang-go Standards-Version: 3.9.5 Homepage: https://github.com/jessevdk/go-flags diff -Nru golang-go-flags-0.0~git20141007/flags.go golang-go-flags-0.0~git20150817/flags.go --- golang-go-flags-0.0~git20141007/flags.go 2014-10-03 09:24:45.000000000 +0000 +++ golang-go-flags-0.0~git20150817/flags.go 2015-08-16 10:05:21.000000000 +0000 @@ -102,7 +102,7 @@ env-delim: the 'env' default value from environment is split into multiple values with the given delimiter string, use with slices and maps (optional) - value-name: the name of the argument value (to be shown in the help, + value-name: the name of the argument value (to be shown in the help) (optional) base: a base (radix) used to convert strings to integer values, the @@ -134,6 +134,10 @@ Positional arguments are optional by default, unless the "required" tag is specified together with the "positional-args" tag (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. @@ -179,12 +183,16 @@ remaining command line arguments. Command structs can have options which become valid to parse after the -command has been specified on the command line. It is currently not valid -to specify options from the parent level of the command after the command -name has occurred. Thus, given a top-level option "-v" and a command "add": - - Valid: ./app -v add - Invalid: ./app add -v +command has been specified on the command line, in addition to the options +of all the parent commands. I.e. considering a -v flag on the parser and an +add command, the following are equivalent: + + ./app -v add + ./app add -v + +However, if the -v flag is defined on the add command, then the first of +the two examples above would fail since the -v flag is not defined before +the add command. Completion diff -Nru golang-go-flags-0.0~git20141007/help_test.go golang-go-flags-0.0~git20150817/help_test.go --- golang-go-flags-0.0~git20141007/help_test.go 2014-10-03 09:24:45.000000000 +0000 +++ golang-go-flags-0.0~git20150817/help_test.go 2015-08-16 10:05:21.000000000 +0000 @@ -15,11 +15,12 @@ PtrSlice []*string `long:"ptrslice" description:"A slice of pointers to string"` EmptyDescription bool `long:"empty-description"` - Default string `long:"default" default:"Some\nvalue" description:"Test default value"` - DefaultArray []string `long:"default-array" default:"Some value" default:"Other\tvalue" description:"Test default array value"` - DefaultMap map[string]string `long:"default-map" default:"some:value" default:"another:value" description:"Testdefault map value"` - 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"` + Default string `long:"default" default:"Some\nvalue" description:"Test default value"` + DefaultArray []string `long:"default-array" default:"Some value" default:"Other\tvalue" description:"Test default array value"` + DefaultMap map[string]string `long:"default-map" default:"some:value" default:"another:value" description:"Testdefault map value"` + 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"` OnlyIni string `ini-name:"only-ini" description:"Option only available in ini"` @@ -41,8 +42,8 @@ } `command:"command" alias:"cm" alias:"cmd" description:"A command"` Args struct { - Filename string `name:"filename" description:"A filename"` - Num int `name:"num" description:"A number"` + Filename string `positional-arg-name:"filename" description:"A filename"` + Number int `positional-arg-name:"num" description:"A number"` } `positional-args:"yes"` } @@ -75,33 +76,34 @@ TestHelp [OPTIONS] [filename] [num] 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%] + /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 Other Options: - /s: A slice of strings (some, value) - /intmap: A map from string to int (a:1) + /s: A slice of strings (some, value) + /intmap: A map from string to int (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) @@ -111,32 +113,36 @@ TestHelp [OPTIONS] [filename] [num] 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] + --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 Other Options: - -s= A slice of strings (some, value) - --intmap= A map from string to int (a:1) + -s= A slice of strings (some, value) + --intmap= A map from string to int (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 + num: A number Available commands: command A command (aliases: cm, cmd) @@ -167,6 +173,14 @@ tt := time.Now() + var envDefaultName string + + if runtime.GOOS == "windows" { + envDefaultName = "%ENV_DEFAULT%" + } else { + envDefaultName = "$ENV_DEFAULT" + } + expected := fmt.Sprintf(`.TH TestMan 1 "%s" .SH NAME TestMan \- Test manpage generation @@ -176,42 +190,45 @@ This is a somewhat \fBlonger\fP description of what this does .SH OPTIONS .TP -\fB-v, --verbose\fP +\fB\fB\-v\fR, \fB\-\-verbose\fR\fP Show verbose debug information .TP -\fB-c\fP +\fB\fB\-c\fR\fP Call phone number .TP -\fB--ptrslice\fP +\fB\fB\-\-ptrslice\fR\fP A slice of pointers to string .TP -\fB--empty-description\fP +\fB\fB\-\-empty-description\fR\fP .TP -\fB--default\fP +\fB\fB\-\-default\fR \fP Test default value .TP -\fB--default-array\fP +\fB\fB\-\-default-array\fR \fP Test default array value .TP -\fB--default-map\fP +\fB\fB\-\-default-map\fR \fP Testdefault map value .TP -\fB--env-default1\fP +\fB\fB\-\-env-default1\fR \fP Test env-default1 value .TP -\fB--env-default2\fP +\fB\fB\-\-env-default2\fR \fP Test env-default2 value .TP -\fB-s\fP +\fB\fB\-\-opt-with-arg-name\fR \fIsomething\fR\fP +Option with named argument +.TP +\fB\fB\-s\fR \fP A slice of strings .TP -\fB--intmap\fP +\fB\fB\-\-intmap\fR \fP A map from string to int .TP -\fB--sip.opt\fP +\fB\fB\-\-sip.opt\fR\fP This is a subgroup option .TP -\fB--sip.sap.opt\fP +\fB\fB\-\-sip.sap.opt\fR\fP This is a subsubgroup option .SH COMMANDS .SS command @@ -225,9 +242,9 @@ \fBAliases\fP: cm, cmd .TP -\fB--extra-verbose\fP +\fB\fB\-\-extra-verbose\fR\fP Use for extra verbosity -`, tt.Format("2 January 2006")) +`, tt.Format("2 January 2006"), envDefaultName) assertDiff(t, got, expected, "man page") } diff -Nru golang-go-flags-0.0~git20141007/ini_private.go golang-go-flags-0.0~git20150817/ini_private.go --- golang-go-flags-0.0~git20141007/ini_private.go 2014-10-03 09:24:45.000000000 +0000 +++ golang-go-flags-0.0~git20150817/ini_private.go 2015-08-16 10:05:21.000000000 +0000 @@ -366,10 +366,7 @@ groups := i.matchingGroups(name) if len(groups) == 0 { - return newError( - ErrUnknownGroup, - fmt.Sprintf("could not find option group `%s'", name), - ) + return newErrorf(ErrUnknownGroup, "could not find option group `%s'", name) } for _, inival := range section { diff -Nru golang-go-flags-0.0~git20141007/ini_test.go golang-go-flags-0.0~git20150817/ini_test.go --- golang-go-flags-0.0~git20141007/ini_test.go 2014-10-03 09:24:45.000000000 +0000 +++ golang-go-flags-0.0~git20150817/ini_test.go 2015-08-16 10:05:21.000000000 +0000 @@ -71,6 +71,9 @@ ; Test env-default2 value EnvDefault2 = env-def +; Option with named argument +OptionWithArgName = + ; Option only available in ini only-ini = @@ -126,6 +129,9 @@ ; Test env-default2 value EnvDefault2 = env-def +; Option with named argument +; OptionWithArgName = + ; Option only available in ini ; only-ini = @@ -178,6 +184,9 @@ ; Test env-default2 value EnvDefault2 = env-def +; Option with named argument +; OptionWithArgName = + ; Option only available in ini ; only-ini = @@ -569,7 +578,8 @@ func TestIniNoIni(t *testing.T) { var opts struct { - Value string `short:"v" long:"value" no-ini:"yes"` + NoValue string `short:"n" long:"novalue" no-ini:"yes"` + Value string `short:"v" long:"value"` } p := NewNamedParser("TestIni", Default) @@ -577,8 +587,10 @@ inip := NewIniParser(p) + // read INI inic := `[Application Options] -value = some value +novalue = some value +value = some other value ` b := strings.NewReader(inic) @@ -594,9 +606,33 @@ t.Errorf("Expected opts.Add.Name to be %d, but got %d", v, iniError.LineNumber) } - if v := "unknown option: value"; iniError.Message != v { + if v := "unknown option: novalue"; iniError.Message != v { t.Errorf("Expected opts.Add.Name to be %s, but got %s", v, iniError.Message) } + + // write INI + opts.NoValue = "some value" + opts.Value = "some other value" + + file, err := ioutil.TempFile("", "") + if err != nil { + t.Fatalf("Cannot create temporary file: %s", err) + } + defer os.Remove(file.Name()) + + err = inip.WriteFile(file.Name(), IniIncludeDefaults) + if err != nil { + t.Fatalf("Could not write ini file: %s", err) + } + + found, err := ioutil.ReadFile(file.Name()) + if err != nil { + t.Fatalf("Could not read written ini file: %s", err) + } + + expected := "[Application Options]\nValue = some other value\n\n" + + assertDiff(t, string(found), expected, "ini content") } func TestIniParse(t *testing.T) { diff -Nru golang-go-flags-0.0~git20141007/man.go golang-go-flags-0.0~git20150817/man.go --- golang-go-flags-0.0~git20141007/man.go 2014-10-03 09:24:45.000000000 +0000 +++ golang-go-flags-0.0~git20150817/man.go 2015-08-16 10:05:21.000000000 +0000 @@ -3,30 +3,35 @@ import ( "fmt" "io" + "runtime" "strings" "time" ) +func manQuote(s string) string { + return strings.Replace(s, "\\", "\\\\", -1) +} + func formatForMan(wr io.Writer, s string) { for { idx := strings.IndexRune(s, '`') if idx < 0 { - fmt.Fprintf(wr, "%s", s) + fmt.Fprintf(wr, "%s", manQuote(s)) break } - fmt.Fprintf(wr, "%s", s[:idx]) + fmt.Fprintf(wr, "%s", manQuote(s[:idx])) s = s[idx+1:] idx = strings.IndexRune(s, '\'') if idx < 0 { - fmt.Fprintf(wr, "%s", s) + fmt.Fprintf(wr, "%s", manQuote(s)) break } - fmt.Fprintf(wr, "\\fB%s\\fP", s[:idx]) + fmt.Fprintf(wr, "\\fB%s\\fP", manQuote(s[:idx])) s = s[idx+1:] } } @@ -42,7 +47,7 @@ fmt.Fprintf(wr, "\\fB") if opt.ShortName != 0 { - fmt.Fprintf(wr, "-%c", opt.ShortName) + fmt.Fprintf(wr, "\\fB\\-%c\\fR", opt.ShortName) } if len(opt.LongName) != 0 { @@ -50,10 +55,33 @@ fmt.Fprintf(wr, ", ") } - fmt.Fprintf(wr, "--%s", opt.LongNameWithNamespace()) + fmt.Fprintf(wr, "\\fB\\-\\-%s\\fR", manQuote(opt.LongNameWithNamespace())) + } + + if len(opt.ValueName) != 0 || opt.OptionalArgument { + if opt.OptionalArgument { + fmt.Fprintf(wr, " [\\fI%s=%s\\fR]", manQuote(opt.ValueName), manQuote(strings.Join(quoteV(opt.OptionalValue), ", "))) + } else { + fmt.Fprintf(wr, " \\fI%s\\fR", manQuote(opt.ValueName)) + } + } + + if len(opt.Default) != 0 { + fmt.Fprintf(wr, " ", manQuote(strings.Join(quoteV(opt.Default), ", "))) + } else if len(opt.EnvDefaultKey) != 0 { + if runtime.GOOS == "windows" { + fmt.Fprintf(wr, " ", manQuote(opt.EnvDefaultKey)) + } else { + fmt.Fprintf(wr, " ", manQuote(opt.EnvDefaultKey)) + } + } + + if opt.Required { + fmt.Fprintf(wr, " (\\fIrequired\\fR)") } fmt.Fprintln(wr, "\\fP") + if len(opt.Description) != 0 { formatForMan(wr, opt.Description) fmt.Fprintln(wr, "") @@ -85,10 +113,10 @@ if len(command.LongDescription) > 0 { fmt.Fprintln(wr, "") - cmdstart := fmt.Sprintf("The %s command", command.Name) + cmdstart := fmt.Sprintf("The %s command", manQuote(command.Name)) if strings.HasPrefix(command.LongDescription, cmdstart) { - fmt.Fprintf(wr, "The \\fI%s\\fP command", command.Name) + fmt.Fprintf(wr, "The \\fI%s\\fP command", manQuote(command.Name)) formatForMan(wr, command.LongDescription[len(cmdstart):]) fmt.Fprintln(wr, "") @@ -113,11 +141,11 @@ } if len(usage) > 0 { - fmt.Fprintf(wr, "\n\\fBUsage\\fP: %s %s\n\n", pre, usage) + fmt.Fprintf(wr, "\n\\fBUsage\\fP: %s %s\n\n", manQuote(pre), manQuote(usage)) } if len(command.Aliases) > 0 { - fmt.Fprintf(wr, "\n\\fBAliases\\fP: %s\n\n", strings.Join(command.Aliases, ", ")) + fmt.Fprintf(wr, "\n\\fBAliases\\fP: %s\n\n", manQuote(strings.Join(command.Aliases, ", "))) } writeManPageOptions(wr, command.Group) @@ -129,9 +157,9 @@ func (p *Parser) WriteManPage(wr io.Writer) { t := time.Now() - fmt.Fprintf(wr, ".TH %s 1 \"%s\"\n", p.Name, t.Format("2 January 2006")) + fmt.Fprintf(wr, ".TH %s 1 \"%s\"\n", manQuote(p.Name), t.Format("2 January 2006")) fmt.Fprintln(wr, ".SH NAME") - fmt.Fprintf(wr, "%s \\- %s\n", p.Name, p.ShortDescription) + fmt.Fprintf(wr, "%s \\- %s\n", manQuote(p.Name), manQuote(p.ShortDescription)) fmt.Fprintln(wr, ".SH SYNOPSIS") usage := p.Usage @@ -140,7 +168,7 @@ usage = "[OPTIONS]" } - fmt.Fprintf(wr, "\\fB%s\\fP %s\n", p.Name, usage) + fmt.Fprintf(wr, "\\fB%s\\fP %s\n", manQuote(p.Name), manQuote(usage)) fmt.Fprintln(wr, ".SH DESCRIPTION") formatForMan(wr, p.LongDescription) diff -Nru golang-go-flags-0.0~git20141007/option.go golang-go-flags-0.0~git20150817/option.go --- golang-go-flags-0.0~git20141007/option.go 2014-10-03 09:24:45.000000000 +0000 +++ golang-go-flags-0.0~git20150817/option.go 2015-08-16 10:05:21.000000000 +0000 @@ -35,7 +35,7 @@ // If true, specifies that the argument to an option flag is optional. // When no argument to the flag is specified on the command line, the - // value of Default will be set in the field this option represents. + // value of OptionalValue will be set in the field this option represents. // This is only valid for non-boolean options. OptionalArgument bool @@ -155,3 +155,8 @@ func (option *Option) Value() interface{} { return option.value.Interface() } + +// IsSet returns true if option has been set +func (option *Option) IsSet() bool { + return option.isSet +} diff -Nru golang-go-flags-0.0~git20141007/optstyle_other.go golang-go-flags-0.0~git20150817/optstyle_other.go --- golang-go-flags-0.0~git20141007/optstyle_other.go 2014-10-03 09:24:45.000000000 +0000 +++ golang-go-flags-0.0~git20150817/optstyle_other.go 2015-08-16 10:05:21.000000000 +0000 @@ -12,10 +12,22 @@ defaultNameArgDelimiter = '=' ) -func argumentIsOption(arg string) bool { +func argumentStartsOption(arg string) bool { return len(arg) > 0 && arg[0] == '-' } +func argumentIsOption(arg string) bool { + if len(arg) > 1 && arg[0] == '-' && arg[1] != '-' { + return true + } + + if len(arg) > 2 && arg[0] == '-' && arg[1] == '-' && arg[2] != '-' { + return true + } + + return false +} + // stripOptionPrefix returns the option without the prefix and whether or // not the option is a long option or not. func stripOptionPrefix(optname string) (prefix string, name string, islong bool) { diff -Nru golang-go-flags-0.0~git20141007/optstyle_windows.go golang-go-flags-0.0~git20150817/optstyle_windows.go --- golang-go-flags-0.0~git20141007/optstyle_windows.go 2014-10-03 09:24:45.000000000 +0000 +++ golang-go-flags-0.0~git20150817/optstyle_windows.go 2015-08-16 10:05:21.000000000 +0000 @@ -12,10 +12,26 @@ defaultNameArgDelimiter = ':' ) +func argumentStartsOption(arg string) bool { + return len(arg) > 0 && (arg[0] == '-' || arg[0] == '/') +} + func argumentIsOption(arg string) bool { // Windows-style options allow front slash for the option // delimiter. - return len(arg) > 0 && (arg[0] == '-' || arg[0] == '/') + if len(arg) > 1 && arg[0] == '/' { + return true + } + + if len(arg) > 1 && arg[0] == '-' && arg[1] != '-' { + return true + } + + if len(arg) > 2 && arg[0] == '-' && arg[1] == '-' && arg[2] != '-' { + return true + } + + return false } // stripOptionPrefix returns the option without the prefix and whether or diff -Nru golang-go-flags-0.0~git20141007/parser.go golang-go-flags-0.0~git20150817/parser.go --- golang-go-flags-0.0~git20141007/parser.go 2014-10-03 09:24:45.000000000 +0000 +++ golang-go-flags-0.0~git20150817/parser.go 2015-08-16 10:05:21.000000000 +0000 @@ -24,9 +24,37 @@ // NamespaceDelimiter separates group namespaces and option long names NamespaceDelimiter string + // UnknownOptionsHandler is a function which gets called when the parser + // encounters an unknown option. The function receives the unknown option + // name, a SplitArgument which specifies its value if set with an argument + // separator, and the remaining command line arguments. + // It should return a new list of remaining arguments to continue parsing, + // or an error to indicate a parse failure. + UnknownOptionHandler func(option string, arg SplitArgument, args []string) ([]string, error) + internalError error } +// SplitArgument represents the argument value of an option that was passed using +// an argument separator. +type SplitArgument interface { + // String returns the option's value as a string, and a boolean indicating + // if the option was present. + Value() (string, bool) +} + +type strArgument struct { + value *string +} + +func (s strArgument) Value() (string, bool) { + if s.value == nil { + return "", false + } + + return *s.value, true +} + // Options provides parser options that change the behavior of the option // parser. type Options uint @@ -204,13 +232,22 @@ ignoreUnknown := (p.Options & IgnoreUnknown) != None parseErr := wrapError(err) - if !(parseErr.Type == ErrUnknownFlag && ignoreUnknown) { + if parseErr.Type != ErrUnknownFlag || (!ignoreUnknown && p.UnknownOptionHandler == nil) { s.err = parseErr break } if ignoreUnknown { s.addArgs(arg) + } else if p.UnknownOptionHandler != nil { + modifiedArgs, err := p.UnknownOptionHandler(optname, strArgument{argument}, s.args) + + if err != nil { + s.err = err + break + } + + s.args = modifiedArgs } } } diff -Nru golang-go-flags-0.0~git20141007/parser_private.go golang-go-flags-0.0~git20150817/parser_private.go --- golang-go-flags-0.0~git20141007/parser_private.go 2014-10-03 09:24:45.000000000 +0000 +++ golang-go-flags-0.0~git20150817/parser_private.go 2015-08-16 10:05:21.000000000 +0000 @@ -159,8 +159,7 @@ func (p *Parser) parseOption(s *parseState, name string, option *Option, canarg bool, argument *string) (err error) { if !option.canArgument() { if argument != nil { - msg := fmt.Sprintf("bool flag `%s' cannot have an argument", option) - return newError(ErrNoArgumentForBool, msg) + return newErrorf(ErrNoArgumentForBool, "bool flag `%s' cannot have an argument", option) } err = option.set(nil) @@ -171,6 +170,12 @@ 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" { @@ -191,18 +196,15 @@ } } } else { - msg := fmt.Sprintf("expected argument for flag `%s'", option) - err = newError(ErrExpectedArgument, msg) + err = newErrorf(ErrExpectedArgument, "expected argument for flag `%s'", option) } if err != nil { if _, ok := err.(*Error); !ok { - msg := fmt.Sprintf("invalid argument for flag `%s' (expected %s): %s", + err = newErrorf(ErrMarshal, "invalid argument for flag `%s' (expected %s): %s", option, option.value.Type(), err.Error()) - - err = newError(ErrMarshal, msg) } } @@ -218,7 +220,7 @@ return p.parseOption(s, name, option, canarg, argument) } - return newError(ErrUnknownFlag, fmt.Sprintf("unknown flag `%s'", name)) + return newErrorf(ErrUnknownFlag, "unknown flag `%s'", name) } func (p *Parser) splitShortConcatArg(s *parseState, optname string) (string, *string) { @@ -255,7 +257,7 @@ return err } } else { - return newError(ErrUnknownFlag, fmt.Sprintf("unknown flag `%s'", shortname)) + return newErrorf(ErrUnknownFlag, "unknown flag `%s'", shortname) } // Only the first option can have a concatted argument, so just diff -Nru golang-go-flags-0.0~git20141007/parser_test.go golang-go-flags-0.0~git20150817/parser_test.go --- golang-go-flags-0.0~git20141007/parser_test.go 2014-10-03 09:24:45.000000000 +0000 +++ golang-go-flags-0.0~git20150817/parser_test.go 2015-08-16 10:05:21.000000000 +0000 @@ -1,6 +1,7 @@ package flags import ( + "fmt" "os" "reflect" "strconv" @@ -13,6 +14,11 @@ 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" default:"false"` + String string `long:"str"` StringDefault string `long:"strd" default:"abc"` StringNotUnquoted string `long:"strnot" unquote:"false"` @@ -40,6 +46,11 @@ Int: 0, IntDefault: 1, + Float64: 0.0, + Float64Default: -3.14, + + NumericFlag: false, + String: "", StringDefault: "abc", @@ -55,11 +66,16 @@ }, { 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", @@ -75,11 +91,14 @@ }, { 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: "", @@ -305,3 +324,140 @@ } } } + +func TestOptionAsArgument(t *testing.T) { + var tests = []struct { + args []string + expectError bool + errType ErrorType + errMsg string + rest []string + }{ + { + // short option must not be accepted as argument + args: []string{"--string-slice", "foobar", "--string-slice", "-o"}, + expectError: true, + errType: ErrExpectedArgument, + errMsg: "expected argument for flag `--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'", + }, + { + // 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 `--'", + }, + { + // quoted and appended option should be accepted as argument (even if it looks like an option) + args: []string{"--string-slice", "foobar", "--string-slice=\"--other-option\""}, + }, + { + // 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 `--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 `--string-slice', but got option `-character'", + }, + { + args: []string{"-o", "-", "-"}, + rest: []string{"-", "-"}, + }, + } + var opts struct { + StringSlice []string `long:"string-slice"` + OtherOption bool `long:"other-option" short:"o"` + } + + for _, test := range tests { + if test.expectError { + assertParseFail(t, test.errType, test.errMsg, &opts, test.args...) + } else { + args := assertParseSuccess(t, &opts, test.args...) + + assertStringArray(t, args, test.rest) + } + } +} + +func TestUnknownFlagHandler(t *testing.T) { + + var opts struct { + Flag1 string `long:"flag1"` + Flag2 string `long:"flag2"` + } + + p := NewParser(&opts, None) + + var unknownFlag1 string + var unknownFlag2 bool + var unknownFlag3 string + + // Set up a callback to intercept unknown options during parsing + p.UnknownOptionHandler = func(option string, arg SplitArgument, args []string) ([]string, error) { + if option == "unknownFlag1" { + if argValue, ok := arg.Value(); ok { + unknownFlag1 = argValue + return args, nil + } + // consume a value from remaining args list + unknownFlag1 = args[0] + return args[1:], nil + } else if option == "unknownFlag2" { + // treat this one as a bool switch, don't consume any args + unknownFlag2 = true + return args, nil + } else if option == "unknownFlag3" { + if argValue, ok := arg.Value(); ok { + unknownFlag3 = argValue + return args, nil + } + // consume a value from remaining args list + unknownFlag3 = args[0] + return args[1:], nil + } + + return args, fmt.Errorf("Unknown flag: %v", option) + } + + // Parse args containing some unknown flags, verify that + // our callback can handle all of them + _, err := p.ParseArgs([]string{"--flag1=stuff", "--unknownFlag1", "blah", "--unknownFlag2", "--unknownFlag3=baz", "--flag2=foo"}) + + if err != nil { + assertErrorf(t, "Parser returned unexpected error %v", err) + } + + assertString(t, opts.Flag1, "stuff") + assertString(t, opts.Flag2, "foo") + assertString(t, unknownFlag1, "blah") + assertString(t, unknownFlag3, "baz") + + if !unknownFlag2 { + assertErrorf(t, "Flag should have been set by unknown handler, but had value: %v", unknownFlag2) + } + + // Parse args with unknown flags that callback doesn't handle, verify it returns error + _, err = p.ParseArgs([]string{"--flag1=stuff", "--unknownFlagX", "blah", "--flag2=foo"}) + + if err == nil { + assertErrorf(t, "Parser should have returned error, but returned nil") + } +} diff -Nru golang-go-flags-0.0~git20141007/.travis.yml golang-go-flags-0.0~git20150817/.travis.yml --- golang-go-flags-0.0~git20141007/.travis.yml 2014-10-03 09:24:45.000000000 +0000 +++ golang-go-flags-0.0~git20150817/.travis.yml 2015-08-16 10:05:21.000000000 +0000 @@ -6,12 +6,12 @@ - go build -v ./... # linting - - go get code.google.com/p/go.tools/cmd/vet + - go get golang.org/x/tools/cmd/vet - go get github.com/golang/lint - go install github.com/golang/lint/golint # code coverage - - go get code.google.com/p/go.tools/cmd/cover + - go get golang.org/x/tools/cmd/cover - go get github.com/onsi/ginkgo/ginkgo - go get github.com/modocache/gover - if [ "$TRAVIS_SECURE_ENV_VARS" = "true" ]; then go get github.com/mattn/goveralls; fi