From bfa6c440f58e52d2633747eb5fcdcaf112d69a4c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 May 2024 07:17:54 +0000 Subject: [PATCH] Go Dependency: Bump github.com/alexflint/go-arg from 1.4.3 to 1.5.0 Bumps [github.com/alexflint/go-arg](https://github.com/alexflint/go-arg) from 1.4.3 to 1.5.0. - [Release notes](https://github.com/alexflint/go-arg/releases) - [Commits](https://github.com/alexflint/go-arg/compare/v1.4.3...v1.5.0) --- updated-dependencies: - dependency-name: github.com/alexflint/go-arg dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 5 +- vendor/github.com/alexflint/go-arg/README.md | 54 +++- vendor/github.com/alexflint/go-arg/parse.go | 281 +++++++++++++----- vendor/github.com/alexflint/go-arg/reflect.go | 17 +- .../github.com/alexflint/go-arg/subcommand.go | 42 +-- vendor/github.com/alexflint/go-arg/usage.go | 185 ++++++------ vendor/modules.txt | 4 +- 8 files changed, 388 insertions(+), 202 deletions(-) diff --git a/go.mod b/go.mod index 47c8e78..d7be760 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ module github.com/atc0005/check-illiad go 1.19 require ( - github.com/alexflint/go-arg v1.4.3 + github.com/alexflint/go-arg v1.5.0 github.com/atc0005/go-nagios v0.16.1 github.com/denisenkom/go-mssqldb v0.12.3 github.com/rs/zerolog v1.32.0 diff --git a/go.sum b/go.sum index ca1621c..c162dc3 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,8 @@ github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw= github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0= github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8= -github.com/alexflint/go-arg v1.4.3 h1:9rwwEBpMXfKQKceuZfYcwuc/7YY7tWJbFsgG5cAU/uo= -github.com/alexflint/go-arg v1.4.3/go.mod h1:3PZ/wp/8HuqRZMUUgu7I+e1qcpUbvmS258mRXkFH4IA= -github.com/alexflint/go-scalar v1.1.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o= +github.com/alexflint/go-arg v1.5.0 h1:rwMKGiaQuRbXfZNyRUvIfke63QvOBt1/QTshlGQHohM= +github.com/alexflint/go-arg v1.5.0/go.mod h1:A7vTJzvjoaSTypg4biM5uYNTkJ27SkNTArtYXnlqVO8= github.com/alexflint/go-scalar v1.2.0 h1:WR7JPKkeNpnYIOfHRa7ivM21aWAdHD0gEWHCx+WQBRw= github.com/alexflint/go-scalar v1.2.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o= github.com/atc0005/go-nagios v0.16.1 h1:ef0AWjY9sqWq6dhfJuXtASe7dCkVDonoZhtYvNYWBlo= diff --git a/vendor/github.com/alexflint/go-arg/README.md b/vendor/github.com/alexflint/go-arg/README.md index dab2996..f105b17 100644 --- a/vendor/github.com/alexflint/go-arg/README.md +++ b/vendor/github.com/alexflint/go-arg/README.md @@ -134,10 +134,10 @@ arg.MustParse(&args) ```shell $ ./example -h -Usage: [--verbose] [--dataset DATASET] [--optimize OPTIMIZE] [--help] INPUT [OUTPUT [OUTPUT ...]] +Usage: [--verbose] [--dataset DATASET] [--optimize OPTIMIZE] [--help] INPUT [OUTPUT [OUTPUT ...]] Positional arguments: - INPUT + INPUT OUTPUT Options: @@ -180,6 +180,24 @@ var args struct { 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 { @@ -444,6 +462,9 @@ Options: ### 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 @@ -469,6 +490,35 @@ Options: --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* diff --git a/vendor/github.com/alexflint/go-arg/parse.go b/vendor/github.com/alexflint/go-arg/parse.go index 7588dfb..251b005 100644 --- a/vendor/github.com/alexflint/go-arg/parse.go +++ b/vendor/github.com/alexflint/go-arg/parse.go @@ -5,6 +5,7 @@ import ( "encoding/csv" "errors" "fmt" + "io" "os" "path/filepath" "reflect" @@ -43,23 +44,25 @@ func (p path) Child(f reflect.StructField) path { // 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 - defaultVal string // default value for this option - placeholder string // name of the data in help + 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 @@ -67,33 +70,30 @@ type command struct { parent *command } -// ErrHelp indicates that -h or --help were provided +// ErrHelp indicates that the builtin -h or --help were provided var ErrHelp = errors.New("help requested by user") -// ErrVersion indicates that --version was provided +// ErrVersion indicates that the builtin --version was provided var ErrVersion = errors.New("version requested by user") +// for monkey patching in example code +var mustParseExit = os.Exit + // MustParse processes command line arguments and exits upon failure func MustParse(dest ...interface{}) *Parser { - p, err := NewParser(Config{}, dest...) - if err != nil { - fmt.Fprintln(stdout, err) - osExit(-1) - return nil // just in case osExit was monkey-patched - } + return mustParse(Config{Exit: mustParseExit}, dest...) +} - err = p.Parse(flags()) - switch { - case err == ErrHelp: - p.writeHelpForSubcommand(stdout, p.lastCmd) - osExit(0) - case err == ErrVersion: - fmt.Fprintln(stdout, p.version) - osExit(0) - case err != nil: - p.failWithSubcommand(err.Error(), p.lastCmd) +// 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 } @@ -121,6 +121,20 @@ type Config struct { // 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 @@ -130,9 +144,10 @@ type Parser struct { config Config version string description string + epilogue string // the following field changes during processing of command line arguments - lastCmd *command + subcommand []string } // Versioned is the interface that the destination struct should implement to @@ -151,6 +166,14 @@ type Described interface { 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) @@ -174,6 +197,14 @@ func walkFieldsImpl(t reflect.Type, visit func(field reflect.StructField, owner // 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 { @@ -208,18 +239,31 @@ func NewParser(config Config, dests ...interface{}) (*Parser, error) { return nil, err } - // add nonzero field values as defaults + // 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 { - if v := p.val(spec.dest); v.IsValid() && !isZero(v) { - if defaultVal, ok := v.Interface().(encoding.TextMarshaler); ok { - str, err := defaultVal.MarshalText() - if err != nil { - return nil, fmt.Errorf("%v: error marshaling default value to string: %v", spec.dest, err) - } - spec.defaultVal = string(str) - } else { - spec.defaultVal = fmt.Sprintf("%v", v) + // 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) } } @@ -232,6 +276,9 @@ func NewParser(config Config, dests ...interface{}) (*Parser, error) { if dest, ok := dest.(Described); ok { p.description = dest.Description() } + if dest, ok := dest.(Epilogued); ok { + p.epilogue = dest.Epilogue() + } } return &p, nil @@ -288,13 +335,8 @@ func cmdFromStruct(name string, dest path, t reflect.Type) (*command, error) { spec.help = help } - defaultVal, hasDefault := field.Tag.Lookup("default") - if hasDefault { - spec.defaultVal = defaultVal - } - - // Look at the tag - var isSubcommand bool // tracks whether this field is a subcommand + // process each comma-separated part of the tag + var isSubcommand bool for _, key := range strings.Split(tag, ",") { if key == "" { continue @@ -312,18 +354,13 @@ func cmdFromStruct(name string, dest path, t reflect.Type) (*command, error) { case strings.HasPrefix(key, "--"): spec.long = key[2:] case strings.HasPrefix(key, "-"): - if len(key) != 2 { + 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": - if hasDefault { - errs = append(errs, fmt.Sprintf("%s.%s: 'required' cannot be used when a default value is specified", - t.Name(), field.Name)) - return false - } spec.required = true case key == "positional": spec.positional = true @@ -340,18 +377,24 @@ func cmdFromStruct(name string, dest path, t reflect.Type) (*command, error) { } case key == "subcommand": // decide on a name for the subcommand - cmdname := value - if cmdname == "" { - cmdname = strings.ToLower(field.Name) + 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(cmdname, subdest, field.Type) + 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") @@ -363,6 +406,7 @@ func cmdFromStruct(name string, dest path, t reflect.Type) (*command, error) { } } + // placeholder is the string used in the help text like this: "--somearg PLACEHOLDER" placeholder, hasPlaceholder := field.Tag.Lookup("placeholder") if hasPlaceholder { spec.placeholder = placeholder @@ -372,27 +416,60 @@ func cmdFromStruct(name string, dest path, t reflect.Type) (*command, error) { spec.placeholder = strings.ToUpper(spec.field.Name) } - // Check whether this field is supported. It's good to do this here rather than + // 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. - if !isSubcommand { - cmd.specs = append(cmd.specs, &spec) + 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 + } - 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())) + 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 } - if spec.cardinality == multiple && hasDefault { - errs = append(errs, fmt.Sprintf("%s.%s: default values are not supported for slice or map fields", + + // 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 }) @@ -433,6 +510,20 @@ func (p *Parser) Parse(args []string) error { 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 { @@ -486,7 +577,7 @@ func (p *Parser) process(args []string) error { // union of specs for the chain of subcommands encountered so far curCmd := p.cmd - p.lastCmd = curCmd + 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)) @@ -500,6 +591,15 @@ func (p *Parser) process(args []string) error { } } + // 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 @@ -527,10 +627,17 @@ func (p *Parser) process(args []string) error { // instantiate the field to point to a new struct v := p.val(subcmd.dest) - v.Set(reflect.New(v.Type().Elem())) // we already checked that all subcommands are struct pointers + 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 - specs = append(specs, subcmd.specs...) + 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 { @@ -541,7 +648,7 @@ func (p *Parser) process(args []string) error { } curCmd = subcmd - p.lastCmd = curCmd + p.subcommand = append(p.subcommand, arg) continue } @@ -550,7 +657,9 @@ func (p *Parser) process(args []string) error { case "-h", "--help": return ErrHelp case "--version": - return ErrVersion + if !hasVersionOption && p.version != "" { + return ErrVersion + } } // check for an equals sign, as in "--foo=bar" @@ -564,7 +673,7 @@ func (p *Parser) process(args []string) error { // 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 { + if spec == nil || opt == "" { return fmt.Errorf("unknown argument %s", arg) } wasPresent[spec] = true @@ -653,17 +762,26 @@ func (p *Parser) process(args []string) error { } 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.defaultVal != "" { - err := scalar.ParseValue(p.val(spec.dest), spec.defaultVal) - if err != nil { - return fmt.Errorf("error processing default value for %s: %v", name, err) - } + + 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) } } @@ -724,6 +842,11 @@ func findSubcommand(cmds []*command, name string) *command { 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 index cd80be7..5d6acb3 100644 --- a/vendor/github.com/alexflint/go-arg/reflect.go +++ b/vendor/github.com/alexflint/go-arg/reflect.go @@ -13,9 +13,9 @@ import ( 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 +// - 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 ( @@ -74,10 +74,10 @@ func cardinalityOf(t reflect.Type) (cardinality, error) { } } -// isBoolean returns true if the type can be parsed from a single string +// isBoolean returns true if the type is a boolean or a pointer to a boolean func isBoolean(t reflect.Type) bool { switch { - case t.Implements(textUnmarshalerType): + case isTextUnmarshaler(t): return false case t.Kind() == reflect.Bool: return true @@ -88,6 +88,11 @@ func isBoolean(t reflect.Type) bool { } } +// 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 @@ -97,7 +102,7 @@ func isExported(field string) bool { // 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.Slice || t.Kind() == reflect.Map { + 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() { diff --git a/vendor/github.com/alexflint/go-arg/subcommand.go b/vendor/github.com/alexflint/go-arg/subcommand.go index dff732c..da6ed11 100644 --- a/vendor/github.com/alexflint/go-arg/subcommand.go +++ b/vendor/github.com/alexflint/go-arg/subcommand.go @@ -1,5 +1,7 @@ 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 @@ -7,31 +9,35 @@ package arg // no command line arguments have been processed by this parser then it // returns nil. func (p *Parser) Subcommand() interface{} { - if p.lastCmd == nil || p.lastCmd.parent == nil { + if len(p.subcommand) == 0 { + return nil + } + cmd, err := p.lookupCommand(p.subcommand...) + if err != nil { return nil } - return p.val(p.lastCmd.dest).Interface() + 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 { - if p.lastCmd == nil { - return nil - } - - // make a list of ancestor commands - var ancestors []string - cur := p.lastCmd - for cur.parent != nil { // we want to exclude the root - ancestors = append(ancestors, cur.name) - cur = cur.parent - } + return p.subcommand +} - // reverse the list - out := make([]string, len(ancestors)) - for i := 0; i < len(ancestors); i++ { - out[i] = ancestors[len(ancestors)-i-1] +// 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 out + return cmd, nil } diff --git a/vendor/github.com/alexflint/go-arg/usage.go b/vendor/github.com/alexflint/go-arg/usage.go index e936811..6b578a5 100644 --- a/vendor/github.com/alexflint/go-arg/usage.go +++ b/vendor/github.com/alexflint/go-arg/usage.go @@ -3,23 +3,15 @@ package arg import ( "fmt" "io" - "os" "strings" ) // the width of the left column const colWidth = 25 -// to allow monkey patching in tests -var ( - stdout io.Writer = os.Stdout - stderr io.Writer = os.Stderr - osExit = os.Exit -) - // Fail prints usage information to stderr and exits with non-zero status func (p *Parser) Fail(msg string) { - p.failWithSubcommand(msg, p.cmd) + p.FailSubcommand(msg) } // FailSubcommand prints usage information for a specified subcommand to stderr, @@ -29,28 +21,19 @@ func (p *Parser) Fail(msg string) { // 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 { - cmd, err := p.lookupCommand(subcommand...) + err := p.WriteUsageForSubcommand(p.config.Out, subcommand...) if err != nil { return err } - p.failWithSubcommand(msg, cmd) - return nil -} -// failWithSubcommand prints usage information for the given subcommand to stderr and exits with non-zero status -func (p *Parser) failWithSubcommand(msg string, cmd *command) { - p.writeUsageForSubcommand(stderr, cmd) - fmt.Fprintln(stderr, "error:", msg) - osExit(-1) + 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) { - cmd := p.cmd - if p.lastCmd != nil { - cmd = p.lastCmd - } - p.writeUsageForSubcommand(w, cmd) + p.WriteUsageForSubcommand(w, p.subcommand...) } // WriteUsageForSubcommand writes the usage information for a specified @@ -63,12 +46,7 @@ func (p *Parser) WriteUsageForSubcommand(w io.Writer, subcommand ...string) erro if err != nil { return err } - p.writeUsageForSubcommand(w, cmd) - return nil -} -// writeUsageForSubcommand writes usage information for the given subcommand -func (p *Parser) writeUsageForSubcommand(w io.Writer, cmd *command) { var positionals, longOptions, shortOptions []*spec for _, spec := range cmd.specs { switch { @@ -85,18 +63,10 @@ func (p *Parser) writeUsageForSubcommand(w io.Writer, cmd *command) { fmt.Fprintln(w, p.version) } - // make a list of ancestor commands so that we print with full context - var ancestors []string - ancestor := cmd - for ancestor != nil { - ancestors = append(ancestors, ancestor.name) - ancestor = ancestor.parent - } - // print the beginning of the usage string - fmt.Fprint(w, "Usage:") - for i := len(ancestors) - 1; i >= 0; i-- { - fmt.Fprint(w, " "+ancestors[i]) + fmt.Fprintf(w, "Usage: %s", p.cmd.name) + for _, s := range subcommand { + fmt.Fprint(w, " "+s) } // write the option component of the usage message @@ -157,47 +127,66 @@ func (p *Parser) writeUsageForSubcommand(w io.Writer, cmd *command) { } fmt.Fprint(w, "\n") + return nil } -func printTwoCols(w io.Writer, left, help string, defaultVal string, envVal string) { - lhs := " " + left +// 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 help != "" { + 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, help) + fmt.Fprint(w, description) } - bracketsContent := []string{} + var brack string + for _, s := range bracketed { + if s != "" { + if brack != "" { + brack += ", " + } + brack += s + } + } - if defaultVal != "" { - bracketsContent = append(bracketsContent, - fmt.Sprintf("default: %s", defaultVal), - ) + if brack != "" { + fmt.Fprintf(w, " [%s]", brack) } + fmt.Fprint(w, "\n") +} - if envVal != "" { - bracketsContent = append(bracketsContent, - fmt.Sprintf("env: %s", envVal), - ) +func withDefault(s string) string { + if s == "" { + return "" } + return "default: " + s +} - if len(bracketsContent) > 0 { - fmt.Fprintf(w, " [%s]", strings.Join(bracketsContent, ", ")) +func withEnv(env string) string { + if env == "" { + return "" } - fmt.Fprint(w, "\n") + return "env: " + env } // WriteHelp writes the usage string followed by the full help string for each option func (p *Parser) WriteHelp(w io.Writer) { - cmd := p.cmd - if p.lastCmd != nil { - cmd = p.lastCmd - } - p.writeHelpForSubcommand(w, cmd) + p.WriteHelpForSubcommand(w, p.subcommand...) } // WriteHelpForSubcommand writes the usage string followed by the full help @@ -210,13 +199,9 @@ func (p *Parser) WriteHelpForSubcommand(w io.Writer, subcommand ...string) error if err != nil { return err } - p.writeHelpForSubcommand(w, cmd) - return nil -} -// writeHelp writes the usage string for the given subcommand -func (p *Parser) writeHelpForSubcommand(w io.Writer, cmd *command) { - var positionals, longOptions, shortOptions []*spec + var positionals, longOptions, shortOptions, envOnlyOptions []*spec + var hasVersionOption bool for _, spec := range cmd.specs { switch { case spec.positional: @@ -225,19 +210,21 @@ func (p *Parser) writeHelpForSubcommand(w io.Writer, cmd *command) { 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, cmd) + p.WriteUsageForSubcommand(w, subcommand...) // write the list of positionals if len(positionals) > 0 { fmt.Fprint(w, "\nPositional arguments:\n") for _, spec := range positionals { - printTwoCols(w, spec.placeholder, spec.help, "", "") + print(w, spec.placeholder, spec.help) } } @@ -249,6 +236,9 @@ func (p *Parser) writeHelpForSubcommand(w io.Writer, cmd *command) { } for _, spec := range longOptions { p.printOption(w, spec) + if spec.long == "version" { + hasVersionOption = true + } } } @@ -265,6 +255,9 @@ func (p *Parser) writeHelpForSubcommand(w io.Writer, cmd *command) { fmt.Fprint(w, "\nGlobal options:\n") for _, spec := range globals { p.printOption(w, spec) + if spec.long == "version" { + hasVersionOption = true + } } } @@ -275,7 +268,7 @@ func (p *Parser) writeHelpForSubcommand(w io.Writer, cmd *command) { short: "h", help: "display this help and exit", }) - if p.version != "" { + if !hasVersionOption && p.version != "" { p.printOption(w, &spec{ cardinality: zero, long: "version", @@ -283,13 +276,27 @@ func (p *Parser) writeHelpForSubcommand(w io.Writer, cmd *command) { }) } + // 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 { - printTwoCols(w, subcmd.name, subcmd.help, "", "") + 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) { @@ -301,34 +308,30 @@ func (p *Parser) printOption(w io.Writer, spec *spec) { ways = append(ways, synopsis(spec, "-"+spec.short)) } if len(ways) > 0 { - printTwoCols(w, strings.Join(ways, ", "), spec.help, spec.defaultVal, spec.env) + print(w, strings.Join(ways, ", "), spec.help, withDefault(spec.defaultString), withEnv(spec.env)) } } -// 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 { - var found *command - for _, child := range cmd.subcommands { - if child.name == name { - found = child - } - } - if found == nil { - return nil, fmt.Errorf("%q is not a subcommand of %s", name, cmd.name) - } - cmd = found +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.") } - return cmd, nil + + 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 spec.cardinality == zero { + // 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/modules.txt b/vendor/modules.txt index 865fd2f..f4c701a 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,5 +1,5 @@ -# github.com/alexflint/go-arg v1.4.3 -## explicit; go 1.13 +# github.com/alexflint/go-arg v1.5.0 +## explicit; go 1.18 github.com/alexflint/go-arg # github.com/alexflint/go-scalar v1.2.0 ## explicit; go 1.15