diff --git a/vendor/github.com/alexflint/go-arg/.gitignore b/vendor/github.com/alexflint/go-arg/.gitignore new file mode 100644 index 0000000..daf913b --- /dev/null +++ b/vendor/github.com/alexflint/go-arg/.gitignore @@ -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 diff --git a/vendor/github.com/alexflint/go-arg/LICENSE b/vendor/github.com/alexflint/go-arg/LICENSE new file mode 100644 index 0000000..a50c494 --- /dev/null +++ b/vendor/github.com/alexflint/go-arg/LICENSE @@ -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. + diff --git a/vendor/github.com/alexflint/go-arg/README.md b/vendor/github.com/alexflint/go-arg/README.md new file mode 100644 index 0000000..f105b17 --- /dev/null +++ b/vendor/github.com/alexflint/go-arg/README.md @@ -0,0 +1,602 @@ +

+ go-arg +
+ go-arg +
+

+

Struct-based argument parsing for Go

+

+ Sourcegraph + Documentation + Build Status + Coverage Status + Go Report Card +

+
+ +Declare command line arguments for your program 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 +``` + +### Installation + +```shell +go get github.com/alexflint/go-arg +``` + +### Required arguments + +```go +var args struct { + ID int `arg:"required"` + Timeout time.Duration +} +arg.MustParse(&args) +``` + +```shell +$ ./example +Usage: example --id ID [--timeout TIMEOUT] +error: --id 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] +``` + +### Environment variables + +```go +var args struct { + Workers int `arg:"env"` +} +arg.MustParse(&args) +fmt.Println("Workers:", args.Workers) +``` + +``` +$ WORKERS=4 ./example +Workers: 4 +``` + +``` +$ WORKERS=4 ./example --workers=6 +Workers: 6 +``` + +You can also override the name of the environment variable: + +```go +var args struct { + Workers int `arg:"env:NUM_WORKERS"` +} +arg.MustParse(&args) +fmt.Println("Workers:", args.Workers) +``` + +``` +$ NUM_WORKERS=4 ./example +Workers: 4 +``` + +You can provide multiple values using the CSV (RFC 4180) format: + +```go +var args struct { + Workers []int `arg:"env"` +} +arg.MustParse(&args) +fmt.Println("Workers:", args.Workers) +``` + +``` +$ WORKERS='1,99' ./example +Workers: [1 99] +``` + +### Usage strings +```go +var args struct { + Input string `arg:"positional"` + Output []string `arg:"positional"` + Verbose bool `arg:"-v,--verbose" help:"verbosity level"` + Dataset string `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 `default:"abc"` + Bar bool +} +arg.MustParse(&args) +``` + +### Default values (before v1.2) + +```go +var args struct { + Foo string + Bar bool +} +arg.Foo = "abc" +arg.MustParse(&args) +``` + +### Combining command line options, environment variables, and default values + +You can combine command line arguments, environment variables, and default values. Command line arguments take precedence over environment variables, which take precedence over default values. This means that we check whether a certain option was provided on the command line, then if not, we check for an environment variable (only if an `env` tag was provided), then if none is found, we check for a `default` tag containing a default value. + +```go +var args struct { + Test string `arg:"-t,env:TEST" default:"something"` +} +arg.MustParse(&args) +``` + +#### Ignoring environment variables and/or default values + +The values in an existing structure can be kept in-tact by ignoring environment +variables and/or default values. + +```go +var args struct { + Test string `arg:"-t,env:TEST" default:"something"` +} + +p, err := arg.NewParser(arg.Config{ + IgnoreEnv: true, + IgnoreDefault: true, +}, &args) + +err = p.Parse(os.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] +``` + +### 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"` +} +arg.MustParse(&args) +``` + +```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] +``` + +### Arguments with keys and values +```go +var args struct { + UserIDs map[string]int +} +arg.MustParse(&args) +fmt.Println(args.UserIDs) +``` + +```shell +./example --userids john=123 mary=456 +map[john:123 mary:456] +``` + +### Custom validation +```go +var args struct { + Foo string + Bar string +} +p := arg.MustParse(&args) +if args.Foo == "" && args.Bar == "" { + p.Fail("you must provide either --foo or --bar") +} +``` + +```shell +./example +Usage: samples [--foo FOO] [--bar BAR] +error: you must provide either --foo or --bar +``` + +### Version strings + +```go +type args struct { + ... +} + +func (args) Version() string { + return "someprogram 4.3.0" +} + +func main() { + var args args + arg.MustParse(&args) +} +``` + +```shell +$ ./example --version +someprogram 4.3.0 +``` + +### Overriding option names + +```go +var args struct { + Short string `arg:"-s"` + Long string `arg:"--custom-long-option"` + ShortAndLong string `arg:"-x,--my-option"` + OnlyShort string `arg:"-o,--"` +} +arg.MustParse(&args) +``` + +```shell +$ ./example --help +Usage: example [-o ONLYSHORT] [--short SHORT] [--custom-long-option CUSTOM-LONG-OPTION] [--my-option MY-OPTION] + +Options: + --short SHORT, -s SHORT + --custom-long-option CUSTOM-LONG-OPTION + --my-option MY-OPTION, -x MY-OPTION + -o ONLYSHORT + --help, -h display this help and exit +``` + + +### Embedded structs + +The fields of embedded structs are treated just like regular fields: + +```go + +type DatabaseOptions struct { + Host string + Username string + Password string +} + +type LogOptions struct { + LogFile string + Verbose bool +} + +func main() { + var args struct { + DatabaseOptions + LogOptions + } + arg.MustParse(&args) +} +``` + +As usual, any field tagged with `arg:"-"` is ignored. + +### Supported types + +The following types may be used as arguments: +- built-in integer types: `int, int8, int16, int32, int64, byte, rune` +- built-in floating point types: `float32, float64` +- strings +- booleans +- URLs represented as `url.URL` +- time durations represented as `time.Duration` +- email addresses represented as `mail.Address` +- MAC addresses represented as `net.HardwareAddr` +- pointers to any of the above +- slices of any of the above +- maps using any of the above as keys and values +- any type that implements `encoding.TextUnmarshaler` + +### Custom parsing + +Implement `encoding.TextUnmarshaler` to define your own parsing logic. + +```go +// Accepts command line arguments of the form "head.tail" +type NameDotName struct { + Head, Tail string +} + +func (n *NameDotName) UnmarshalText(b []byte) error { + s := string(b) + pos := strings.Index(s, ".") + if pos == -1 { + return fmt.Errorf("missing period in %s", s) + } + n.Head = s[:pos] + n.Tail = s[pos+1:] + return nil +} + +func main() { + var args struct { + Name NameDotName + } + arg.MustParse(&args) + fmt.Printf("%#v\n", args.Name) +} +``` +```shell +$ ./example --name=foo.bar +main.NameDotName{Head:"foo", Tail:"bar"} + +$ ./example --name=oops +Usage: example [--name NAME] +error: error processing --name: missing period in "oops" +``` + +### Custom parsing with default values + +Implement `encoding.TextMarshaler` to define your own default value strings: + +```go +// Accepts command line arguments of the form "head.tail" +type NameDotName struct { + Head, Tail string +} + +func (n *NameDotName) UnmarshalText(b []byte) error { + // same as previous example +} + +// this is only needed if you want to display a default value in the usage string +func (n *NameDotName) MarshalText() ([]byte, error) { + return []byte(fmt.Sprintf("%s.%s", n.Head, n.Tail)), nil +} + +func main() { + var args struct { + Name NameDotName `default:"file.txt"` + } + arg.MustParse(&args) + fmt.Printf("%#v\n", args.Name) +} +``` +```shell +$ ./example --help +Usage: test [--name NAME] + +Options: + --name NAME [default: file.txt] + --help, -h display this help and exit + +$ ./example +main.NameDotName{Head:"file", Tail:"txt"} +``` + +### Custom placeholders + +*Introduced in version 1.3.0* + +Use the `placeholder` tag to control which placeholder text is used in the usage text. + +```go +var args struct { + Input string `arg:"positional" placeholder:"SRC"` + Output []string `arg:"positional" placeholder:"DST"` + Optimize int `arg:"-O" help:"optimization level" placeholder:"LEVEL"` + MaxJobs int `arg:"-j" help:"maximum number of simultaneous jobs" placeholder:"N"` +} +arg.MustParse(&args) +``` +```shell +$ ./example -h +Usage: example [--optimize LEVEL] [--maxjobs N] SRC [DST [DST ...]] + +Positional arguments: + SRC + DST + +Options: + --optimize LEVEL, -O LEVEL + optimization level + --maxjobs N, -j N maximum number of simultaneous jobs + --help, -h display this help and exit +``` + +### Description strings + +A descriptive message can be added at the top of the help text by implementing +a `Description` function that returns a string. + +```go +type args struct { + Foo string +} + +func (args) Description() string { + return "this program does this and that" +} + +func main() { + var args args + arg.MustParse(&args) +} +``` + +```shell +$ ./example -h +this program does this and that +Usage: example [--foo FOO] + +Options: + --foo FOO + --help, -h display this help and exit +``` + +Similarly an epilogue can be added at the end of the help text by implementing +the `Epilogue` function. + +```go +type args struct { + Foo string +} + +func (args) Epilogue() string { + return "For more information visit github.com/alexflint/go-arg" +} + +func main() { + var args args + arg.MustParse(&args) +} +``` + +```shell +$ ./example -h +Usage: example [--foo FOO] + +Options: + --foo FOO + --help, -h display this help and exit + +For more information visit github.com/alexflint/go-arg +``` + +### Subcommands + +*Introduced in version 1.1.0* + +Subcommands are commonly used in tools that wish to group multiple functions into a single program. An example is the `git` tool: +```shell +$ git checkout [arguments specific to checking out code] +$ git commit [arguments specific to committing] +$ git push [arguments specific to pushing] +``` + +The strings "checkout", "commit", and "push" are different from simple positional arguments because the options available to the user change depending on which subcommand they choose. + +This can be implemented with `go-arg` as follows: + +```go +type CheckoutCmd struct { + Branch string `arg:"positional"` + Track bool `arg:"-t"` +} +type CommitCmd struct { + All bool `arg:"-a"` + Message string `arg:"-m"` +} +type PushCmd struct { + Remote string `arg:"positional"` + Branch string `arg:"positional"` + SetUpstream bool `arg:"-u"` +} +var args struct { + Checkout *CheckoutCmd `arg:"subcommand:checkout"` + Commit *CommitCmd `arg:"subcommand:commit"` + Push *PushCmd `arg:"subcommand:push"` + Quiet bool `arg:"-q"` // this flag is global to all subcommands +} + +arg.MustParse(&args) + +switch { +case args.Checkout != nil: + fmt.Printf("checkout requested for branch %s\n", args.Checkout.Branch) +case args.Commit != nil: + fmt.Printf("commit requested with message \"%s\"\n", args.Commit.Message) +case args.Push != nil: + fmt.Printf("push requested from %s to %s\n", args.Push.Branch, args.Push.Remote) +} +``` + +Some additional rules apply when working with subcommands: +* The `subcommand` tag can only be used with fields that are pointers to structs +* Any struct that contains a subcommand must not contain any positionals + +This package allows to have a program that accepts subcommands, but also does something else +when no subcommands are specified. +If on the other hand you want the program to terminate when no subcommands are specified, +the recommended way is: + +```go +p := arg.MustParse(&args) +if p.Subcommand() == nil { + p.Fail("missing subcommand") +} +``` + +### API 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 `flag` library that ships in the standard library seems awkward to me. Positional arguments must preceed options, so `./prog x --foo=1` does what you expect but `./prog --foo=1 x` does not. It also does not allow arguments to have both long (`--foo`) and short (`-f`) forms. + +Many third-party argument parsing libraries are great for writing sophisticated command line interfaces, but feel to me like overkill for a simple script with a few flags. + +The idea behind `go-arg` is that Go already has an excellent way to describe data structures using structs, so there is no need to develop additional levels of abstraction. Instead of one API to specify which arguments your program accepts, and then another API to get the values of those arguments, `go-arg` replaces both with a single struct. + +### Backward compatibility notes + +Earlier versions of this library required the help text to be part of the `arg` tag. This is still supported but is now deprecated. Instead, you should use a separate `help` tag, described above, which removes most of the limits on the text you can write. In particular, you will need to use the new `help` tag if your help text includes any commas. diff --git a/vendor/github.com/alexflint/go-arg/doc.go b/vendor/github.com/alexflint/go-arg/doc.go new file mode 100644 index 0000000..3b0bafd --- /dev/null +++ b/vendor/github.com/alexflint/go-arg/doc.go @@ -0,0 +1,39 @@ +// 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` and `help` tag names: +// +// 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:"-"` +// } +// +// 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). +// +// Other valid tag strings are `positional` and `required`. +// +// Fields can be excluded from processing with `arg:"-"`. +package arg diff --git a/vendor/github.com/alexflint/go-arg/parse.go b/vendor/github.com/alexflint/go-arg/parse.go new file mode 100644 index 0000000..98c21cd --- /dev/null +++ b/vendor/github.com/alexflint/go-arg/parse.go @@ -0,0 +1,853 @@ +package arg + +import ( + "encoding" + "encoding/csv" + "errors" + "fmt" + "io" + "os" + "path/filepath" + "reflect" + "strings" + + scalar "github.com/alexflint/go-scalar" +) + +// path represents a sequence of steps to find the output location for an +// argument or subcommand in the final destination struct +type path struct { + root int // index of the destination struct + fields []reflect.StructField // sequence of struct fields to traverse +} + +// String gets a string representation of the given path +func (p path) String() string { + s := "args" + for _, f := range p.fields { + s += "." + f.Name + } + return s +} + +// Child gets a new path representing a child of this path. +func (p path) Child(f reflect.StructField) path { + // copy the entire slice of fields to avoid possible slice overwrite + subfields := make([]reflect.StructField, len(p.fields)+1) + copy(subfields, p.fields) + subfields[len(subfields)-1] = f + return path{ + root: p.root, + fields: subfields, + } +} + +// spec represents a command line option +type spec struct { + dest path + field reflect.StructField // the struct field from which this option was created + long string // the --long form for this option, or empty if none + short string // the -s short form for this option, or empty if none + cardinality cardinality // determines how many tokens will be present (possible values: zero, one, multiple) + required bool // if true, this option must be present on the command line + positional bool // if true, this option will be looked for in the positional flags + separate bool // if true, each slice and map entry will have its own --flag + help string // the help text for this option + env string // the name of the environment variable for this option, or empty for none + defaultValue reflect.Value // default value for this option + defaultString string // default value for this option, in string form to be displayed in help text + placeholder string // placeholder string in help +} + +// command represents a named subcommand, or the top-level command +type command struct { + name string + aliases []string + help string + dest path + specs []*spec + subcommands []*command + parent *command +} + +// ErrHelp indicates that the builtin -h or --help were provided +var ErrHelp = errors.New("help requested by user") + +// ErrVersion indicates that the builtin --version was provided +var ErrVersion = errors.New("version requested by user") + +// for monkey patching in example and test code +var mustParseExit = os.Exit +var mustParseOut io.Writer = os.Stdout + +// MustParse processes command line arguments and exits upon failure +func MustParse(dest ...interface{}) *Parser { + return mustParse(Config{Exit: mustParseExit, Out: mustParseOut}, dest...) +} + +// mustParse is a helper that facilitates testing +func mustParse(config Config, dest ...interface{}) *Parser { + p, err := NewParser(config, dest...) + if err != nil { + fmt.Fprintln(config.Out, err) + config.Exit(-1) + return nil + } + + p.MustParse(flags()) + return p +} + +// Parse processes command line arguments and stores them in dest +func Parse(dest ...interface{}) error { + p, err := NewParser(Config{}, dest...) + if err != nil { + return err + } + return p.Parse(flags()) +} + +// flags gets all command line arguments other than the first (program name) +func flags() []string { + if len(os.Args) == 0 { // os.Args could be empty + return nil + } + return os.Args[1:] +} + +// Config represents configuration options for an argument parser +type Config struct { + // Program is the name of the program used in the help text + Program string + + // IgnoreEnv instructs the library not to read environment variables + IgnoreEnv bool + + // IgnoreDefault instructs the library not to reset the variables to the + // default values, including pointers to sub commands + IgnoreDefault bool + + // StrictSubcommands intructs the library not to allow global commands after + // subcommand + StrictSubcommands bool + + // Exit is called to terminate the process with an error code (defaults to os.Exit) + Exit func(int) + + // Out is where help text, usage text, and failure messages are printed (defaults to os.Stdout) + Out io.Writer +} + +// Parser represents a set of command line options with destination values +type Parser struct { + cmd *command + roots []reflect.Value + config Config + version string + description string + epilogue string + + // the following field changes during processing of command line arguments + subcommand []string +} + +// Versioned is the interface that the destination struct should implement to +// make a version string appear at the top of the help message. +type Versioned interface { + // Version returns the version string that will be printed on a line by itself + // at the top of the help message. + Version() string +} + +// Described is the interface that the destination struct should implement to +// make a description string appear at the top of the help message. +type Described interface { + // Description returns the string that will be printed on a line by itself + // at the top of the help message. + Description() string +} + +// Epilogued is the interface that the destination struct should implement to +// add an epilogue string at the bottom of the help message. +type Epilogued interface { + // Epilogue returns the string that will be printed on a line by itself + // at the end of the help message. + Epilogue() string +} + +// walkFields calls a function for each field of a struct, recursively expanding struct fields. +func walkFields(t reflect.Type, visit func(field reflect.StructField, owner reflect.Type) bool) { + walkFieldsImpl(t, visit, nil) +} + +func walkFieldsImpl(t reflect.Type, visit func(field reflect.StructField, owner reflect.Type) bool, path []int) { + for i := 0; i < t.NumField(); i++ { + field := t.Field(i) + field.Index = make([]int, len(path)+1) + copy(field.Index, append(path, i)) + expand := visit(field, t) + if expand && field.Type.Kind() == reflect.Struct { + var subpath []int + if field.Anonymous { + subpath = append(path, i) + } + walkFieldsImpl(field.Type, visit, subpath) + } + } +} + +// NewParser constructs a parser from a list of destination structs +func NewParser(config Config, dests ...interface{}) (*Parser, error) { + // fill in defaults + if config.Exit == nil { + config.Exit = os.Exit + } + if config.Out == nil { + config.Out = os.Stdout + } + + // first pick a name for the command for use in the usage text + var name string + switch { + case config.Program != "": + name = config.Program + case len(os.Args) > 0: + name = filepath.Base(os.Args[0]) + default: + name = "program" + } + + // construct a parser + p := Parser{ + cmd: &command{name: name}, + config: config, + } + + // make a list of roots + for _, dest := range dests { + p.roots = append(p.roots, reflect.ValueOf(dest)) + } + + // process each of the destination values + for i, dest := range dests { + t := reflect.TypeOf(dest) + if t.Kind() != reflect.Ptr { + panic(fmt.Sprintf("%s is not a pointer (did you forget an ampersand?)", t)) + } + + cmd, err := cmdFromStruct(name, path{root: i}, t) + if err != nil { + return nil, err + } + + // for backwards compatibility, add nonzero field values as defaults + // this applies only to the top-level command, not to subcommands (this inconsistency + // is the reason that this method for setting default values was deprecated) + for _, spec := range cmd.specs { + // get the value + v := p.val(spec.dest) + + // if the value is the "zero value" (e.g. nil pointer, empty struct) then ignore + if isZero(v) { + continue + } + + // store as a default + spec.defaultValue = v + + // we need a string to display in help text + // if MarshalText is implemented then use that + if m, ok := v.Interface().(encoding.TextMarshaler); ok { + s, err := m.MarshalText() + if err != nil { + return nil, fmt.Errorf("%v: error marshaling default value to string: %v", spec.dest, err) + } + spec.defaultString = string(s) + } else { + spec.defaultString = fmt.Sprintf("%v", v) + } + } + + p.cmd.specs = append(p.cmd.specs, cmd.specs...) + p.cmd.subcommands = append(p.cmd.subcommands, cmd.subcommands...) + + if dest, ok := dest.(Versioned); ok { + p.version = dest.Version() + } + if dest, ok := dest.(Described); ok { + p.description = dest.Description() + } + if dest, ok := dest.(Epilogued); ok { + p.epilogue = dest.Epilogue() + } + } + + return &p, nil +} + +func cmdFromStruct(name string, dest path, t reflect.Type) (*command, error) { + // commands can only be created from pointers to structs + if t.Kind() != reflect.Ptr { + return nil, fmt.Errorf("subcommands must be pointers to structs but %s is a %s", + dest, t.Kind()) + } + + t = t.Elem() + if t.Kind() != reflect.Struct { + return nil, fmt.Errorf("subcommands must be pointers to structs but %s is a pointer to %s", + dest, t.Kind()) + } + + cmd := command{ + name: name, + dest: dest, + } + + var errs []string + walkFields(t, func(field reflect.StructField, t reflect.Type) bool { + // check for the ignore switch in the tag + tag := field.Tag.Get("arg") + if tag == "-" { + return false + } + + // if this is an embedded struct then recurse into its fields, even if + // it is unexported, because exported fields on unexported embedded + // structs are still writable + if field.Anonymous && field.Type.Kind() == reflect.Struct { + return true + } + + // ignore any other unexported field + if !isExported(field.Name) { + return false + } + + // duplicate the entire path to avoid slice overwrites + subdest := dest.Child(field) + spec := spec{ + dest: subdest, + field: field, + long: strings.ToLower(field.Name), + } + + help, exists := field.Tag.Lookup("help") + if exists { + spec.help = help + } + + // process each comma-separated part of the tag + var isSubcommand bool + for _, key := range strings.Split(tag, ",") { + if key == "" { + continue + } + key = strings.TrimLeft(key, " ") + var value string + if pos := strings.Index(key, ":"); pos != -1 { + value = key[pos+1:] + key = key[:pos] + } + + switch { + case strings.HasPrefix(key, "---"): + errs = append(errs, fmt.Sprintf("%s.%s: too many hyphens", t.Name(), field.Name)) + case strings.HasPrefix(key, "--"): + spec.long = key[2:] + case strings.HasPrefix(key, "-"): + if len(key) > 2 { + errs = append(errs, fmt.Sprintf("%s.%s: short arguments must be one character only", + t.Name(), field.Name)) + return false + } + spec.short = key[1:] + case key == "required": + spec.required = true + case key == "positional": + spec.positional = true + case key == "separate": + spec.separate = true + case key == "help": // deprecated + spec.help = value + case key == "env": + // Use override name if provided + if value != "" { + spec.env = value + } else { + spec.env = strings.ToUpper(field.Name) + } + case key == "subcommand": + // decide on a name for the subcommand + var cmdnames []string + if value == "" { + cmdnames = []string{strings.ToLower(field.Name)} + } else { + cmdnames = strings.Split(value, "|") + } + for i := range cmdnames { + cmdnames[i] = strings.TrimSpace(cmdnames[i]) + } + + // parse the subcommand recursively + subcmd, err := cmdFromStruct(cmdnames[0], subdest, field.Type) + if err != nil { + errs = append(errs, err.Error()) + return false + } + + subcmd.aliases = cmdnames[1:] + subcmd.parent = &cmd + subcmd.help = field.Tag.Get("help") + + cmd.subcommands = append(cmd.subcommands, subcmd) + isSubcommand = true + default: + errs = append(errs, fmt.Sprintf("unrecognized tag '%s' on field %s", key, tag)) + return false + } + } + + // placeholder is the string used in the help text like this: "--somearg PLACEHOLDER" + placeholder, hasPlaceholder := field.Tag.Lookup("placeholder") + if hasPlaceholder { + spec.placeholder = placeholder + } else if spec.long != "" { + spec.placeholder = strings.ToUpper(spec.long) + } else { + spec.placeholder = strings.ToUpper(spec.field.Name) + } + + // if this is a subcommand then we've done everything we need to do + if isSubcommand { + return false + } + + // check whether this field is supported. It's good to do this here rather than + // wait until ParseValue because it means that a program with invalid argument + // fields will always fail regardless of whether the arguments it received + // exercised those fields. + var err error + spec.cardinality, err = cardinalityOf(field.Type) + if err != nil { + errs = append(errs, fmt.Sprintf("%s.%s: %s fields are not supported", + t.Name(), field.Name, field.Type.String())) + return false + } + + defaultString, hasDefault := field.Tag.Lookup("default") + if hasDefault { + // we do not support default values for maps and slices + if spec.cardinality == multiple { + errs = append(errs, fmt.Sprintf("%s.%s: default values are not supported for slice or map fields", + t.Name(), field.Name)) + return false + } + + // a required field cannot also have a default value + if spec.required { + errs = append(errs, fmt.Sprintf("%s.%s: 'required' cannot be used when a default value is specified", + t.Name(), field.Name)) + return false + } + + // parse the default value + spec.defaultString = defaultString + if field.Type.Kind() == reflect.Ptr { + // here we have a field of type *T and we create a new T, no need to dereference + // in order for the value to be settable + spec.defaultValue = reflect.New(field.Type.Elem()) + } else { + // here we have a field of type T and we create a new T and then dereference it + // so that the resulting value is settable + spec.defaultValue = reflect.New(field.Type).Elem() + } + err := scalar.ParseValue(spec.defaultValue, defaultString) + if err != nil { + errs = append(errs, fmt.Sprintf("%s.%s: error processing default value: %v", t.Name(), field.Name, err)) + return false + } + } + + // add the spec to the list of specs + cmd.specs = append(cmd.specs, &spec) + + // if this was an embedded field then we already returned true up above + return false + }) + + if len(errs) > 0 { + return nil, errors.New(strings.Join(errs, "\n")) + } + + // check that we don't have both positionals and subcommands + var hasPositional bool + for _, spec := range cmd.specs { + if spec.positional { + hasPositional = true + } + } + if hasPositional && len(cmd.subcommands) > 0 { + return nil, fmt.Errorf("%s cannot have both subcommands and positional arguments", dest) + } + + return &cmd, 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 { + err := p.process(args) + if err != nil { + // If -h or --help were specified then make sure help text supercedes other errors + for _, arg := range args { + if arg == "-h" || arg == "--help" { + return ErrHelp + } + if arg == "--" { + break + } + } + } + return err +} + +func (p *Parser) MustParse(args []string) { + err := p.Parse(args) + switch { + case err == ErrHelp: + p.WriteHelpForSubcommand(p.config.Out, p.subcommand...) + p.config.Exit(0) + case err == ErrVersion: + fmt.Fprintln(p.config.Out, p.version) + p.config.Exit(0) + case err != nil: + p.FailSubcommand(err.Error(), p.subcommand...) + } +} + +// process environment vars for the given arguments +func (p *Parser) captureEnvVars(specs []*spec, wasPresent map[*spec]bool) error { + for _, spec := range specs { + if spec.env == "" { + continue + } + + value, found := os.LookupEnv(spec.env) + if !found { + continue + } + + if spec.cardinality == multiple { + // expect a CSV string in an environment + // variable in the case of multiple values + var values []string + var err error + if len(strings.TrimSpace(value)) > 0 { + values, err = csv.NewReader(strings.NewReader(value)).Read() + if err != nil { + return fmt.Errorf( + "error reading a CSV string from environment variable %s with multiple values: %v", + spec.env, + err, + ) + } + } + if err = setSliceOrMap(p.val(spec.dest), values, !spec.separate); err != nil { + return fmt.Errorf( + "error processing environment variable %s with multiple values: %v", + spec.env, + err, + ) + } + } else { + if err := scalar.ParseValue(p.val(spec.dest), value); err != nil { + return fmt.Errorf("error processing environment variable %s: %v", spec.env, err) + } + } + wasPresent[spec] = true + } + + return nil +} + +// process goes through arguments one-by-one, parses them, and assigns the result to +// the underlying struct field +func (p *Parser) process(args []string) error { + // track the options we have seen + wasPresent := make(map[*spec]bool) + + // union of specs for the chain of subcommands encountered so far + curCmd := p.cmd + p.subcommand = nil + + // make a copy of the specs because we will add to this list each time we expand a subcommand + specs := make([]*spec, len(curCmd.specs)) + copy(specs, curCmd.specs) + + // deal with environment vars + if !p.config.IgnoreEnv { + err := p.captureEnvVars(specs, wasPresent) + if err != nil { + return err + } + } + + // determine if the current command has a version option spec + var hasVersionOption bool + for _, spec := range curCmd.specs { + if spec.long == "version" { + hasVersionOption = true + break + } + } + + // 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 !isFlag(arg) || allpositional { + // each subcommand can have either subcommands or positionals, but not both + if len(curCmd.subcommands) == 0 { + positionals = append(positionals, arg) + continue + } + + // if we have a subcommand then make sure it is valid for the current context + subcmd := findSubcommand(curCmd.subcommands, arg) + if subcmd == nil { + return fmt.Errorf("invalid subcommand: %s", arg) + } + + // instantiate the field to point to a new struct + v := p.val(subcmd.dest) + if v.IsNil() { + v.Set(reflect.New(v.Type().Elem())) // we already checked that all subcommands are struct pointers + } + + // add the new options to the set of allowed options + if p.config.StrictSubcommands { + specs = make([]*spec, len(subcmd.specs)) + copy(specs, subcmd.specs) + } else { + specs = append(specs, subcmd.specs...) + } + + // capture environment vars for these new options + if !p.config.IgnoreEnv { + err := p.captureEnvVars(subcmd.specs, wasPresent) + if err != nil { + return err + } + } + + curCmd = subcmd + p.subcommand = append(p.subcommand, arg) + continue + } + + // check for special --help and --version flags + switch arg { + case "-h", "--help": + return ErrHelp + case "--version": + if !hasVersionOption && p.version != "" { + return ErrVersion + } + } + + // 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 (note that the "specs" slice changes as + // we expand subcommands so it is better not to use a map) + spec := findOption(specs, opt) + if spec == nil || opt == "" { + return fmt.Errorf("unknown argument %s", arg) + } + wasPresent[spec] = true + + // deal with the case of multiple values + if spec.cardinality == multiple { + var values []string + if value == "" { + for i+1 < len(args) && !isFlag(args[i+1]) && args[i+1] != "--" { + values = append(values, args[i+1]) + i++ + if spec.separate { + break + } + } + } else { + values = append(values, value) + } + err := setSliceOrMap(p.val(spec.dest), values, !spec.separate) + 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 + // use boolean because this takes account of TextUnmarshaler + if spec.cardinality == zero && value == "" { + value = "true" + } + + // if we have something like "--foo" then the value is the next argument + if value == "" { + if i+1 == len(args) { + return fmt.Errorf("missing value for %s", arg) + } + if !nextIsNumeric(spec.field.Type, args[i+1]) && isFlag(args[i+1]) { + return fmt.Errorf("missing value for %s", arg) + } + value = args[i+1] + i++ + } + + err := scalar.ParseValue(p.val(spec.dest), value) + if err != nil { + return fmt.Errorf("error processing %s: %v", arg, err) + } + } + + // process positionals + for _, spec := range specs { + if !spec.positional { + continue + } + if len(positionals) == 0 { + break + } + wasPresent[spec] = true + if spec.cardinality == multiple { + err := setSliceOrMap(p.val(spec.dest), positionals, true) + if err != nil { + return fmt.Errorf("error processing %s: %v", spec.field.Name, err) + } + positionals = nil + } else { + err := scalar.ParseValue(p.val(spec.dest), positionals[0]) + if err != nil { + return fmt.Errorf("error processing %s: %v", spec.field.Name, err) + } + positionals = positionals[1:] + } + } + if len(positionals) > 0 { + return fmt.Errorf("too many positional arguments at '%s'", positionals[0]) + } + + // fill in defaults and check that all the required args were provided + for _, spec := range specs { + if wasPresent[spec] { + continue + } + + name := strings.ToLower(spec.field.Name) + if spec.long != "" && !spec.positional { + name = "--" + spec.long + } + + if spec.required { + if spec.short == "" && spec.long == "" { + msg := fmt.Sprintf("environment variable %s is required", spec.env) + return errors.New(msg) + } + + msg := fmt.Sprintf("%s is required", name) + if spec.env != "" { + msg += " (or environment variable " + spec.env + ")" + } + + return errors.New(msg) + } + + if spec.defaultValue.IsValid() && !p.config.IgnoreDefault { + // One issue here is that if the user now modifies the value then + // the default value stored in the spec will be corrupted. There + // is no general way to "deep-copy" values in Go, and we still + // support the old-style method for specifying defaults as + // Go values assigned directly to the struct field, so we are stuck. + p.val(spec.dest).Set(spec.defaultValue) + } + } + + return nil +} + +func nextIsNumeric(t reflect.Type, s string) bool { + switch t.Kind() { + case reflect.Ptr: + return nextIsNumeric(t.Elem(), s) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Float32, reflect.Float64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + v := reflect.New(t) + err := scalar.ParseValue(v, s) + return err == nil + default: + return false + } +} + +// isFlag returns true if a token is a flag such as "-v" or "--user" but not "-" or "--" +func isFlag(s string) bool { + return strings.HasPrefix(s, "-") && strings.TrimLeft(s, "-") != "" +} + +// val returns a reflect.Value corresponding to the current value for the +// given path +func (p *Parser) val(dest path) reflect.Value { + v := p.roots[dest.root] + for _, field := range dest.fields { + if v.Kind() == reflect.Ptr { + if v.IsNil() { + return reflect.Value{} + } + v = v.Elem() + } + + v = v.FieldByIndex(field.Index) + } + return v +} + +// findOption finds an option from its name, or returns null if no spec is found +func findOption(specs []*spec, name string) *spec { + for _, spec := range specs { + if spec.positional { + continue + } + if spec.long == name || spec.short == name { + return spec + } + } + return nil +} + +// findSubcommand finds a subcommand using its name, or returns null if no subcommand is found +func findSubcommand(cmds []*command, name string) *command { + for _, cmd := range cmds { + if cmd.name == name { + return cmd + } + for _, alias := range cmd.aliases { + if alias == name { + return cmd + } + } + } + return nil +} diff --git a/vendor/github.com/alexflint/go-arg/reflect.go b/vendor/github.com/alexflint/go-arg/reflect.go new file mode 100644 index 0000000..5d6acb3 --- /dev/null +++ b/vendor/github.com/alexflint/go-arg/reflect.go @@ -0,0 +1,112 @@ +package arg + +import ( + "encoding" + "fmt" + "reflect" + "unicode" + "unicode/utf8" + + scalar "github.com/alexflint/go-scalar" +) + +var textUnmarshalerType = reflect.TypeOf([]encoding.TextUnmarshaler{}).Elem() + +// cardinality tracks how many tokens are expected for a given spec +// - zero is a boolean, which does to expect any value +// - one is an ordinary option that will be parsed from a single token +// - multiple is a slice or map that can accept zero or more tokens +type cardinality int + +const ( + zero cardinality = iota + one + multiple + unsupported +) + +func (k cardinality) String() string { + switch k { + case zero: + return "zero" + case one: + return "one" + case multiple: + return "multiple" + case unsupported: + return "unsupported" + default: + return fmt.Sprintf("unknown(%d)", int(k)) + } +} + +// cardinalityOf returns true if the type can be parsed from a string +func cardinalityOf(t reflect.Type) (cardinality, error) { + if scalar.CanParse(t) { + if isBoolean(t) { + return zero, nil + } + return one, nil + } + + // look inside pointer types + if t.Kind() == reflect.Ptr { + t = t.Elem() + } + + // look inside slice and map types + switch t.Kind() { + case reflect.Slice: + if !scalar.CanParse(t.Elem()) { + return unsupported, fmt.Errorf("cannot parse into %v because %v not supported", t, t.Elem()) + } + return multiple, nil + case reflect.Map: + if !scalar.CanParse(t.Key()) { + return unsupported, fmt.Errorf("cannot parse into %v because key type %v not supported", t, t.Elem()) + } + if !scalar.CanParse(t.Elem()) { + return unsupported, fmt.Errorf("cannot parse into %v because value type %v not supported", t, t.Elem()) + } + return multiple, nil + default: + return unsupported, fmt.Errorf("cannot parse into %v", t) + } +} + +// isBoolean returns true if the type is a boolean or a pointer to a boolean +func isBoolean(t reflect.Type) bool { + switch { + case isTextUnmarshaler(t): + return false + case t.Kind() == reflect.Bool: + return true + case t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Bool: + return true + default: + return false + } +} + +// isTextUnmarshaler returns true if the type or its pointer implements encoding.TextUnmarshaler +func isTextUnmarshaler(t reflect.Type) bool { + return t.Implements(textUnmarshalerType) || reflect.PtrTo(t).Implements(textUnmarshalerType) +} + +// isExported returns true if the struct field name is exported +func isExported(field string) bool { + r, _ := utf8.DecodeRuneInString(field) // returns RuneError for empty string or invalid UTF8 + return unicode.IsLetter(r) && unicode.IsUpper(r) +} + +// isZero returns true if v contains the zero value for its type +func isZero(v reflect.Value) bool { + t := v.Type() + if t.Kind() == reflect.Ptr || t.Kind() == reflect.Slice || t.Kind() == reflect.Map || t.Kind() == reflect.Chan || t.Kind() == reflect.Interface { + return v.IsNil() + } + if !t.Comparable() { + return false + } + return v.Interface() == reflect.Zero(t).Interface() +} diff --git a/vendor/github.com/alexflint/go-arg/sequence.go b/vendor/github.com/alexflint/go-arg/sequence.go new file mode 100644 index 0000000..35a3614 --- /dev/null +++ b/vendor/github.com/alexflint/go-arg/sequence.go @@ -0,0 +1,123 @@ +package arg + +import ( + "fmt" + "reflect" + "strings" + + scalar "github.com/alexflint/go-scalar" +) + +// setSliceOrMap parses a sequence of strings into a slice or map. If clear is +// true then any values already in the slice or map are first removed. +func setSliceOrMap(dest reflect.Value, values []string, clear bool) error { + if !dest.CanSet() { + return fmt.Errorf("field is not writable") + } + + t := dest.Type() + if t.Kind() == reflect.Ptr { + dest = dest.Elem() + t = t.Elem() + } + + switch t.Kind() { + case reflect.Slice: + return setSlice(dest, values, clear) + case reflect.Map: + return setMap(dest, values, clear) + default: + return fmt.Errorf("setSliceOrMap cannot insert values into a %v", t) + } +} + +// setSlice parses a sequence of strings and inserts them into a slice. If clear +// is true then any values already in the slice are removed. +func setSlice(dest reflect.Value, values []string, clear bool) error { + var ptr bool + elem := dest.Type().Elem() + if elem.Kind() == reflect.Ptr && !elem.Implements(textUnmarshalerType) { + ptr = true + elem = elem.Elem() + } + + // clear the slice in case default values exist + if clear && !dest.IsNil() { + dest.SetLen(0) + } + + // parse the values one-by-one + for _, s := range values { + v := reflect.New(elem) + if err := scalar.ParseValue(v.Elem(), s); err != nil { + return err + } + if !ptr { + v = v.Elem() + } + dest.Set(reflect.Append(dest, v)) + } + return nil +} + +// setMap parses a sequence of name=value strings and inserts them into a map. +// If clear is true then any values already in the map are removed. +func setMap(dest reflect.Value, values []string, clear bool) error { + // determine the key and value type + var keyIsPtr bool + keyType := dest.Type().Key() + if keyType.Kind() == reflect.Ptr && !keyType.Implements(textUnmarshalerType) { + keyIsPtr = true + keyType = keyType.Elem() + } + + var valIsPtr bool + valType := dest.Type().Elem() + if valType.Kind() == reflect.Ptr && !valType.Implements(textUnmarshalerType) { + valIsPtr = true + valType = valType.Elem() + } + + // clear the slice in case default values exist + if clear && !dest.IsNil() { + for _, k := range dest.MapKeys() { + dest.SetMapIndex(k, reflect.Value{}) + } + } + + // allocate the map if it is not allocated + if dest.IsNil() { + dest.Set(reflect.MakeMap(dest.Type())) + } + + // parse the values one-by-one + for _, s := range values { + // split at the first equals sign + pos := strings.Index(s, "=") + if pos == -1 { + return fmt.Errorf("cannot parse %q into a map, expected format key=value", s) + } + + // parse the key + k := reflect.New(keyType) + if err := scalar.ParseValue(k.Elem(), s[:pos]); err != nil { + return err + } + if !keyIsPtr { + k = k.Elem() + } + + // parse the value + v := reflect.New(valType) + if err := scalar.ParseValue(v.Elem(), s[pos+1:]); err != nil { + return err + } + if !valIsPtr { + v = v.Elem() + } + + // add it to the map + dest.SetMapIndex(k, v) + } + return nil +} diff --git a/vendor/github.com/alexflint/go-arg/subcommand.go b/vendor/github.com/alexflint/go-arg/subcommand.go new file mode 100644 index 0000000..da6ed11 --- /dev/null +++ b/vendor/github.com/alexflint/go-arg/subcommand.go @@ -0,0 +1,43 @@ +package arg + +import "fmt" + +// Subcommand returns the user struct for the subcommand selected by +// the command line arguments most recently processed by the parser. +// The return value is always a pointer to a struct. If no subcommand +// was specified then it returns the top-level arguments struct. If +// no command line arguments have been processed by this parser then it +// returns nil. +func (p *Parser) Subcommand() interface{} { + if len(p.subcommand) == 0 { + return nil + } + cmd, err := p.lookupCommand(p.subcommand...) + if err != nil { + return nil + } + return p.val(cmd.dest).Interface() +} + +// SubcommandNames returns the sequence of subcommands specified by the +// user. If no subcommands were given then it returns an empty slice. +func (p *Parser) SubcommandNames() []string { + return p.subcommand +} + +// lookupCommand finds a subcommand based on a sequence of subcommand names. The +// first string should be a top-level subcommand, the next should be a child +// subcommand of that subcommand, and so on. If no strings are given then the +// root command is returned. If no such subcommand exists then an error is +// returned. +func (p *Parser) lookupCommand(path ...string) (*command, error) { + cmd := p.cmd + for _, name := range path { + found := findSubcommand(cmd.subcommands, name) + if found == nil { + return nil, fmt.Errorf("%q is not a subcommand of %s", name, cmd.name) + } + cmd = found + } + return cmd, nil +} diff --git a/vendor/github.com/alexflint/go-arg/usage.go b/vendor/github.com/alexflint/go-arg/usage.go new file mode 100644 index 0000000..6b578a5 --- /dev/null +++ b/vendor/github.com/alexflint/go-arg/usage.go @@ -0,0 +1,338 @@ +package arg + +import ( + "fmt" + "io" + "strings" +) + +// the width of the left column +const colWidth = 25 + +// Fail prints usage information to stderr and exits with non-zero status +func (p *Parser) Fail(msg string) { + p.FailSubcommand(msg) +} + +// FailSubcommand prints usage information for a specified subcommand to stderr, +// then exits with non-zero status. To write usage information for a top-level +// subcommand, provide just the name of that subcommand. To write usage +// information for a subcommand that is nested under another subcommand, provide +// a sequence of subcommand names starting with the top-level subcommand and so +// on down the tree. +func (p *Parser) FailSubcommand(msg string, subcommand ...string) error { + err := p.WriteUsageForSubcommand(p.config.Out, subcommand...) + if err != nil { + return err + } + + fmt.Fprintln(p.config.Out, "error:", msg) + p.config.Exit(-1) + return nil +} + +// WriteUsage writes usage information to the given writer +func (p *Parser) WriteUsage(w io.Writer) { + p.WriteUsageForSubcommand(w, p.subcommand...) +} + +// WriteUsageForSubcommand writes the usage information for a specified +// subcommand. To write usage information for a top-level subcommand, provide +// just the name of that subcommand. To write usage information for a subcommand +// that is nested under another subcommand, provide a sequence of subcommand +// names starting with the top-level subcommand and so on down the tree. +func (p *Parser) WriteUsageForSubcommand(w io.Writer, subcommand ...string) error { + cmd, err := p.lookupCommand(subcommand...) + if err != nil { + return err + } + + var positionals, longOptions, shortOptions []*spec + for _, spec := range cmd.specs { + switch { + case spec.positional: + positionals = append(positionals, spec) + case spec.long != "": + longOptions = append(longOptions, spec) + case spec.short != "": + shortOptions = append(shortOptions, spec) + } + } + + if p.version != "" { + fmt.Fprintln(w, p.version) + } + + // print the beginning of the usage string + fmt.Fprintf(w, "Usage: %s", p.cmd.name) + for _, s := range subcommand { + fmt.Fprint(w, " "+s) + } + + // write the option component of the usage message + for _, spec := range shortOptions { + // prefix with a space + fmt.Fprint(w, " ") + if !spec.required { + fmt.Fprint(w, "[") + } + fmt.Fprint(w, synopsis(spec, "-"+spec.short)) + if !spec.required { + fmt.Fprint(w, "]") + } + } + + for _, spec := range longOptions { + // prefix with a space + fmt.Fprint(w, " ") + if !spec.required { + fmt.Fprint(w, "[") + } + fmt.Fprint(w, synopsis(spec, "--"+spec.long)) + if !spec.required { + fmt.Fprint(w, "]") + } + } + + // When we parse positionals, we check that: + // 1. required positionals come before non-required positionals + // 2. there is at most one multiple-value positional + // 3. if there is a multiple-value positional then it comes after all other positionals + // Here we merely print the usage string, so we do not explicitly re-enforce those rules + + // write the positionals in following form: + // REQUIRED1 REQUIRED2 + // REQUIRED1 REQUIRED2 [OPTIONAL1 [OPTIONAL2]] + // REQUIRED1 REQUIRED2 REPEATED [REPEATED ...] + // REQUIRED1 REQUIRED2 [REPEATEDOPTIONAL [REPEATEDOPTIONAL ...]] + // REQUIRED1 REQUIRED2 [OPTIONAL1 [REPEATEDOPTIONAL [REPEATEDOPTIONAL ...]]] + var closeBrackets int + for _, spec := range positionals { + fmt.Fprint(w, " ") + if !spec.required { + fmt.Fprint(w, "[") + closeBrackets += 1 + } + if spec.cardinality == multiple { + fmt.Fprintf(w, "%s [%s ...]", spec.placeholder, spec.placeholder) + } else { + fmt.Fprint(w, spec.placeholder) + } + } + fmt.Fprint(w, strings.Repeat("]", closeBrackets)) + + // if the program supports subcommands, give a hint to the user about their existence + if len(cmd.subcommands) > 0 { + fmt.Fprint(w, " []") + } + + fmt.Fprint(w, "\n") + return nil +} + +// print prints a line like this: +// +// --option FOO A description of the option [default: 123] +// +// If the text on the left is longer than a certain threshold, the description is moved to the next line: +// +// --verylongoptionoption VERY_LONG_VARIABLE +// A description of the option [default: 123] +// +// If multiple "extras" are provided then they are put inside a single set of square brackets: +// +// --option FOO A description of the option [default: 123, env: FOO] +func print(w io.Writer, item, description string, bracketed ...string) { + lhs := " " + item + fmt.Fprint(w, lhs) + if description != "" { + if len(lhs)+2 < colWidth { + fmt.Fprint(w, strings.Repeat(" ", colWidth-len(lhs))) + } else { + fmt.Fprint(w, "\n"+strings.Repeat(" ", colWidth)) + } + fmt.Fprint(w, description) + } + + var brack string + for _, s := range bracketed { + if s != "" { + if brack != "" { + brack += ", " + } + brack += s + } + } + + if brack != "" { + fmt.Fprintf(w, " [%s]", brack) + } + fmt.Fprint(w, "\n") +} + +func withDefault(s string) string { + if s == "" { + return "" + } + return "default: " + s +} + +func withEnv(env string) string { + if env == "" { + return "" + } + return "env: " + env +} + +// WriteHelp writes the usage string followed by the full help string for each option +func (p *Parser) WriteHelp(w io.Writer) { + p.WriteHelpForSubcommand(w, p.subcommand...) +} + +// WriteHelpForSubcommand writes the usage string followed by the full help +// string for a specified subcommand. To write help for a top-level subcommand, +// provide just the name of that subcommand. To write help for a subcommand that +// is nested under another subcommand, provide a sequence of subcommand names +// starting with the top-level subcommand and so on down the tree. +func (p *Parser) WriteHelpForSubcommand(w io.Writer, subcommand ...string) error { + cmd, err := p.lookupCommand(subcommand...) + if err != nil { + return err + } + + var positionals, longOptions, shortOptions, envOnlyOptions []*spec + var hasVersionOption bool + for _, spec := range cmd.specs { + switch { + case spec.positional: + positionals = append(positionals, spec) + case spec.long != "": + longOptions = append(longOptions, spec) + case spec.short != "": + shortOptions = append(shortOptions, spec) + case spec.short == "" && spec.long == "": + envOnlyOptions = append(envOnlyOptions, spec) + } + } + + if p.description != "" { + fmt.Fprintln(w, p.description) + } + p.WriteUsageForSubcommand(w, subcommand...) + + // write the list of positionals + if len(positionals) > 0 { + fmt.Fprint(w, "\nPositional arguments:\n") + for _, spec := range positionals { + print(w, spec.placeholder, spec.help) + } + } + + // write the list of options with the short-only ones first to match the usage string + if len(shortOptions)+len(longOptions) > 0 || cmd.parent == nil { + fmt.Fprint(w, "\nOptions:\n") + for _, spec := range shortOptions { + p.printOption(w, spec) + } + for _, spec := range longOptions { + p.printOption(w, spec) + if spec.long == "version" { + hasVersionOption = true + } + } + } + + // obtain a flattened list of options from all ancestors + var globals []*spec + ancestor := cmd.parent + for ancestor != nil { + globals = append(globals, ancestor.specs...) + ancestor = ancestor.parent + } + + // write the list of global options + if len(globals) > 0 { + fmt.Fprint(w, "\nGlobal options:\n") + for _, spec := range globals { + p.printOption(w, spec) + if spec.long == "version" { + hasVersionOption = true + } + } + } + + // write the list of built in options + p.printOption(w, &spec{ + cardinality: zero, + long: "help", + short: "h", + help: "display this help and exit", + }) + if !hasVersionOption && p.version != "" { + p.printOption(w, &spec{ + cardinality: zero, + long: "version", + help: "display version and exit", + }) + } + + // write the list of environment only variables + if len(envOnlyOptions) > 0 { + fmt.Fprint(w, "\nEnvironment variables:\n") + for _, spec := range envOnlyOptions { + p.printEnvOnlyVar(w, spec) + } + } + + // write the list of subcommands + if len(cmd.subcommands) > 0 { + fmt.Fprint(w, "\nCommands:\n") + for _, subcmd := range cmd.subcommands { + names := append([]string{subcmd.name}, subcmd.aliases...) + print(w, strings.Join(names, ", "), subcmd.help) + } + } + + if p.epilogue != "" { + fmt.Fprintln(w, "\n"+p.epilogue) + } + return nil +} + +func (p *Parser) printOption(w io.Writer, spec *spec) { + ways := make([]string, 0, 2) + if spec.long != "" { + ways = append(ways, synopsis(spec, "--"+spec.long)) + } + if spec.short != "" { + ways = append(ways, synopsis(spec, "-"+spec.short)) + } + if len(ways) > 0 { + print(w, strings.Join(ways, ", "), spec.help, withDefault(spec.defaultString), withEnv(spec.env)) + } +} + +func (p *Parser) printEnvOnlyVar(w io.Writer, spec *spec) { + ways := make([]string, 0, 2) + if spec.required { + ways = append(ways, "Required.") + } else { + ways = append(ways, "Optional.") + } + + if spec.help != "" { + ways = append(ways, spec.help) + } + + print(w, spec.env, strings.Join(ways, " "), withDefault(spec.defaultString)) +} + +func synopsis(spec *spec, form string) string { + // if the user omits the placeholder tag then we pick one automatically, + // but if the user explicitly specifies an empty placeholder then we + // leave out the placeholder in the help message + if spec.cardinality == zero || spec.placeholder == "" { + return form + } + return form + " " + spec.placeholder +} diff --git a/vendor/github.com/alexflint/go-scalar/.gitignore b/vendor/github.com/alexflint/go-scalar/.gitignore new file mode 100644 index 0000000..daf913b --- /dev/null +++ b/vendor/github.com/alexflint/go-scalar/.gitignore @@ -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 diff --git a/vendor/github.com/alexflint/go-scalar/LICENSE b/vendor/github.com/alexflint/go-scalar/LICENSE new file mode 100644 index 0000000..a50c494 --- /dev/null +++ b/vendor/github.com/alexflint/go-scalar/LICENSE @@ -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. + diff --git a/vendor/github.com/alexflint/go-scalar/README.md b/vendor/github.com/alexflint/go-scalar/README.md new file mode 100644 index 0000000..e6f510e --- /dev/null +++ b/vendor/github.com/alexflint/go-scalar/README.md @@ -0,0 +1,28 @@ +[![GoDoc](https://godoc.org/github.com/alexflint/go-scalar?status.svg)](https://godoc.org/github.com/alexflint/go-scalar) +[![Build Status](https://travis-ci.org/alexflint/go-scalar.svg?branch=master)](https://travis-ci.org/alexflint/go-scalar) +[![Coverage Status](https://coveralls.io/repos/alexflint/go-scalar/badge.svg?branch=master&service=github)](https://coveralls.io/github/alexflint/go-scalar?branch=master) +[![Report Card](https://goreportcard.com/badge/github.com/alexflint/go-scalar)](https://goreportcard.com/badge/github.com/alexflint/go-scalar) + +## Scalar parsing library + +Scalar is a library for parsing strings into arbitrary scalars (integers, +floats, strings, booleans, etc). It is helpful for tasks such as parsing +strings passed as environment variables or command line arguments. + +```shell +go get github.com/alexflint/go-scalar +``` + +The main API works as follows: + +```go +var value int +err := scalar.Parse(&value, "123") +``` + +There is also a variant that takes a `reflect.Value`: + +```go +var value int +err := scalar.ParseValue(reflect.ValueOf(&value), "123") +``` diff --git a/vendor/github.com/alexflint/go-scalar/scalar.go b/vendor/github.com/alexflint/go-scalar/scalar.go new file mode 100644 index 0000000..0c17cd4 --- /dev/null +++ b/vendor/github.com/alexflint/go-scalar/scalar.go @@ -0,0 +1,162 @@ +// Package scalar parses strings into values of scalar type. + +package scalar + +import ( + "encoding" + "errors" + "fmt" + "net" + "net/mail" + "net/url" + "reflect" + "strconv" + "time" +) + +// The reflected form of some special types +var ( + textUnmarshalerType = reflect.TypeOf([]encoding.TextUnmarshaler{}).Elem() + durationType = reflect.TypeOf(time.Duration(0)) + mailAddressType = reflect.TypeOf(mail.Address{}) + macType = reflect.TypeOf(net.HardwareAddr{}) + urlType = reflect.TypeOf(url.URL{}) +) + +var ( + errNotSettable = errors.New("value is not settable") + errPtrNotSettable = errors.New("value is a nil pointer and is not settable") +) + +// Parse assigns a value to v by parsing s. +func Parse(dest interface{}, s string) error { + return ParseValue(reflect.ValueOf(dest), s) +} + +// ParseValue assigns a value to v by parsing s. +func ParseValue(v reflect.Value, s string) error { + // If we have a nil pointer then allocate a new object + if v.Kind() == reflect.Ptr && v.IsNil() { + if !v.CanSet() { + return errPtrNotSettable + } + + v.Set(reflect.New(v.Type().Elem())) + } + + // If it implements encoding.TextUnmarshaler then use that + if scalar, ok := v.Interface().(encoding.TextUnmarshaler); ok { + return scalar.UnmarshalText([]byte(s)) + } + // If it's a value instead of a pointer, check that we can unmarshal it + // via TextUnmarshaler as well + if v.CanAddr() { + if scalar, ok := v.Addr().Interface().(encoding.TextUnmarshaler); ok { + return scalar.UnmarshalText([]byte(s)) + } + } + + // If we have a pointer then dereference it + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + + if !v.CanSet() { + return errNotSettable + } + + // Switch on concrete type + switch scalar := v.Interface(); scalar.(type) { + case time.Duration: + duration, err := time.ParseDuration(s) + if err != nil { + return err + } + v.Set(reflect.ValueOf(duration)) + return nil + case mail.Address: + addr, err := mail.ParseAddress(s) + if err != nil { + return err + } + v.Set(reflect.ValueOf(*addr)) + return nil + case net.HardwareAddr: + ip, err := net.ParseMAC(s) + if err != nil { + return err + } + v.Set(reflect.ValueOf(ip)) + return nil + case url.URL: + url, err := url.Parse(s) + if err != nil { + return err + } + v.Set(reflect.ValueOf(*url)) + return nil + } + + // Switch on kind so that we can handle derived types + switch v.Kind() { + case reflect.String: + v.SetString(s) + case reflect.Bool: + x, err := strconv.ParseBool(s) + if err != nil { + return err + } + v.SetBool(x) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + x, err := strconv.ParseInt(s, 0, v.Type().Bits()) + if err != nil { + return err + } + v.SetInt(x) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + x, err := strconv.ParseUint(s, 0, v.Type().Bits()) + if err != nil { + return err + } + v.SetUint(x) + case reflect.Float32, reflect.Float64: + x, err := strconv.ParseFloat(s, v.Type().Bits()) + if err != nil { + return err + } + v.SetFloat(x) + default: + return fmt.Errorf("cannot parse into %v", v.Type()) + } + return nil +} + +// CanParse returns true if the type can be parsed from a string. +func CanParse(t reflect.Type) bool { + // If it implements encoding.TextUnmarshaler then use that + if t.Implements(textUnmarshalerType) || reflect.PtrTo(t).Implements(textUnmarshalerType) { + return true + } + + // If we have a pointer then dereference it + if t.Kind() == reflect.Ptr { + t = t.Elem() + } + + // Check for other special types + switch t { + case durationType, mailAddressType, macType, urlType: + return true + } + + // Fall back to checking the kind + switch t.Kind() { + case reflect.Bool: + return true + case reflect.String, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, + reflect.Float32, reflect.Float64: + return true + } + return false +} diff --git a/vendor/modules.txt b/vendor/modules.txt new file mode 100644 index 0000000..9af7e7d --- /dev/null +++ b/vendor/modules.txt @@ -0,0 +1,6 @@ +# github.com/alexflint/go-arg v1.5.1 +## explicit; go 1.18 +github.com/alexflint/go-arg +# github.com/alexflint/go-scalar v1.2.0 +## explicit; go 1.15 +github.com/alexflint/go-scalar