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
This commit is contained in:
parent
8488cf10ce
commit
d4c2b35b2e
18
README.md
18
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 {
|
||||
|
|
|
@ -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
|
||||
|
|
14
parse.go
14
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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue