diff --git a/README.md b/README.md index 46b97ec..3f9223c 100644 --- a/README.md +++ b/README.md @@ -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) +}