git-subtree-dir: vendor/github.com/alexflint/go-arg
git-subtree-split: 53364a0be4
master
| @ -0,0 +1,24 @@ | |||
| # Compiled Object files, Static and Dynamic libs (Shared Objects) | |||
| *.o | |||
| *.a | |||
| *.so | |||
| # Folders | |||
| _obj | |||
| _test | |||
| # Architecture specific extensions/prefixes | |||
| *.[568vq] | |||
| [568vq].out | |||
| *.cgo1.go | |||
| *.cgo2.c | |||
| _cgo_defun.c | |||
| _cgo_gotypes.go | |||
| _cgo_export.* | |||
| _testmain.go | |||
| *.exe | |||
| *.test | |||
| *.prof | |||
| @ -0,0 +1,9 @@ | |||
| language: go | |||
| go: | |||
| - tip | |||
| before_install: | |||
| - go get github.com/axw/gocov/gocov | |||
| - go get github.com/mattn/goveralls | |||
| - if ! go get github.com/golang/tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi | |||
| script: | |||
| - $HOME/gopath/bin/goveralls -service=travis-ci | |||
| @ -0,0 +1,24 @@ | |||
| Copyright (c) 2015, Alex Flint | |||
| All rights reserved. | |||
| Redistribution and use in source and binary forms, with or without | |||
| modification, are permitted provided that the following conditions are met: | |||
| * Redistributions of source code must retain the above copyright notice, this | |||
| list of conditions and the following disclaimer. | |||
| * Redistributions in binary form must reproduce the above copyright notice, | |||
| this list of conditions and the following disclaimer in the documentation | |||
| and/or other materials provided with the distribution. | |||
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |||
| AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||
| IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |||
| DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |||
| FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |||
| DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |||
| SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |||
| CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |||
| OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |||
| OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||
| @ -0,0 +1,129 @@ | |||
| [](https://godoc.org/github.com/alexflint/go-arg) | |||
| [](https://travis-ci.org/alexflint/go-arg) | |||
| [](https://coveralls.io/github/alexflint/go-arg?branch=master) | |||
| ## Structured argument parsing for Go | |||
| Declare the command line arguments your program accepts by defining a struct. | |||
| ```go | |||
| var args struct { | |||
| Foo string | |||
| Bar bool | |||
| } | |||
| arg.MustParse(&args) | |||
| fmt.Println(args.Foo, args.Bar) | |||
| ``` | |||
| ```shell | |||
| $ ./example --foo=hello --bar | |||
| hello true | |||
| ``` | |||
| ### Required arguments | |||
| ```go | |||
| var args struct { | |||
| Foo string `arg:"required"` | |||
| Bar bool | |||
| } | |||
| arg.MustParse(&args) | |||
| ``` | |||
| ```shell | |||
| $ ./example | |||
| usage: example --foo FOO [--bar] | |||
| error: --foo is required | |||
| ``` | |||
| ### Positional arguments | |||
| ```go | |||
| var args struct { | |||
| Input string `arg:"positional"` | |||
| Output []string `arg:"positional"` | |||
| } | |||
| arg.MustParse(&args) | |||
| fmt.Println("Input:", args.Input) | |||
| fmt.Println("Output:", args.Output) | |||
| ``` | |||
| ``` | |||
| $ ./example src.txt x.out y.out z.out | |||
| Input: src.txt | |||
| Output: [x.out y.out z.out] | |||
| ``` | |||
| ### Usage strings | |||
| ```go | |||
| var args struct { | |||
| Input string `arg:"positional"` | |||
| Output []string `arg:"positional"` | |||
| Verbose bool `arg:"-v,help:verbosity level"` | |||
| Dataset string `arg:"help:dataset to use"` | |||
| Optimize int `arg:"-O,help:optimization level"` | |||
| } | |||
| arg.MustParse(&args) | |||
| ``` | |||
| ```shell | |||
| $ ./example -h | |||
| usage: [--verbose] [--dataset DATASET] [--optimize OPTIMIZE] [--help] INPUT [OUTPUT [OUTPUT ...]] | |||
| positional arguments: | |||
| input | |||
| output | |||
| options: | |||
| --verbose, -v verbosity level | |||
| --dataset DATASET dataset to use | |||
| --optimize OPTIMIZE, -O OPTIMIZE | |||
| optimization level | |||
| --help, -h print this help message | |||
| ``` | |||
| ### Default values | |||
| ```go | |||
| var args struct { | |||
| Foo string | |||
| Bar bool | |||
| } | |||
| args.Foo = "default value" | |||
| arg.MustParse(&args) | |||
| ``` | |||
| ### Arguments with multiple values | |||
| ```go | |||
| var args struct { | |||
| Database string | |||
| IDs []int64 | |||
| } | |||
| arg.MustParse(&args) | |||
| fmt.Printf("Fetching the following IDs from %s: %q", args.Database, args.IDs) | |||
| ``` | |||
| ```shell | |||
| ./example -database foo -ids 1 2 3 | |||
| Fetching the following IDs from foo: [1 2 3] | |||
| ``` | |||
| ### Installation | |||
| ```shell | |||
| go get github.com/alexflint/go-arg | |||
| ``` | |||
| ### Documentation | |||
| https://godoc.org/github.com/alexflint/go-arg | |||
| ### Rationale | |||
| There are many command line argument parsing libraries for Go, including one in the standard library, so why build another? | |||
| The shortcomings of the `flag` library that ships in the standard library are well known. Positional arguments must preceed options, so `./prog x --foo=1` does what you expect but `./prog --foo=1 x` does not. Arguments cannot have both long (`--foo`) and short (`-f`) forms. | |||
| Many third-party argument parsing libraries are geared for writing sophisticated command line interfaces. The excellent `codegangsta/cli` is perfect for working with multiple sub-commands and nested flags, but is probably overkill for a simple script with a handful of flags. | |||
| The main idea behind `go-arg` is that Go already has an excellent way to describe data structures using Go structs, so there is no need to develop more levels of abstraction on top of this. Instead of one API to specify which arguments your program accepts, and then another API to get the values of those arguments, why not replace both with a single struct? | |||
| @ -0,0 +1,87 @@ | |||
| package arg | |||
| import ( | |||
| "fmt" | |||
| "os" | |||
| ) | |||
| // This example demonstrates basic usage | |||
| func Example_Basic() { | |||
| // These are the args you would pass in on the command line | |||
| os.Args = []string{"./example", "--foo=hello", "--bar"} | |||
| var args struct { | |||
| Foo string | |||
| Bar bool | |||
| } | |||
| MustParse(&args) | |||
| fmt.Println(args.Foo, args.Bar) | |||
| } | |||
| // This example demonstrates arguments that have default values | |||
| func Example_DefaultValues() { | |||
| // These are the args you would pass in on the command line | |||
| os.Args = []string{"--help"} | |||
| var args struct { | |||
| Foo string | |||
| Bar bool | |||
| } | |||
| args.Foo = "default value" | |||
| MustParse(&args) | |||
| fmt.Println(args.Foo, args.Bar) | |||
| } | |||
| // This example demonstrates arguments that are required | |||
| func Example_RequiredArguments() { | |||
| // These are the args you would pass in on the command line | |||
| os.Args = []string{"--foo=1", "--bar"} | |||
| var args struct { | |||
| Foo string `arg:"required"` | |||
| Bar bool | |||
| } | |||
| MustParse(&args) | |||
| } | |||
| // This example demonstrates positional arguments | |||
| func Example_PositionalArguments() { | |||
| // These are the args you would pass in on the command line | |||
| os.Args = []string{"./example", "in", "out1", "out2", "out3"} | |||
| var args struct { | |||
| Input string `arg:"positional"` | |||
| Output []string `arg:"positional"` | |||
| } | |||
| MustParse(&args) | |||
| fmt.Println("Input:", args.Input) | |||
| fmt.Println("Output:", args.Output) | |||
| } | |||
| // This example demonstrates arguments that have multiple values | |||
| func Example_MultipleValues() { | |||
| // The args you would pass in on the command line | |||
| os.Args = []string{"--help"} | |||
| var args struct { | |||
| Database string | |||
| IDs []int64 | |||
| } | |||
| MustParse(&args) | |||
| fmt.Printf("Fetching the following IDs from %s: %q", args.Database, args.IDs) | |||
| } | |||
| // 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 | |||
| os.Args = []string{"--help"} | |||
| var args struct { | |||
| Input string `arg:"positional"` | |||
| Output []string `arg:"positional"` | |||
| Verbose bool `arg:"-v,help:verbosity level"` | |||
| Dataset string `arg:"help:dataset to use"` | |||
| Optimize int `arg:"-O,help:optimization level"` | |||
| } | |||
| MustParse(&args) | |||
| } | |||
| @ -0,0 +1,381 @@ | |||
| // Package arg parses command line arguments using the fields from a struct. | |||
| // | |||
| // For example, | |||
| // | |||
| // var args struct { | |||
| // Iter int | |||
| // Debug bool | |||
| // } | |||
| // arg.MustParse(&args) | |||
| // | |||
| // defines two command line arguments, which can be set using any of | |||
| // | |||
| // ./example --iter=1 --debug // debug is a boolean flag so its value is set to true | |||
| // ./example -iter 1 // debug defaults to its zero value (false) | |||
| // ./example --debug=true // iter defaults to its zero value (zero) | |||
| // | |||
| // The fastest way to see how to use go-arg is to read the examples below. | |||
| // | |||
| // Fields can be bool, string, any float type, or any signed or unsigned integer type. | |||
| // They can also be slices of any of the above, or slices of pointers to any of the above. | |||
| // | |||
| // Tags can be specified using the `arg` package name: | |||
| // | |||
| // var args struct { | |||
| // Input string `arg:"positional"` | |||
| // Log string `arg:"positional,required"` | |||
| // Debug bool `arg:"-d,help:turn on debug mode"` | |||
| // RealMode bool `arg:"--real" | |||
| // Wr io.Writer `arg:"-"` | |||
| // } | |||
| // | |||
| // The valid tag strings are `positional`, `required`, and `help`. Further, any tag string | |||
| // that starts with a single hyphen is the short form for an argument (e.g. `./example -d`), | |||
| // and any tag string that starts with two hyphens is the long form for the argument | |||
| // (instead of the field name). Fields can be excluded from processing with `arg:"-"`. | |||
| package arg | |||
| import ( | |||
| "errors" | |||
| "fmt" | |||
| "os" | |||
| "reflect" | |||
| "strconv" | |||
| "strings" | |||
| ) | |||
| // spec represents a command line option | |||
| type spec struct { | |||
| dest reflect.Value | |||
| long string | |||
| short string | |||
| multiple bool | |||
| required bool | |||
| positional bool | |||
| help string | |||
| wasPresent bool | |||
| } | |||
| // ErrHelp indicates that -h or --help were provided | |||
| var ErrHelp = errors.New("help requested by user") | |||
| // MustParse processes command line arguments and exits upon failure | |||
| func MustParse(dest ...interface{}) { | |||
| p, err := NewParser(dest...) | |||
| if err != nil { | |||
| fmt.Println(err) | |||
| os.Exit(-1) | |||
| } | |||
| err = p.Parse(os.Args[1:]) | |||
| if err == ErrHelp { | |||
| p.WriteHelp(os.Stdout) | |||
| os.Exit(0) | |||
| } | |||
| if err != nil { | |||
| p.Fail(err.Error()) | |||
| } | |||
| } | |||
| // Parse processes command line arguments and stores them in dest | |||
| func Parse(dest ...interface{}) error { | |||
| p, err := NewParser(dest...) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| return p.Parse(os.Args[1:]) | |||
| } | |||
| // Parser represents a set of command line options with destination values | |||
| type Parser struct { | |||
| spec []*spec | |||
| } | |||
| // NewParser constructs a parser from a list of destination structs | |||
| func NewParser(dests ...interface{}) (*Parser, error) { | |||
| var specs []*spec | |||
| for _, dest := range dests { | |||
| v := reflect.ValueOf(dest) | |||
| if v.Kind() != reflect.Ptr { | |||
| panic(fmt.Sprintf("%s is not a pointer (did you forget an ampersand?)", v.Type())) | |||
| } | |||
| v = v.Elem() | |||
| if v.Kind() != reflect.Struct { | |||
| panic(fmt.Sprintf("%T is not a struct pointer", dest)) | |||
| } | |||
| t := v.Type() | |||
| for i := 0; i < t.NumField(); i++ { | |||
| // Check for the ignore switch in the tag | |||
| field := t.Field(i) | |||
| tag := field.Tag.Get("arg") | |||
| if tag == "-" { | |||
| continue | |||
| } | |||
| spec := spec{ | |||
| long: strings.ToLower(field.Name), | |||
| dest: v.Field(i), | |||
| } | |||
| // Get the scalar type for this field | |||
| scalarType := field.Type | |||
| if scalarType.Kind() == reflect.Slice { | |||
| spec.multiple = true | |||
| scalarType = scalarType.Elem() | |||
| if scalarType.Kind() == reflect.Ptr { | |||
| scalarType = scalarType.Elem() | |||
| } | |||
| } | |||
| // Check for unsupported types | |||
| switch scalarType.Kind() { | |||
| case reflect.Array, reflect.Chan, reflect.Func, reflect.Interface, | |||
| reflect.Map, reflect.Ptr, reflect.Struct, | |||
| reflect.Complex64, reflect.Complex128: | |||
| return nil, fmt.Errorf("%s.%s: %s fields are not supported", t.Name(), field.Name, scalarType.Kind()) | |||
| } | |||
| // Look at the tag | |||
| if tag != "" { | |||
| for _, key := range strings.Split(tag, ",") { | |||
| var value string | |||
| if pos := strings.Index(key, ":"); pos != -1 { | |||
| value = key[pos+1:] | |||
| key = key[:pos] | |||
| } | |||
| switch { | |||
| case strings.HasPrefix(key, "--"): | |||
| spec.long = key[2:] | |||
| case strings.HasPrefix(key, "-"): | |||
| if len(key) != 2 { | |||
| return nil, fmt.Errorf("%s.%s: short arguments must be one character only", t.Name(), field.Name) | |||
| } | |||
| spec.short = key[1:] | |||
| case key == "required": | |||
| spec.required = true | |||
| case key == "positional": | |||
| spec.positional = true | |||
| case key == "help": | |||
| spec.help = value | |||
| default: | |||
| return nil, fmt.Errorf("unrecognized tag '%s' on field %s", key, tag) | |||
| } | |||
| } | |||
| } | |||
| specs = append(specs, &spec) | |||
| } | |||
| } | |||
| return &Parser{spec: specs}, nil | |||
| } | |||
| // Parse processes the given command line option, storing the results in the field | |||
| // of the structs from which NewParser was constructed | |||
| func (p *Parser) Parse(args []string) error { | |||
| // If -h or --help were specified then print usage | |||
| for _, arg := range args { | |||
| if arg == "-h" || arg == "--help" { | |||
| return ErrHelp | |||
| } | |||
| if arg == "--" { | |||
| break | |||
| } | |||
| } | |||
| // Process all command line arguments | |||
| err := process(p.spec, args) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| // Validate | |||
| return validate(p.spec) | |||
| } | |||
| // process goes through arguments one-by-one, parses them, and assigns the result to | |||
| // the underlying struct field | |||
| func process(specs []*spec, args []string) error { | |||
| // construct a map from --option to spec | |||
| optionMap := make(map[string]*spec) | |||
| for _, spec := range specs { | |||
| if spec.positional { | |||
| continue | |||
| } | |||
| if spec.long != "" { | |||
| optionMap[spec.long] = spec | |||
| } | |||
| if spec.short != "" { | |||
| optionMap[spec.short] = spec | |||
| } | |||
| } | |||
| // process each string from the command line | |||
| var allpositional bool | |||
| var positionals []string | |||
| // must use explicit for loop, not range, because we manipulate i inside the loop | |||
| for i := 0; i < len(args); i++ { | |||
| arg := args[i] | |||
| if arg == "--" { | |||
| allpositional = true | |||
| continue | |||
| } | |||
| if !strings.HasPrefix(arg, "-") || allpositional { | |||
| positionals = append(positionals, arg) | |||
| continue | |||
| } | |||
| // check for an equals sign, as in "--foo=bar" | |||
| var value string | |||
| opt := strings.TrimLeft(arg, "-") | |||
| if pos := strings.Index(opt, "="); pos != -1 { | |||
| value = opt[pos+1:] | |||
| opt = opt[:pos] | |||
| } | |||
| // lookup the spec for this option | |||
| spec, ok := optionMap[opt] | |||
| if !ok { | |||
| return fmt.Errorf("unknown argument %s", arg) | |||
| } | |||
| spec.wasPresent = true | |||
| // deal with the case of multiple values | |||
| if spec.multiple { | |||
| var values []string | |||
| if value == "" { | |||
| for i+1 < len(args) && !strings.HasPrefix(args[i+1], "-") { | |||
| values = append(values, args[i+1]) | |||
| i++ | |||
| } | |||
| } else { | |||
| values = append(values, value) | |||
| } | |||
| err := setSlice(spec.dest, values) | |||
| if err != nil { | |||
| return fmt.Errorf("error processing %s: %v", arg, err) | |||
| } | |||
| continue | |||
| } | |||
| // if it's a flag and it has no value then set the value to true | |||
| if spec.dest.Kind() == reflect.Bool && value == "" { | |||
| value = "true" | |||
| } | |||
| // if we have something like "--foo" then the value is the next argument | |||
| if value == "" { | |||
| if i+1 == len(args) || strings.HasPrefix(args[i+1], "-") { | |||
| return fmt.Errorf("missing value for %s", arg) | |||
| } | |||
| value = args[i+1] | |||
| i++ | |||
| } | |||
| err := setScalar(spec.dest, value) | |||
| if err != nil { | |||
| return fmt.Errorf("error processing %s: %v", arg, err) | |||
| } | |||
| } | |||
| // process positionals | |||
| for _, spec := range specs { | |||
| if spec.positional { | |||
| if spec.multiple { | |||
| err := setSlice(spec.dest, positionals) | |||
| if err != nil { | |||
| return fmt.Errorf("error processing %s: %v", spec.long, err) | |||
| } | |||
| positionals = nil | |||
| } else if len(positionals) > 0 { | |||
| err := setScalar(spec.dest, positionals[0]) | |||
| if err != nil { | |||
| return fmt.Errorf("error processing %s: %v", spec.long, err) | |||
| } | |||
| positionals = positionals[1:] | |||
| } else if spec.required { | |||
| return fmt.Errorf("%s is required", spec.long) | |||
| } | |||
| } | |||
| } | |||
| if len(positionals) > 0 { | |||
| return fmt.Errorf("too many positional arguments at '%s'", positionals[0]) | |||
| } | |||
| return nil | |||
| } | |||
| // validate an argument spec after arguments have been parse | |||
| func validate(spec []*spec) error { | |||
| for _, arg := range spec { | |||
| if !arg.positional && arg.required && !arg.wasPresent { | |||
| return fmt.Errorf("--%s is required", arg.long) | |||
| } | |||
| } | |||
| return nil | |||
| } | |||
| // parse a value as the apropriate type and store it in the struct | |||
| func setSlice(dest reflect.Value, values []string) error { | |||
| if !dest.CanSet() { | |||
| return fmt.Errorf("field is not writable") | |||
| } | |||
| var ptr bool | |||
| elem := dest.Type().Elem() | |||
| if elem.Kind() == reflect.Ptr { | |||
| ptr = true | |||
| elem = elem.Elem() | |||
| } | |||
| for _, s := range values { | |||
| v := reflect.New(elem) | |||
| if err := setScalar(v.Elem(), s); err != nil { | |||
| return err | |||
| } | |||
| if !ptr { | |||
| v = v.Elem() | |||
| } | |||
| dest.Set(reflect.Append(dest, v)) | |||
| } | |||
| return nil | |||
| } | |||
| // set a value from a string | |||
| func setScalar(v reflect.Value, s string) error { | |||
| if !v.CanSet() { | |||
| return fmt.Errorf("field is not exported") | |||
| } | |||
| switch v.Kind() { | |||
| case reflect.String: | |||
| v.Set(reflect.ValueOf(s)) | |||
| case reflect.Bool: | |||
| x, err := strconv.ParseBool(s) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| v.Set(reflect.ValueOf(x)) | |||
| case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: | |||
| x, err := strconv.ParseInt(s, 10, v.Type().Bits()) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| v.Set(reflect.ValueOf(x).Convert(v.Type())) | |||
| case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: | |||
| x, err := strconv.ParseUint(s, 10, v.Type().Bits()) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| v.Set(reflect.ValueOf(x).Convert(v.Type())) | |||
| case reflect.Float32, reflect.Float64: | |||
| x, err := strconv.ParseFloat(s, v.Type().Bits()) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| v.Set(reflect.ValueOf(x).Convert(v.Type())) | |||
| default: | |||
| return fmt.Errorf("not a scalar type: %s", v.Kind()) | |||
| } | |||
| return nil | |||
| } | |||
| @ -0,0 +1,358 @@ | |||
| package arg | |||
| import ( | |||
| "os" | |||
| "strings" | |||
| "testing" | |||
| "github.com/stretchr/testify/assert" | |||
| "github.com/stretchr/testify/require" | |||
| ) | |||
| func parse(cmdline string, dest interface{}) error { | |||
| p, err := NewParser(dest) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| return p.Parse(strings.Split(cmdline, " ")) | |||
| } | |||
| func TestStringSingle(t *testing.T) { | |||
| var args struct { | |||
| Foo string | |||
| } | |||
| err := parse("--foo bar", &args) | |||
| require.NoError(t, err) | |||
| assert.Equal(t, "bar", args.Foo) | |||
| } | |||
| func TestMixed(t *testing.T) { | |||
| var args struct { | |||
| Foo string `arg:"-f"` | |||
| Bar int | |||
| Baz uint `arg:"positional"` | |||
| Ham bool | |||
| Spam float32 | |||
| } | |||
| args.Bar = 3 | |||
| err := parse("123 -spam=1.2 -ham -f xyz", &args) | |||
| require.NoError(t, err) | |||
| assert.Equal(t, "xyz", args.Foo) | |||
| assert.Equal(t, 3, args.Bar) | |||
| assert.Equal(t, uint(123), args.Baz) | |||
| assert.Equal(t, true, args.Ham) | |||
| assert.EqualValues(t, 1.2, args.Spam) | |||
| } | |||
| func TestRequired(t *testing.T) { | |||
| var args struct { | |||
| Foo string `arg:"required"` | |||
| } | |||
| err := parse("", &args) | |||
| require.Error(t, err, "--foo is required") | |||
| } | |||
| func TestShortFlag(t *testing.T) { | |||
| var args struct { | |||
| Foo string `arg:"-f"` | |||
| } | |||
| err := parse("-f xyz", &args) | |||
| require.NoError(t, err) | |||
| assert.Equal(t, "xyz", args.Foo) | |||
| err = parse("-foo xyz", &args) | |||
| require.NoError(t, err) | |||
| assert.Equal(t, "xyz", args.Foo) | |||
| err = parse("--foo xyz", &args) | |||
| require.NoError(t, err) | |||
| assert.Equal(t, "xyz", args.Foo) | |||
| } | |||
| func TestInvalidShortFlag(t *testing.T) { | |||
| var args struct { | |||
| Foo string `arg:"-foo"` | |||
| } | |||
| err := parse("", &args) | |||
| assert.Error(t, err) | |||
| } | |||
| func TestLongFlag(t *testing.T) { | |||
| var args struct { | |||
| Foo string `arg:"--abc"` | |||
| } | |||
| err := parse("-abc xyz", &args) | |||
| require.NoError(t, err) | |||
| assert.Equal(t, "xyz", args.Foo) | |||
| err = parse("--abc xyz", &args) | |||
| require.NoError(t, err) | |||
| assert.Equal(t, "xyz", args.Foo) | |||
| } | |||
| func TestCaseSensitive(t *testing.T) { | |||
| var args struct { | |||
| Lower bool `arg:"-v"` | |||
| Upper bool `arg:"-V"` | |||
| } | |||
| err := parse("-v", &args) | |||
| require.NoError(t, err) | |||
| assert.True(t, args.Lower) | |||
| assert.False(t, args.Upper) | |||
| } | |||
| func TestCaseSensitive2(t *testing.T) { | |||
| var args struct { | |||
| Lower bool `arg:"-v"` | |||
| Upper bool `arg:"-V"` | |||
| } | |||
| err := parse("-V", &args) | |||
| require.NoError(t, err) | |||
| assert.False(t, args.Lower) | |||
| assert.True(t, args.Upper) | |||
| } | |||
| func TestPositional(t *testing.T) { | |||
| var args struct { | |||
| Input string `arg:"positional"` | |||
| Output string `arg:"positional"` | |||
| } | |||
| err := parse("foo", &args) | |||
| require.NoError(t, err) | |||
| assert.Equal(t, "foo", args.Input) | |||
| assert.Equal(t, "", args.Output) | |||
| } | |||
| func TestPositionalPointer(t *testing.T) { | |||
| var args struct { | |||
| Input string `arg:"positional"` | |||
| Output []*string `arg:"positional"` | |||
| } | |||
| err := parse("foo bar baz", &args) | |||
| require.NoError(t, err) | |||
| assert.Equal(t, "foo", args.Input) | |||
| bar := "bar" | |||
| baz := "baz" | |||
| assert.Equal(t, []*string{&bar, &baz}, args.Output) | |||
| } | |||
| func TestRequiredPositional(t *testing.T) { | |||
| var args struct { | |||
| Input string `arg:"positional"` | |||
| Output string `arg:"positional,required"` | |||
| } | |||
| err := parse("foo", &args) | |||
| assert.Error(t, err) | |||
| } | |||
| func TestTooManyPositional(t *testing.T) { | |||
| var args struct { | |||
| Input string `arg:"positional"` | |||
| Output string `arg:"positional"` | |||
| } | |||
| err := parse("foo bar baz", &args) | |||
| assert.Error(t, err) | |||
| } | |||
| func TestMultiple(t *testing.T) { | |||
| var args struct { | |||
| Foo []int | |||
| Bar []string | |||
| } | |||
| err := parse("--foo 1 2 3 --bar x y z", &args) | |||
| require.NoError(t, err) | |||
| assert.Equal(t, []int{1, 2, 3}, args.Foo) | |||
| assert.Equal(t, []string{"x", "y", "z"}, args.Bar) | |||
| } | |||
| func TestMultipleWithEq(t *testing.T) { | |||
| var args struct { | |||
| Foo []int | |||
| Bar []string | |||
| } | |||
| err := parse("--foo 1 2 3 --bar=x", &args) | |||
| require.NoError(t, err) | |||
| assert.Equal(t, []int{1, 2, 3}, args.Foo) | |||
| assert.Equal(t, []string{"x"}, args.Bar) | |||
| } | |||
| func TestExemptField(t *testing.T) { | |||
| var args struct { | |||
| Foo string | |||
| Bar interface{} `arg:"-"` | |||
| } | |||
| err := parse("--foo xyz", &args) | |||
| require.NoError(t, err) | |||
| assert.Equal(t, "xyz", args.Foo) | |||
| } | |||
| func TestUnknownField(t *testing.T) { | |||
| var args struct { | |||
| Foo string | |||
| } | |||
| err := parse("--bar xyz", &args) | |||
| assert.Error(t, err) | |||
| } | |||
| func TestMissingRequired(t *testing.T) { | |||
| var args struct { | |||
| Foo string `arg:"required"` | |||
| X []string `arg:"positional"` | |||
| } | |||
| err := parse("x", &args) | |||
| assert.Error(t, err) | |||
| } | |||
| func TestMissingValue(t *testing.T) { | |||
| var args struct { | |||
| Foo string | |||
| } | |||
| err := parse("--foo", &args) | |||
| assert.Error(t, err) | |||
| } | |||
| func TestInvalidInt(t *testing.T) { | |||
| var args struct { | |||
| Foo int | |||
| } | |||
| err := parse("--foo=xyz", &args) | |||
| assert.Error(t, err) | |||
| } | |||
| func TestInvalidUint(t *testing.T) { | |||
| var args struct { | |||
| Foo uint | |||
| } | |||
| err := parse("--foo=xyz", &args) | |||
| assert.Error(t, err) | |||
| } | |||
| func TestInvalidFloat(t *testing.T) { | |||
| var args struct { | |||
| Foo float64 | |||
| } | |||
| err := parse("--foo xyz", &args) | |||
| require.Error(t, err) | |||
| } | |||
| func TestInvalidBool(t *testing.T) { | |||
| var args struct { | |||
| Foo bool | |||
| } | |||
| err := parse("--foo=xyz", &args) | |||
| require.Error(t, err) | |||
| } | |||
| func TestInvalidIntSlice(t *testing.T) { | |||
| var args struct { | |||
| Foo []int | |||
| } | |||
| err := parse("--foo 1 2 xyz", &args) | |||
| require.Error(t, err) | |||
| } | |||
| func TestInvalidPositional(t *testing.T) { | |||
| var args struct { | |||
| Foo int `arg:"positional"` | |||
| } | |||
| err := parse("xyz", &args) | |||
| require.Error(t, err) | |||
| } | |||
| func TestInvalidPositionalSlice(t *testing.T) { | |||
| var args struct { | |||
| Foo []int `arg:"positional"` | |||
| } | |||
| err := parse("1 2 xyz", &args) | |||
| require.Error(t, err) | |||
| } | |||
| func TestNoMoreOptions(t *testing.T) { | |||
| var args struct { | |||
| Foo string | |||
| Bar []string `arg:"positional"` | |||
| } | |||
| err := parse("abc -- --foo xyz", &args) | |||
| require.NoError(t, err) | |||
| assert.Equal(t, "", args.Foo) | |||
| assert.Equal(t, []string{"abc", "--foo", "xyz"}, args.Bar) | |||
| } | |||
| func TestHelpFlag(t *testing.T) { | |||
| var args struct { | |||
| Foo string | |||
| Bar interface{} `arg:"-"` | |||
| } | |||
| err := parse("--help", &args) | |||
| assert.Equal(t, ErrHelp, err) | |||
| } | |||
| func TestPanicOnNonPointer(t *testing.T) { | |||
| var args struct{} | |||
| assert.Panics(t, func() { | |||
| parse("", args) | |||
| }) | |||
| } | |||
| func TestPanicOnNonStruct(t *testing.T) { | |||
| var args string | |||
| assert.Panics(t, func() { | |||
| parse("", &args) | |||
| }) | |||
| } | |||
| func TestUnsupportedType(t *testing.T) { | |||
| var args struct { | |||
| Foo interface{} | |||
| } | |||
| err := parse("--foo", &args) | |||
| assert.Error(t, err) | |||
| } | |||
| func TestUnsupportedSliceElement(t *testing.T) { | |||
| var args struct { | |||
| Foo []interface{} | |||
| } | |||
| err := parse("--foo", &args) | |||
| assert.Error(t, err) | |||
| } | |||
| func TestUnknownTag(t *testing.T) { | |||
| var args struct { | |||
| Foo string `arg:"this_is_not_valid"` | |||
| } | |||
| err := parse("--foo xyz", &args) | |||
| assert.Error(t, err) | |||
| } | |||
| func TestParse(t *testing.T) { | |||
| var args struct { | |||
| Foo string | |||
| } | |||
| os.Args = []string{"example", "--foo", "bar"} | |||
| err := Parse(&args) | |||
| require.NoError(t, err) | |||
| assert.Equal(t, "bar", args.Foo) | |||
| } | |||
| func TestParseError(t *testing.T) { | |||
| var args struct { | |||
| Foo string `arg:"this_is_not_valid"` | |||
| } | |||
| os.Args = []string{"example", "--bar"} | |||
| err := Parse(&args) | |||
| assert.Error(t, err) | |||
| } | |||
| func TestMustParse(t *testing.T) { | |||
| var args struct { | |||
| Foo string | |||
| } | |||
| os.Args = []string{"example", "--foo", "bar"} | |||
| MustParse(&args) | |||
| assert.Equal(t, "bar", args.Foo) | |||
| } | |||
| @ -0,0 +1,107 @@ | |||
| package arg | |||
| import ( | |||
| "fmt" | |||
| "io" | |||
| "os" | |||
| "path/filepath" | |||
| "reflect" | |||
| "strings" | |||
| ) | |||
| // Fail prints usage information to stdout and exits with non-zero status | |||
| func (p *Parser) Fail(msg string) { | |||
| p.WriteUsage(os.Stdout) | |||
| fmt.Println("error:", msg) | |||
| os.Exit(-1) | |||
| } | |||
| // WriteUsage writes usage information to the given writer | |||
| func (p *Parser) WriteUsage(w io.Writer) { | |||
| var positionals, options []*spec | |||
| for _, spec := range p.spec { | |||
| if spec.positional { | |||
| positionals = append(positionals, spec) | |||
| } else { | |||
| options = append(options, spec) | |||
| } | |||
| } | |||
| fmt.Fprintf(w, "usage: %s ", filepath.Base(os.Args[0])) | |||
| // write the option component of the usage message | |||
| for _, spec := range options { | |||
| if !spec.required { | |||
| fmt.Fprint(w, "[") | |||
| } | |||
| fmt.Fprint(w, synopsis(spec, "--"+spec.long)) | |||
| if !spec.required { | |||
| fmt.Fprint(w, "]") | |||
| } | |||
| fmt.Fprint(w, " ") | |||
| } | |||
| // write the positional component of the usage message | |||
| for _, spec := range positionals { | |||
| up := strings.ToUpper(spec.long) | |||
| if spec.multiple { | |||
| fmt.Fprintf(w, "[%s [%s ...]]", up, up) | |||
| } else { | |||
| fmt.Fprint(w, up) | |||
| } | |||
| fmt.Fprint(w, " ") | |||
| } | |||
| fmt.Fprint(w, "\n") | |||
| } | |||
| // WriteHelp writes the usage string followed by the full help string for each option | |||
| func (p *Parser) WriteHelp(w io.Writer) { | |||
| var positionals, options []*spec | |||
| for _, spec := range p.spec { | |||
| if spec.positional { | |||
| positionals = append(positionals, spec) | |||
| } else { | |||
| options = append(options, spec) | |||
| } | |||
| } | |||
| p.WriteUsage(w) | |||
| // write the list of positionals | |||
| if len(positionals) > 0 { | |||
| fmt.Fprint(w, "\npositional arguments:\n") | |||
| for _, spec := range positionals { | |||
| fmt.Fprintf(w, " %s\n", spec.long) | |||
| } | |||
| } | |||
| // write the list of options | |||
| if len(options) > 0 { | |||
| fmt.Fprint(w, "\noptions:\n") | |||
| const colWidth = 25 | |||
| for _, spec := range options { | |||
| left := " " + synopsis(spec, "--"+spec.long) | |||
| if spec.short != "" { | |||
| left += ", " + synopsis(spec, "-"+spec.short) | |||
| } | |||
| fmt.Fprint(w, left) | |||
| if spec.help != "" { | |||
| if len(left)+2 < colWidth { | |||
| fmt.Fprint(w, strings.Repeat(" ", colWidth-len(left))) | |||
| } else { | |||
| fmt.Fprint(w, "\n"+strings.Repeat(" ", colWidth)) | |||
| } | |||
| fmt.Fprint(w, spec.help) | |||
| } | |||
| fmt.Fprint(w, "\n") | |||
| } | |||
| } | |||
| } | |||
| func synopsis(spec *spec, form string) string { | |||
| if spec.dest.Kind() == reflect.Bool { | |||
| return form | |||
| } else { | |||
| return form + " " + strings.ToUpper(spec.long) | |||
| } | |||
| } | |||
| @ -0,0 +1,46 @@ | |||
| package arg | |||
| import ( | |||
| "bytes" | |||
| "os" | |||
| "testing" | |||
| "github.com/stretchr/testify/assert" | |||
| "github.com/stretchr/testify/require" | |||
| ) | |||
| func TestWriteUsage(t *testing.T) { | |||
| expectedUsage := "usage: example [--verbose] [--dataset DATASET] [--optimize OPTIMIZE] INPUT [OUTPUT [OUTPUT ...]] \n" | |||
| expectedHelp := `usage: example [--verbose] [--dataset DATASET] [--optimize OPTIMIZE] INPUT [OUTPUT [OUTPUT ...]] | |||
| positional arguments: | |||
| input | |||
| output | |||
| options: | |||
| --verbose, -v verbosity level | |||
| --dataset DATASET dataset to use | |||
| --optimize OPTIMIZE, -O OPTIMIZE | |||
| optimization level | |||
| ` | |||
| var args struct { | |||
| Input string `arg:"positional"` | |||
| Output []string `arg:"positional"` | |||
| Verbose bool `arg:"-v,help:verbosity level"` | |||
| Dataset string `arg:"help:dataset to use"` | |||
| Optimize int `arg:"-O,help:optimization level"` | |||
| } | |||
| p, err := NewParser(&args) | |||
| require.NoError(t, err) | |||
| os.Args[0] = "example" | |||
| var usage bytes.Buffer | |||
| p.WriteUsage(&usage) | |||
| assert.Equal(t, expectedUsage, usage.String()) | |||
| var help bytes.Buffer | |||
| p.WriteHelp(&help) | |||
| assert.Equal(t, expectedHelp, help.String()) | |||
| } | |||