Merge pull request #220 from jessevdk/#218/no-unsafe
Remove unnecessary use of unsafe
diff --git a/completion.go b/completion.go
index 708fa9e..7a7a08b 100644
--- a/completion.go
+++ b/completion.go
@@ -73,27 +73,8 @@
}
}
-func (c *completion) completeOptionNames(names map[string]*Option, prefix string, match string) []Completion {
- n := make([]Completion, 0, len(names))
-
- for k, opt := range names {
- if strings.HasPrefix(k, match) && !opt.Hidden {
- n = append(n, Completion{
- Item: prefix + k,
- Description: opt.Description,
- })
- }
- }
-
- return n
-}
-
-func (c *completion) completeLongNames(s *parseState, prefix string, match string) []Completion {
- return c.completeOptionNames(s.lookup.longNames, prefix, match)
-}
-
-func (c *completion) completeShortNames(s *parseState, prefix string, match string) []Completion {
- if len(match) != 0 {
+func (c *completion) completeOptionNames(s *parseState, prefix string, match string, short bool) []Completion {
+ if short && len(match) != 0 {
return []Completion{
Completion{
Item: prefix + match,
@@ -101,7 +82,42 @@
}
}
- return c.completeOptionNames(s.lookup.shortNames, prefix, match)
+ var results []Completion
+ repeats := map[string]bool{}
+
+ for name, opt := range s.lookup.longNames {
+ if strings.HasPrefix(name, match) && !opt.Hidden {
+ results = append(results, Completion{
+ Item: defaultLongOptDelimiter + name,
+ Description: opt.Description,
+ })
+
+ if short {
+ repeats[string(opt.ShortName)] = true
+ }
+ }
+ }
+
+ if short {
+ for name, opt := range s.lookup.shortNames {
+ if _, exist := repeats[name]; !exist && strings.HasPrefix(name, match) && !opt.Hidden {
+ results = append(results, Completion{
+ Item: string(defaultShortOptDelimiter) + name,
+ Description: opt.Description,
+ })
+ }
+ }
+ }
+
+ return results
+}
+
+func (c *completion) completeNamesForLongPrefix(s *parseState, prefix string, match string) []Completion {
+ return c.completeOptionNames(s, prefix, match, false)
+}
+
+func (c *completion) completeNamesForShortPrefix(s *parseState, prefix string, match string) []Completion {
+ return c.completeOptionNames(s, prefix, match, true)
}
func (c *completion) completeCommands(s *parseState, match string) []Completion {
@@ -120,6 +136,9 @@
}
func (c *completion) completeValue(value reflect.Value, prefix string, match string) []Completion {
+ if value.Kind() == reflect.Slice {
+ value = reflect.New(value.Type().Elem())
+ }
i := value.Interface()
var ret []Completion
@@ -139,16 +158,6 @@
return ret
}
-func (c *completion) completeArg(arg *Arg, prefix string, match string) []Completion {
- if arg.isRemaining() {
- // For remaining positional args (that are parsed into a slice), complete
- // based on the element type.
- return c.completeValue(reflect.New(arg.value.Type().Elem()), prefix, match)
- }
-
- return c.completeValue(arg.value, prefix, match)
-}
-
func (c *completion) complete(args []string) []Completion {
if len(args) == 0 {
args = []string{""}
@@ -244,7 +253,7 @@
if opt := s.lookup.shortNames[sname]; opt != nil && opt.canArgument() {
ret = c.completeValue(opt.value, prefix+sname, optname[n:])
} else {
- ret = c.completeShortNames(s, prefix, optname)
+ ret = c.completeNamesForShortPrefix(s, prefix, optname)
}
} else if argument != nil {
if islong {
@@ -257,13 +266,13 @@
ret = c.completeValue(opt.value, prefix+optname+split, *argument)
}
} else if islong {
- ret = c.completeLongNames(s, prefix, optname)
+ ret = c.completeNamesForLongPrefix(s, prefix, optname)
} else {
- ret = c.completeShortNames(s, prefix, optname)
+ ret = c.completeNamesForShortPrefix(s, prefix, optname)
}
} else if len(s.positional) > 0 {
// Complete for positional argument
- ret = c.completeArg(s.positional[0], "", lastarg)
+ ret = c.completeValue(s.positional[0].value, "", lastarg)
} else if len(s.command.commands) > 0 {
// Complete for command
ret = c.completeCommands(s, lastarg)
diff --git a/completion_test.go b/completion_test.go
index 72ee158..26f70e4 100644
--- a/completion_test.go
+++ b/completion_test.go
@@ -38,6 +38,7 @@
var completionTestOptions struct {
Verbose bool `short:"v" long:"verbose" description:"Verbose messages"`
Debug bool `short:"d" long:"debug" description:"Enable debug"`
+ Info bool `short:"i" description:"Display info"`
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"`
@@ -52,8 +53,13 @@
Positional struct {
Filename []Filename
} `positional-args:"yes"`
+ Extra []Filename `short:"f"`
} `command:"add-multi" description:"add multiple items"`
+ AddMultiCommandFlag struct {
+ Files []Filename `short:"f"`
+ } `command:"add-multi-flag" description:"add multiple items via flags"`
+
RemoveCommand struct {
Other bool `short:"o"`
File Filename `short:"f" long:"filename"`
@@ -82,7 +88,14 @@
{
// Short names
[]string{"-"},
- []string{"-d", "-v"},
+ []string{"--debug", "--required", "--verbose", "--version", "-i"},
+ false,
+ },
+
+ {
+ // Short names full
+ []string{"-i"},
+ []string{"-i"},
false,
},
@@ -122,7 +135,7 @@
{
// Commands
[]string{""},
- []string{"add", "add-multi", "rename", "rm"},
+ []string{"add", "add-multi", "add-multi-flag", "rename", "rm"},
false,
},
@@ -130,10 +143,11 @@
// Commands with descriptions
[]string{""},
[]string{
- "add # add an item",
- "add-multi # add multiple items",
- "rename # rename an item",
- "rm # remove an item",
+ "add # add an item",
+ "add-multi # add multiple items",
+ "add-multi-flag # add multiple items via flags",
+ "rename # rename an item",
+ "rm # remove an item",
},
true,
},
@@ -219,6 +233,12 @@
[]string{"hello universe"},
false,
},
+ {
+ // Multiple flag filename
+ []string{"add-multi-flag", "-f", filepath.Join(completionTestSourcedir, "completion")},
+ completionTestFilename,
+ false,
+ },
}
}
diff --git a/convert.go b/convert.go
index 938c3ac..984aac8 100644
--- a/convert.go
+++ b/convert.go
@@ -154,6 +154,13 @@
func convertUnmarshal(val string, retval reflect.Value) (bool, error) {
if retval.Type().NumMethod() > 0 && retval.CanInterface() {
if unmarshaler, ok := retval.Interface().(Unmarshaler); ok {
+ if retval.IsNil() {
+ retval.Set(reflect.New(retval.Type().Elem()))
+
+ // Re-assign from the new value
+ unmarshaler = retval.Interface().(Unmarshaler)
+ }
+
return true, unmarshaler.UnmarshalFlag(val)
}
}
diff --git a/group.go b/group.go
index 26a42d1..9e057ab 100644
--- a/group.go
+++ b/group.go
@@ -173,6 +173,10 @@
}
}
+func isStringFalsy(s string) bool {
+ return s == "" || s == "false" || s == "no" || s == "0"
+}
+
func (g *Group) scanStruct(realval reflect.Value, sfield *reflect.StructField, handler scanHandler) error {
stype := realval.Type()
@@ -254,10 +258,10 @@
valueName := mtag.Get("value-name")
defaultMask := mtag.Get("default-mask")
- optional := (mtag.Get("optional") != "")
- required := (mtag.Get("required") != "")
+ optional := !isStringFalsy(mtag.Get("optional"))
+ required := !isStringFalsy(mtag.Get("required"))
choices := mtag.GetMany("choice")
- hidden := (mtag.Get("hidden") != "")
+ hidden := !isStringFalsy(mtag.Get("hidden"))
option := &Option{
Description: description,
diff --git a/help.go b/help.go
index 9167847..d380305 100644
--- a/help.go
+++ b/help.go
@@ -216,8 +216,10 @@
var def string
- if len(option.DefaultMask) != 0 && option.DefaultMask != "-" {
- def = option.DefaultMask
+ if len(option.DefaultMask) != 0 {
+ if option.DefaultMask != "-" {
+ def = option.DefaultMask
+ }
} else {
def = option.defaultLiteral
}
diff --git a/help_test.go b/help_test.go
index 01942a2..bb76640 100644
--- a/help_test.go
+++ b/help_test.go
@@ -1,10 +1,12 @@
package flags
import (
+ "bufio"
"bytes"
"fmt"
"os"
"runtime"
+ "strings"
"testing"
"time"
)
@@ -36,8 +38,9 @@
} `group:"Hidden group" hidden:"yes"`
Group struct {
- Opt string `long:"opt" description:"This is a subgroup option"`
- HiddenInsideGroup string `long:"hidden-inside-group" description:"Hidden inside group" hidden:"yes"`
+ Opt string `long:"opt" description:"This is a subgroup option"`
+ HiddenInsideGroup string `long:"hidden-inside-group" description:"Hidden inside group" hidden:"yes"`
+ NotHiddenInsideGroup string `long:"not-hidden-inside-group" description:"Not hidden inside group" hidden:"false"`
Group struct {
Opt string `long:"opt" description:"This is a subsubgroup option"`
@@ -113,6 +116,7 @@
Subgroup:
/sip.opt: This is a subgroup option
+ /sip.not-hidden-inside-group: Not hidden inside group
Subsubgroup:
/sip.sap.opt: This is a subsubgroup option
@@ -159,6 +163,7 @@
Subgroup:
--sip.opt= This is a subgroup option
+ --sip.not-hidden-inside-group= Not hidden inside group
Subsubgroup:
--sip.sap.opt= This is a subsubgroup option
@@ -261,6 +266,9 @@
.TP
\fB\fB\-\-sip.opt\fR\fP
This is a subgroup option
+.TP
+\fB\fB\-\-sip.not-hidden-inside-group\fR\fP
+Not hidden inside group
.SS Subsubgroup
.TP
\fB\fB\-\-sip.sap.opt\fR\fP
@@ -466,3 +474,65 @@
assertDiff(t, got, expected, "wrapped paragraph")
}
+
+func TestHelpDefaultMask(t *testing.T) {
+ var tests = []struct {
+ opts interface{}
+ present string
+ }{
+ {
+ opts: &struct {
+ Value string `short:"v" default:"123" description:"V"`
+ }{},
+ present: "V (default: 123)\n",
+ },
+ {
+ opts: &struct {
+ Value string `short:"v" default:"123" default-mask:"abc" description:"V"`
+ }{},
+ present: "V (default: abc)\n",
+ },
+ {
+ opts: &struct {
+ Value string `short:"v" default:"123" default-mask:"-" description:"V"`
+ }{},
+ present: "V\n",
+ },
+ {
+ opts: &struct {
+ Value string `short:"v" description:"V"`
+ }{Value: "123"},
+ present: "V (default: 123)\n",
+ },
+ {
+ opts: &struct {
+ Value string `short:"v" default-mask:"abc" description:"V"`
+ }{Value: "123"},
+ present: "V (default: abc)\n",
+ },
+ {
+ opts: &struct {
+ Value string `short:"v" default-mask:"-" description:"V"`
+ }{Value: "123"},
+ present: "V\n",
+ },
+ }
+
+ for _, test := range tests {
+ p := NewParser(test.opts, HelpFlag)
+ _, err := p.ParseArgs([]string{"-h"})
+ if flagsErr, ok := err.(*Error); ok && flagsErr.Type == ErrHelp {
+ err = nil
+ }
+ if err != nil {
+ t.Fatalf("Unexpected error: %v", err)
+ }
+ h := &bytes.Buffer{}
+ w := bufio.NewWriter(h)
+ p.writeHelpOption(w, p.FindOptionByShortName('v'), p.getAlignmentInfo())
+ w.Flush()
+ if strings.Index(h.String(), test.present) < 0 {
+ t.Errorf("Not present %q\n%s", test.present, h.String())
+ }
+ }
+}
diff --git a/ini.go b/ini.go
index f9aea2e..e714d3d 100644
--- a/ini.go
+++ b/ini.go
@@ -98,8 +98,6 @@
// information on the ini file format. The returned errors can be of the type
// flags.Error or flags.IniError.
func (i *IniParser) ParseFile(filename string) error {
- i.parser.clearIsSet()
-
ini, err := readIniFromFile(filename)
if err != nil {
@@ -134,8 +132,6 @@
//
// The returned errors can be of the type flags.Error or flags.IniError.
func (i *IniParser) Parse(reader io.Reader) error {
- i.parser.clearIsSet()
-
ini, err := readIni(reader, "")
if err != nil {
diff --git a/ini_test.go b/ini_test.go
index 1d25d45..ad4852e 100644
--- a/ini_test.go
+++ b/ini_test.go
@@ -93,6 +93,9 @@
; This is a subgroup option
Opt =
+; Not hidden inside group
+NotHiddenInsideGroup =
+
[Subsubgroup]
; This is a subsubgroup option
Opt =
@@ -153,6 +156,9 @@
; This is a subgroup option
; Opt =
+; Not hidden inside group
+; NotHiddenInsideGroup =
+
[Subsubgroup]
; This is a subsubgroup option
; Opt =
@@ -211,6 +217,9 @@
; This is a subgroup option
; Opt =
+; Not hidden inside group
+; NotHiddenInsideGroup =
+
[Subsubgroup]
; This is a subsubgroup option
; Opt =
@@ -846,6 +855,29 @@
assertString(t, opts.ValueWithDefaultOverrideCli, "cli-value")
}
+func TestIniRequired(t *testing.T) {
+ var opts struct {
+ Required string `short:"r" required:"yes" description:"required"`
+ Config func(s string) error `long:"config" default:"no-ini-file" no-ini:"true"`
+ }
+
+ p := NewParser(&opts, Default)
+
+ opts.Config = func(s string) error {
+ inip := NewIniParser(p)
+ inip.ParseAsDefaults = true
+ return inip.Parse(strings.NewReader("Required = ini-value\n"))
+ }
+
+ _, err := p.ParseArgs([]string{"-r", "cli-value"})
+
+ if err != nil {
+ t.Fatalf("Failed to parse arguments: %s", err)
+ }
+
+ assertString(t, opts.Required, "cli-value")
+}
+
func TestWriteFile(t *testing.T) {
file, err := ioutil.TempFile("", "")
if err != nil {
diff --git a/option.go b/option.go
index 17a2189..ea09fb4 100644
--- a/option.go
+++ b/option.go
@@ -179,6 +179,11 @@
return option.isSet
}
+// IsSetDefault returns true if option has been set via the default option tag
+func (option *Option) IsSetDefault() bool {
+ return option.isSetDefault
+}
+
// 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.
diff --git a/parser.go b/parser.go
index fd2fd5f..0a7922a 100644
--- a/parser.go
+++ b/parser.go
@@ -479,7 +479,7 @@
msg = fmt.Sprintf("%s. You should use the %s command",
msg,
cmdnames[0])
- } else {
+ } else if len(cmdnames) > 1 {
msg = fmt.Sprintf("%s. Please specify one command of: %s or %s",
msg,
strings.Join(cmdnames[:len(cmdnames)-1], ", "),
@@ -490,7 +490,7 @@
if len(cmdnames) == 1 {
msg = fmt.Sprintf("Please specify the %s command", cmdnames[0])
- } else {
+ } else if len(cmdnames) > 1 {
msg = fmt.Sprintf("Please specify one command of: %s or %s",
strings.Join(cmdnames[:len(cmdnames)-1], ", "),
cmdnames[len(cmdnames)-1])
diff --git a/pointer_test.go b/pointer_test.go
index c5ef60a..dc779c7 100644
--- a/pointer_test.go
+++ b/pointer_test.go
@@ -62,6 +62,64 @@
}
}
+type marshalledString string
+
+func (m *marshalledString) UnmarshalFlag(value string) error {
+ *m = marshalledString(value)
+ return nil
+}
+
+func (m marshalledString) MarshalFlag() (string, error) {
+ return string(m), nil
+}
+
+func TestPointerStringMarshalled(t *testing.T) {
+ var opts = struct {
+ Value *marshalledString `short:"v"`
+ }{}
+
+ ret := assertParseSuccess(t, &opts, "-v", "value")
+
+ assertStringArray(t, ret, []string{})
+
+ if opts.Value == nil {
+ t.Error("Expected value not to be nil")
+ return
+ }
+
+ assertString(t, string(*opts.Value), "value")
+}
+
+type marshalledStruct struct {
+ Value string
+}
+
+func (m *marshalledStruct) UnmarshalFlag(value string) error {
+ m.Value = value
+ return nil
+}
+
+func (m marshalledStruct) MarshalFlag() (string, error) {
+ return m.Value, nil
+}
+
+func TestPointerStructMarshalled(t *testing.T) {
+ var opts = struct {
+ Value *marshalledStruct `short:"v"`
+ }{}
+
+ ret := assertParseSuccess(t, &opts, "-v", "value")
+
+ assertStringArray(t, ret, []string{})
+
+ if opts.Value == nil {
+ t.Error("Expected value not to be nil")
+ return
+ }
+
+ assertString(t, opts.Value.Value, "value")
+}
+
type PointerGroup struct {
Value bool `short:"v"`
}
diff --git a/short_test.go b/short_test.go
index 95712c1..5f4106b 100644
--- a/short_test.go
+++ b/short_test.go
@@ -35,6 +35,22 @@
assertParseFail(t, ErrRequired, fmt.Sprintf("the required flag `%cv' was not specified", defaultShortOptDelimiter), &opts)
}
+func TestShortRequiredFalsy1(t *testing.T) {
+ var opts = struct {
+ Value bool `short:"v" required:"false"`
+ }{}
+
+ assertParseSuccess(t, &opts)
+}
+
+func TestShortRequiredFalsy2(t *testing.T) {
+ var opts = struct {
+ Value bool `short:"v" required:"no"`
+ }{}
+
+ assertParseSuccess(t, &opts)
+}
+
func TestShortMultiConcat(t *testing.T) {
var opts = struct {
V bool `short:"v"`
@@ -192,3 +208,27 @@
assertStringArray(t, ret, []string{"f"})
assertString(t, opts.Value, "value")
}
+
+func TestShortOptionalFalsy1(t *testing.T) {
+ var opts = struct {
+ F []bool `short:"f"`
+ Value string `short:"v" optional:"false" optional-value:"value"`
+ }{}
+
+ ret := assertParseSuccess(t, &opts, "-fv", "f")
+
+ assertStringArray(t, ret, []string{})
+ assertString(t, opts.Value, "f")
+}
+
+func TestShortOptionalFalsy2(t *testing.T) {
+ var opts = struct {
+ F []bool `short:"f"`
+ Value string `short:"v" optional:"no" optional-value:"value"`
+ }{}
+
+ ret := assertParseSuccess(t, &opts, "-fv", "f")
+
+ assertStringArray(t, ret, []string{})
+ assertString(t, opts.Value, "f")
+}