update go vendors

This commit is contained in:
cato-001 2025-08-23 14:52:27 +02:00
parent f62531accb
commit 6d35ec5210
4 changed files with 329 additions and 107 deletions

View file

@ -64,7 +64,7 @@ fmt.Println("Input:", args.Input)
fmt.Println("Output:", args.Output) fmt.Println("Output:", args.Output)
``` ```
``` ```shell
$ ./example src.txt x.out y.out z.out $ ./example src.txt x.out y.out z.out
Input: src.txt Input: src.txt
Output: [x.out y.out z.out] Output: [x.out y.out z.out]
@ -80,12 +80,12 @@ arg.MustParse(&args)
fmt.Println("Workers:", args.Workers) fmt.Println("Workers:", args.Workers)
``` ```
``` ```shell
$ WORKERS=4 ./example $ WORKERS=4 ./example
Workers: 4 Workers: 4
``` ```
``` ```shell
$ WORKERS=4 ./example --workers=6 $ WORKERS=4 ./example --workers=6
Workers: 6 Workers: 6
``` ```
@ -100,12 +100,12 @@ arg.MustParse(&args)
fmt.Println("Workers:", args.Workers) fmt.Println("Workers:", args.Workers)
``` ```
``` ```shell
$ NUM_WORKERS=4 ./example $ NUM_WORKERS=4 ./example
Workers: 4 Workers: 4
``` ```
You can provide multiple values using the CSV (RFC 4180) format: You can provide multiple values in environment variables using commas:
```go ```go
var args struct { var args struct {
@ -115,12 +115,50 @@ arg.MustParse(&args)
fmt.Println("Workers:", args.Workers) fmt.Println("Workers:", args.Workers)
``` ```
``` ```shell
$ WORKERS='1,99' ./example $ WORKERS='1,99' ./example
Workers: [1 99] Workers: [1 99]
``` ```
Command line arguments take precedence over environment variables:
```go
var args struct {
Workers int `arg:"--count,env:NUM_WORKERS"`
}
arg.MustParse(&args)
fmt.Println("Workers:", args.Workers)
```
```shell
$ NUM_WORKERS=6 ./example
Workers: 6
$ NUM_WORKERS=6 ./example --count 4
Workers: 4
```
Configuring a global environment variable name prefix is also possible:
```go
var args struct {
Workers int `arg:"--count,env:NUM_WORKERS"`
}
p, err := arg.NewParser(arg.Config{
EnvPrefix: "MYAPP_",
}, &args)
p.MustParse(os.Args[1:])
fmt.Println("Workers:", args.Workers)
```
```shell
$ MYAPP_NUM_WORKERS=6 ./example
Workers: 6
```
### Usage strings ### Usage strings
```go ```go
var args struct { var args struct {
Input string `arg:"positional"` Input string `arg:"positional"`
@ -158,20 +196,7 @@ var args struct {
arg.MustParse(&args) arg.MustParse(&args)
``` ```
### Default values (before v1.2) 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 {
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 ```go
var args struct { var args struct {
@ -182,9 +207,6 @@ arg.MustParse(&args)
#### Ignoring environment variables and/or default values #### 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 ```go
var args struct { var args struct {
Test string `arg:"-t,env:TEST" default:"something"` Test string `arg:"-t,env:TEST" default:"something"`
@ -195,10 +217,11 @@ p, err := arg.NewParser(arg.Config{
IgnoreDefault: true, IgnoreDefault: true,
}, &args) }, &args)
err = p.Parse(os.Args) err = p.Parse(os.Args[1:])
``` ```
### Arguments with multiple values ### Arguments with multiple values
```go ```go
var args struct { var args struct {
Database string Database string
@ -214,6 +237,7 @@ Fetching the following IDs from foo: [1 2 3]
``` ```
### Arguments that can be specified multiple times, mixed with positionals ### Arguments that can be specified multiple times, mixed with positionals
```go ```go
var args struct { var args struct {
Commands []string `arg:"-c,separate"` Commands []string `arg:"-c,separate"`
@ -231,6 +255,7 @@ Databases [db1 db2 db3]
``` ```
### Arguments with keys and values ### Arguments with keys and values
```go ```go
var args struct { var args struct {
UserIDs map[string]int UserIDs map[string]int
@ -244,24 +269,6 @@ fmt.Println(args.UserIDs)
map[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 ### Version strings
```go ```go
@ -284,6 +291,28 @@ $ ./example --version
someprogram 4.3.0 someprogram 4.3.0
``` ```
> **Note**
> If a `--version` flag is defined in `args` or any subcommand, it overrides the built-in versioning.
### 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
```
### Overriding option names ### Overriding option names
```go ```go
@ -308,13 +337,11 @@ Options:
--help, -h display this help and exit --help, -h display this help and exit
``` ```
### Embedded structs ### Embedded structs
The fields of embedded structs are treated just like regular fields: The fields of embedded structs are treated just like regular fields:
```go ```go
type DatabaseOptions struct { type DatabaseOptions struct {
Host string Host string
Username string Username string
@ -382,6 +409,7 @@ func main() {
fmt.Printf("%#v\n", args.Name) fmt.Printf("%#v\n", args.Name)
} }
``` ```
```shell ```shell
$ ./example --name=foo.bar $ ./example --name=foo.bar
main.NameDotName{Head:"foo", Tail:"bar"} main.NameDotName{Head:"foo", Tail:"bar"}
@ -418,6 +446,7 @@ func main() {
fmt.Printf("%#v\n", args.Name) fmt.Printf("%#v\n", args.Name)
} }
``` ```
```shell ```shell
$ ./example --help $ ./example --help
Usage: test [--name NAME] Usage: test [--name NAME]
@ -432,8 +461,6 @@ main.NameDotName{Head:"file", Tail:"txt"}
### Custom placeholders ### Custom placeholders
*Introduced in version 1.3.0*
Use the `placeholder` tag to control which placeholder text is used in the usage text. Use the `placeholder` tag to control which placeholder text is used in the usage text.
```go ```go
@ -445,6 +472,7 @@ var args struct {
} }
arg.MustParse(&args) arg.MustParse(&args)
``` ```
```shell ```shell
$ ./example -h $ ./example -h
Usage: example [--optimize LEVEL] [--maxjobs N] SRC [DST [DST ...]] Usage: example [--optimize LEVEL] [--maxjobs N] SRC [DST [DST ...]]
@ -521,8 +549,6 @@ For more information visit github.com/alexflint/go-arg
### Subcommands ### 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: Subcommands are commonly used in tools that wish to group multiple functions into a single program. An example is the `git` tool:
```shell ```shell
$ git checkout [arguments specific to checking out code] $ git checkout [arguments specific to checking out code]
@ -583,15 +609,187 @@ if p.Subcommand() == nil {
} }
``` ```
### Custom handling of --help and --version
The following reproduces the internal logic of `MustParse` for the simple case where
you are not using subcommands or --version. This allows you to respond
programatically to --help, and to any errors that come up.
```go
var args struct {
Something string
}
p, err := arg.NewParser(arg.Config{}, &args)
if err != nil {
log.Fatalf("there was an error in the definition of the Go struct: %v", err)
}
err = p.Parse(os.Args[1:])
switch {
case err == arg.ErrHelp: // indicates that user wrote "--help" on command line
p.WriteHelp(os.Stdout)
os.Exit(0)
case err != nil:
fmt.Printf("error: %v\n", err)
p.WriteUsage(os.Stdout)
os.Exit(1)
}
```
```shell
$ go run ./example --help
Usage: ./example --something SOMETHING
Options:
--something SOMETHING
--help, -h display this help and exit
$ ./example --wrong
error: unknown argument --wrong
Usage: ./example --something SOMETHING
$ ./example
error: --something is required
Usage: ./example --something SOMETHING
```
To also handle --version programatically, use the following:
```go
type args struct {
Something string
}
func (args) Version() string {
return "1.2.3"
}
func main() {
var args args
p, err := arg.NewParser(arg.Config{}, &args)
if err != nil {
log.Fatalf("there was an error in the definition of the Go struct: %v", err)
}
err = p.Parse(os.Args[1:])
switch {
case err == arg.ErrHelp: // found "--help" on command line
p.WriteHelp(os.Stdout)
os.Exit(0)
case err == arg.ErrVersion: // found "--version" on command line
fmt.Println(args.Version())
os.Exit(0)
case err != nil:
fmt.Printf("error: %v\n", err)
p.WriteUsage(os.Stdout)
os.Exit(1)
}
fmt.Printf("got %q\n", args.Something)
}
```
```shell
$ ./example --version
1.2.3
$ go run ./example --help
1.2.3
Usage: example --something SOMETHING
Options:
--something SOMETHING
--help, -h display this help and exit
$ ./example --wrong
1.2.3
error: unknown argument --wrong
Usage: example --something SOMETHING
$ ./example
error: --something is required
Usage: example --something SOMETHING
```
To generate subcommand-specific help messages, use the following most general version
(this also works in absence of subcommands but is a bit more complex):
```go
type fetchCmd struct {
Count int
}
type args struct {
Something string
Fetch *fetchCmd `arg:"subcommand"`
}
func (args) Version() string {
return "1.2.3"
}
func main() {
var args args
p, err := arg.NewParser(arg.Config{}, &args)
if err != nil {
log.Fatalf("there was an error in the definition of the Go struct: %v", err)
}
err = p.Parse(os.Args[1:])
switch {
case err == arg.ErrHelp: // found "--help" on command line
p.WriteHelpForSubcommand(os.Stdout, p.SubcommandNames()...)
os.Exit(0)
case err == arg.ErrVersion: // found "--version" on command line
fmt.Println(args.Version())
os.Exit(0)
case err != nil:
fmt.Printf("error: %v\n", err)
p.WriteUsageForSubcommand(os.Stdout, p.SubcommandNames()...)
os.Exit(1)
}
}
```
```shell
$ ./example --version
1.2.3
$ ./example --help
1.2.3
Usage: example [--something SOMETHING] <command> [<args>]
Options:
--something SOMETHING
--help, -h display this help and exit
--version display version and exit
Commands:
fetch
$ ./example fetch --help
1.2.3
Usage: example fetch [--count COUNT]
Options:
--count COUNT
Global options:
--something SOMETHING
--help, -h display this help and exit
--version display version and exit
```
### API Documentation ### API Documentation
https://godoc.org/github.com/alexflint/go-arg https://pkg.go.dev/github.com/alexflint/go-arg
### Rationale ### Rationale
There are many command line argument parsing libraries for Go, including one in the standard library, so why build another? 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. The `flag` library that ships in the standard library seems awkward to me. Positional arguments must precede 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. 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.
@ -599,4 +797,4 @@ The idea behind `go-arg` is that Go already has an excellent way to describe dat
### Backward compatibility notes ### 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. 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 makes it possible to include commas inside help text.

View file

@ -90,7 +90,7 @@ func mustParse(config Config, dest ...interface{}) *Parser {
p, err := NewParser(config, dest...) p, err := NewParser(config, dest...)
if err != nil { if err != nil {
fmt.Fprintln(config.Out, err) fmt.Fprintln(config.Out, err)
config.Exit(-1) config.Exit(2)
return nil return nil
} }
@ -131,6 +131,9 @@ type Config struct {
// subcommand // subcommand
StrictSubcommands bool StrictSubcommands bool
// EnvPrefix instructs the library to use a name prefix when reading environment variables.
EnvPrefix string
// Exit is called to terminate the process with an error code (defaults to os.Exit) // Exit is called to terminate the process with an error code (defaults to os.Exit)
Exit func(int) Exit func(int)
@ -235,7 +238,7 @@ func NewParser(config Config, dests ...interface{}) (*Parser, error) {
panic(fmt.Sprintf("%s is not a pointer (did you forget an ampersand?)", t)) panic(fmt.Sprintf("%s is not a pointer (did you forget an ampersand?)", t))
} }
cmd, err := cmdFromStruct(name, path{root: i}, t) cmd, err := cmdFromStruct(name, path{root: i}, t, config.EnvPrefix)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -282,10 +285,17 @@ func NewParser(config Config, dests ...interface{}) (*Parser, error) {
} }
} }
// Set the parent of the subcommands to be the top-level command
// to make sure that global options work when there is more than one
// dest supplied.
for _, subcommand := range p.cmd.subcommands {
subcommand.parent = p.cmd
}
return &p, nil return &p, nil
} }
func cmdFromStruct(name string, dest path, t reflect.Type) (*command, error) { func cmdFromStruct(name string, dest path, t reflect.Type, envPrefix string) (*command, error) {
// commands can only be created from pointers to structs // commands can only be created from pointers to structs
if t.Kind() != reflect.Ptr { if t.Kind() != reflect.Ptr {
return nil, fmt.Errorf("subcommands must be pointers to structs but %s is a %s", return nil, fmt.Errorf("subcommands must be pointers to structs but %s is a %s",
@ -372,9 +382,9 @@ func cmdFromStruct(name string, dest path, t reflect.Type) (*command, error) {
case key == "env": case key == "env":
// Use override name if provided // Use override name if provided
if value != "" { if value != "" {
spec.env = value spec.env = envPrefix + value
} else { } else {
spec.env = strings.ToUpper(field.Name) spec.env = envPrefix + strings.ToUpper(field.Name)
} }
case key == "subcommand": case key == "subcommand":
// decide on a name for the subcommand // decide on a name for the subcommand
@ -389,7 +399,7 @@ func cmdFromStruct(name string, dest path, t reflect.Type) (*command, error) {
} }
// parse the subcommand recursively // parse the subcommand recursively
subcmd, err := cmdFromStruct(cmdnames[0], subdest, field.Type) subcmd, err := cmdFromStruct(cmdnames[0], subdest, field.Type, envPrefix)
if err != nil { if err != nil {
errs = append(errs, err.Error()) errs = append(errs, err.Error())
return false return false
@ -493,8 +503,15 @@ func cmdFromStruct(name string, dest path, t reflect.Type) (*command, error) {
return &cmd, nil return &cmd, nil
} }
// Parse processes the given command line option, storing the results in the field // Parse processes the given command line option, storing the results in the fields
// of the structs from which NewParser was constructed // of the structs from which NewParser was constructed.
//
// It returns ErrHelp if "--help" is one of the command line args and ErrVersion if
// "--version" is one of the command line args (the latter only applies if the
// destination struct passed to NewParser implements Versioned.)
//
// To respond to --help and --version in the way that MustParse does, see examples
// in the README under "Custom handling of --help and --version".
func (p *Parser) Parse(args []string) error { func (p *Parser) Parse(args []string) error {
err := p.process(args) err := p.process(args)
if err != nil { if err != nil {
@ -608,7 +625,7 @@ func (p *Parser) process(args []string) error {
// must use explicit for loop, not range, because we manipulate i inside the loop // must use explicit for loop, not range, because we manipulate i inside the loop
for i := 0; i < len(args); i++ { for i := 0; i < len(args); i++ {
arg := args[i] arg := args[i]
if arg == "--" { if arg == "--" && !allpositional {
allpositional = true allpositional = true
continue continue
} }
@ -683,7 +700,7 @@ func (p *Parser) process(args []string) error {
if spec.cardinality == multiple { if spec.cardinality == multiple {
var values []string var values []string
if value == "" { if value == "" {
for i+1 < len(args) && !isFlag(args[i+1]) && args[i+1] != "--" { for i+1 < len(args) && isValue(args[i+1], spec.field.Type, specs) && args[i+1] != "--" {
values = append(values, args[i+1]) values = append(values, args[i+1])
i++ i++
if spec.separate { if spec.separate {
@ -711,7 +728,7 @@ func (p *Parser) process(args []string) error {
if i+1 == len(args) { if i+1 == len(args) {
return fmt.Errorf("missing value for %s", arg) return fmt.Errorf("missing value for %s", arg)
} }
if !nextIsNumeric(spec.field.Type, args[i+1]) && isFlag(args[i+1]) { if !isValue(args[i+1], spec.field.Type, specs) {
return fmt.Errorf("missing value for %s", arg) return fmt.Errorf("missing value for %s", arg)
} }
value = args[i+1] value = args[i+1]
@ -736,13 +753,13 @@ func (p *Parser) process(args []string) error {
if spec.cardinality == multiple { if spec.cardinality == multiple {
err := setSliceOrMap(p.val(spec.dest), positionals, true) err := setSliceOrMap(p.val(spec.dest), positionals, true)
if err != nil { if err != nil {
return fmt.Errorf("error processing %s: %v", spec.field.Name, err) return fmt.Errorf("error processing %s: %v", spec.placeholder, err)
} }
positionals = nil positionals = nil
} else { } else {
err := scalar.ParseValue(p.val(spec.dest), positionals[0]) err := scalar.ParseValue(p.val(spec.dest), positionals[0])
if err != nil { if err != nil {
return fmt.Errorf("error processing %s: %v", spec.field.Name, err) return fmt.Errorf("error processing %s: %v", spec.placeholder, err)
} }
positionals = positionals[1:] positionals = positionals[1:]
} }
@ -757,18 +774,13 @@ func (p *Parser) process(args []string) error {
continue continue
} }
name := strings.ToLower(spec.field.Name)
if spec.long != "" && !spec.positional {
name = "--" + spec.long
}
if spec.required { if spec.required {
if spec.short == "" && spec.long == "" { if spec.short == "" && spec.long == "" {
msg := fmt.Sprintf("environment variable %s is required", spec.env) msg := fmt.Sprintf("environment variable %s is required", spec.env)
return errors.New(msg) return errors.New(msg)
} }
msg := fmt.Sprintf("%s is required", name) msg := fmt.Sprintf("%s is required", spec.placeholder)
if spec.env != "" { if spec.env != "" {
msg += " (or environment variable " + spec.env + ")" msg += " (or environment variable " + spec.env + ")"
} }
@ -789,24 +801,31 @@ func (p *Parser) process(args []string) error {
return nil 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 "--" // isFlag returns true if a token is a flag such as "-v" or "--user" but not "-" or "--"
func isFlag(s string) bool { func isFlag(s string) bool {
return strings.HasPrefix(s, "-") && strings.TrimLeft(s, "-") != "" return strings.HasPrefix(s, "-") && strings.TrimLeft(s, "-") != ""
} }
// isValue returns true if a token should be consumed as a value for a flag of type t. This
// is almost always the inverse of isFlag. The one exception is for negative numbers, in which
// case we check the list of active options and return true if its not present there.
func isValue(s string, t reflect.Type, specs []*spec) bool {
switch t.Kind() {
case reflect.Ptr, reflect.Slice:
return isValue(s, t.Elem(), specs)
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)
// if value can be parsed and is not an explicit option declared elsewhere, then use it as a value
if err == nil && (!strings.HasPrefix(s, "-") || findOption(specs, strings.TrimPrefix(s, "-")) == nil) {
return true
}
}
// default case that is used in all cases other than negative numbers: inverse of isFlag
return !isFlag(s)
}
// val returns a reflect.Value corresponding to the current value for the // val returns a reflect.Value corresponding to the current value for the
// given path // given path
func (p *Parser) val(dest path) reflect.Value { func (p *Parser) val(dest path) reflect.Value {

View file

@ -9,13 +9,13 @@ import (
// the width of the left column // the width of the left column
const colWidth = 25 const colWidth = 25
// Fail prints usage information to stderr and exits with non-zero status // Fail prints usage information to p.Config.Out and exits with status code 2.
func (p *Parser) Fail(msg string) { func (p *Parser) Fail(msg string) {
p.FailSubcommand(msg) p.FailSubcommand(msg)
} }
// FailSubcommand prints usage information for a specified subcommand to stderr, // FailSubcommand prints usage information for a specified subcommand to p.Config.Out,
// then exits with non-zero status. To write usage information for a top-level // then exits with status code 2. To write usage information for a top-level
// subcommand, provide just the name of that subcommand. To write usage // subcommand, provide just the name of that subcommand. To write usage
// information for a subcommand that is nested under another subcommand, provide // information for a subcommand that is nested under another subcommand, provide
// a sequence of subcommand names starting with the top-level subcommand and so // a sequence of subcommand names starting with the top-level subcommand and so
@ -27,7 +27,7 @@ func (p *Parser) FailSubcommand(msg string, subcommand ...string) error {
} }
fmt.Fprintln(p.config.Out, "error:", msg) fmt.Fprintln(p.config.Out, "error:", msg)
p.config.Exit(-1) p.config.Exit(2)
return nil return nil
} }
@ -59,10 +59,6 @@ func (p *Parser) WriteUsageForSubcommand(w io.Writer, subcommand ...string) erro
} }
} }
if p.version != "" {
fmt.Fprintln(w, p.version)
}
// print the beginning of the usage string // print the beginning of the usage string
fmt.Fprintf(w, "Usage: %s", p.cmd.name) fmt.Fprintf(w, "Usage: %s", p.cmd.name)
for _, s := range subcommand { for _, s := range subcommand {
@ -208,6 +204,9 @@ func (p *Parser) WriteHelpForSubcommand(w io.Writer, subcommand ...string) error
positionals = append(positionals, spec) positionals = append(positionals, spec)
case spec.long != "": case spec.long != "":
longOptions = append(longOptions, spec) longOptions = append(longOptions, spec)
if spec.long == "version" {
hasVersionOption = true
}
case spec.short != "": case spec.short != "":
shortOptions = append(shortOptions, spec) shortOptions = append(shortOptions, spec)
case spec.short == "" && spec.long == "": case spec.short == "" && spec.long == "":
@ -215,16 +214,36 @@ func (p *Parser) WriteHelpForSubcommand(w io.Writer, subcommand ...string) error
} }
} }
// obtain a flattened list of options from all ancestors
// also determine if any ancestor has a version option spec
var globals []*spec
ancestor := cmd.parent
for ancestor != nil {
for _, spec := range ancestor.specs {
if spec.long == "version" {
hasVersionOption = true
break
}
}
globals = append(globals, ancestor.specs...)
ancestor = ancestor.parent
}
if p.description != "" { if p.description != "" {
fmt.Fprintln(w, p.description) fmt.Fprintln(w, p.description)
} }
if !hasVersionOption && p.version != "" {
fmt.Fprintln(w, p.version)
}
p.WriteUsageForSubcommand(w, subcommand...) p.WriteUsageForSubcommand(w, subcommand...)
// write the list of positionals // write the list of positionals
if len(positionals) > 0 { if len(positionals) > 0 {
fmt.Fprint(w, "\nPositional arguments:\n") fmt.Fprint(w, "\nPositional arguments:\n")
for _, spec := range positionals { for _, spec := range positionals {
print(w, spec.placeholder, spec.help) print(w, spec.placeholder, spec.help, withDefault(spec.defaultString), withEnv(spec.env))
} }
} }
@ -236,28 +255,14 @@ func (p *Parser) WriteHelpForSubcommand(w io.Writer, subcommand ...string) error
} }
for _, spec := range longOptions { for _, spec := range longOptions {
p.printOption(w, spec) 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 // write the list of global options
if len(globals) > 0 { if len(globals) > 0 {
fmt.Fprint(w, "\nGlobal options:\n") fmt.Fprint(w, "\nGlobal options:\n")
for _, spec := range globals { for _, spec := range globals {
p.printOption(w, spec) p.printOption(w, spec)
if spec.long == "version" {
hasVersionOption = true
}
} }
} }

2
vendor/modules.txt vendored
View file

@ -1,4 +1,4 @@
# github.com/alexflint/go-arg v1.5.1 # github.com/alexflint/go-arg v1.6.0
## explicit; go 1.18 ## explicit; go 1.18
github.com/alexflint/go-arg github.com/alexflint/go-arg
# github.com/alexflint/go-scalar v1.2.0 # github.com/alexflint/go-scalar v1.2.0