summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKenneth Shaw <[email protected]>2017-03-03 19:12:17 +0700
committerKenneth Shaw <[email protected]>2017-03-04 09:13:12 +0700
commitd4c2b35b2ef5b67c3ec6f904cea0dff806d51e2c (patch)
tree7c7b0944f564159efb512ceca0c562c475249960
parent8488cf10ceffaa0c78f84ce82b38374c3e546940 (diff)
Adding separate tag option
As outlined in #49, there is a need to mimic the behavior of other applications by interweaving positional and non-positional parameters. This change adds the 'separate' option that will force a arg of type []string to only read the next supplied value. For example, when dealing with the following arg type: var MyArgs struct { Pos []string `arg:"positional"` Separate []string `arg:"-s,separate"` } This commit will parse the following command line: ./app pos1 pos2 -s=separate1 -s=separate2 pos3 -s=separate3 pos4 Such that MyArgs.Pos will be [pos1 pos2 pos3 pos4] and MyArgs.Separate will be [separate1 separate2 separate3]. Unit tests for the above have also been written and are included in this commit, as well as the addition of a section to README.md and an example func in example_test.go. Fixes #49
-rw-r--r--README.md18
-rw-r--r--example_test.go15
-rw-r--r--parse.go14
-rw-r--r--parse_test.go54
4 files changed, 96 insertions, 5 deletions
diff --git a/README.md b/README.md
index 8b6f3d8..21a8789 100644
--- a/README.md
+++ b/README.md
@@ -108,7 +108,7 @@ arg.MustParse(&args)
```shell
$ ./example -h
-usage: [--verbose] [--dataset DATASET] [--optimize OPTIMIZE] [--help] INPUT [OUTPUT [OUTPUT ...]]
+usage: [--verbose] [--dataset DATASET] [--optimize OPTIMIZE] [--help] INPUT [OUTPUT [OUTPUT ...]]
positional arguments:
input
@@ -148,6 +148,22 @@ fmt.Printf("Fetching the following IDs from %s: %q", args.Database, args.IDs)
Fetching the following IDs from foo: [1 2 3]
```
+### Arguments that can be specified multiple times, mixed with positionals
+```go
+var args struct {
+ Commands []string `arg:"-c,separate"`
+ Files []string `arg:"-f,separate"`
+ Databases []string `arg:"positional"`
+}
+```
+
+```shell
+./example -c cmd1 db1 -f file1 db2 -c cmd2 -f file2 -f file3 db3 -c cmd3
+Commands: [cmd1 cmd2 cmd3]
+Files [file1 file2 file3]
+Databases [db1 db2 db3]
+```
+
### Custom validation
```go
var args struct {
diff --git a/example_test.go b/example_test.go
index 6fb5197..c34effa 100644
--- a/example_test.go
+++ b/example_test.go
@@ -71,6 +71,21 @@ func Example_multipleValues() {
fmt.Printf("Fetching the following IDs from %s: %q", args.Database, args.IDs)
}
+// This eample demonstrates multiple value arguments that can be mixed with
+// other arguments.
+func Example_multipleMixed() {
+ os.Args = []string{"./example", "-c", "cmd1", "db1", "-f", "file1", "db2", "-c", "cmd2", "-f", "file2", "-f", "file3", "db3", "-c", "cmd3"}
+ var args struct {
+ Commands []string `arg:"-c,separate"`
+ Files []string `arg:"-f,separate"`
+ Databases []string `arg:"positional"`
+ }
+ MustParse(&args)
+ fmt.Println("Commands:", args.Commands)
+ fmt.Println("Files", args.Files)
+ fmt.Println("Databases", args.Databases)
+}
+
// This example shows the usage string generated by go-arg
func Example_usageString() {
// These are the args you would pass in on the command line
diff --git a/parse.go b/parse.go
index 60f35ee..4f62c60 100644
--- a/parse.go
+++ b/parse.go
@@ -20,6 +20,7 @@ type spec struct {
multiple bool
required bool
positional bool
+ separate bool
help string
env string
wasPresent bool
@@ -189,6 +190,8 @@ func NewParser(config Config, dests ...interface{}) (*Parser, error) {
spec.required = true
case key == "positional":
spec.positional = true
+ case key == "separate":
+ spec.separate = true
case key == "help":
spec.help = value
case key == "env":
@@ -314,11 +317,14 @@ func process(specs []*spec, args []string) error {
for i+1 < len(args) && !isFlag(args[i+1]) {
values = append(values, args[i+1])
i++
+ if spec.separate {
+ break
+ }
}
} else {
values = append(values, value)
}
- err := setSlice(spec.dest, values)
+ err := setSlice(spec.dest, values, !spec.separate)
if err != nil {
return fmt.Errorf("error processing %s: %v", arg, err)
}
@@ -350,7 +356,7 @@ func process(specs []*spec, args []string) error {
for _, spec := range specs {
if spec.positional {
if spec.multiple {
- err := setSlice(spec.dest, positionals)
+ err := setSlice(spec.dest, positionals, true)
if err != nil {
return fmt.Errorf("error processing %s: %v", spec.long, err)
}
@@ -388,7 +394,7 @@ func validate(spec []*spec) error {
}
// parse a value as the appropriate type and store it in the struct
-func setSlice(dest reflect.Value, values []string) error {
+func setSlice(dest reflect.Value, values []string, trunc bool) error {
if !dest.CanSet() {
return fmt.Errorf("field is not writable")
}
@@ -401,7 +407,7 @@ func setSlice(dest reflect.Value, values []string) error {
}
// Truncate the dest slice in case default values exist
- if !dest.IsNil() {
+ if trunc && !dest.IsNil() {
dest.SetLen(0)
}
diff --git a/parse_test.go b/parse_test.go
index 91e17bb..267e57c 100644
--- a/parse_test.go
+++ b/parse_test.go
@@ -739,3 +739,57 @@ func TestHyphenInMultiPositional(t *testing.T) {
require.NoError(t, err)
assert.Equal(t, []string{"---", "x", "-", "y"}, args.Foo)
}
+
+func TestSeparate(t *testing.T) {
+ for _, val := range []string{"-f one", "-f=one", "--foo one", "--foo=one"} {
+ var args struct {
+ Foo []string `arg:"--foo,-f,separate"`
+ }
+
+ err := parse(val, &args)
+ require.NoError(t, err)
+ assert.Equal(t, []string{"one"}, args.Foo)
+ }
+}
+
+func TestSeparateWithDefault(t *testing.T) {
+ args := struct {
+ Foo []string `arg:"--foo,-f,separate"`
+ }{
+ Foo: []string{"default"},
+ }
+
+ err := parse("-f one -f=two", &args)
+ require.NoError(t, err)
+ assert.Equal(t, []string{"default", "one", "two"}, args.Foo)
+}
+
+func TestSeparateWithPositional(t *testing.T) {
+ var args struct {
+ Foo []string `arg:"--foo,-f,separate"`
+ Bar string `arg:"positional"`
+ Moo string `arg:"positional"`
+ }
+
+ err := parse("zzz --foo one -f=two --foo=three -f four aaa", &args)
+ require.NoError(t, err)
+ assert.Equal(t, []string{"one", "two", "three", "four"}, args.Foo)
+ assert.Equal(t, "zzz", args.Bar)
+ assert.Equal(t, "aaa", args.Moo)
+}
+
+func TestSeparatePositionalInterweaved(t *testing.T) {
+ var args struct {
+ Foo []string `arg:"--foo,-f,separate"`
+ Bar []string `arg:"--bar,-b,separate"`
+ Pre string `arg:"positional"`
+ Post []string `arg:"positional"`
+ }
+
+ err := parse("zzz -f foo1 -b=bar1 --foo=foo2 -b bar2 post1 -b bar3 post2 post3", &args)
+ require.NoError(t, err)
+ assert.Equal(t, []string{"foo1", "foo2"}, args.Foo)
+ assert.Equal(t, []string{"bar1", "bar2", "bar3"}, args.Bar)
+ assert.Equal(t, "zzz", args.Pre)
+ assert.Equal(t, []string{"post1", "post2", "post3"}, args.Post)
+}