Merge remote-tracking branch 'flint/master' into jcarr
Signed-off-by: Jeff Carr <jcarr@wit.com>
This commit is contained in:
commit
f92d210ca7
|
@ -0,0 +1 @@
|
||||||
|
github: [alexflint]
|
|
@ -15,17 +15,17 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
go: ['1.17', '1.18', '1.19']
|
go: ['1.20', '1.21', '1.22']
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- id: go
|
- id: go
|
||||||
name: Set up Go
|
name: Set up Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go }}
|
go-version: ${{ matrix.go }}
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: go build -v .
|
run: go build -v .
|
||||||
|
|
300
README.md
300
README.md
|
@ -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.
|
||||||
|
|
48
parse.go
48
parse.go
|
@ -56,7 +56,7 @@ type spec struct {
|
||||||
env string // the name of the environment variable for this option, or empty for none
|
env string // the name of the environment variable for this option, or empty for none
|
||||||
defaultValue reflect.Value // default value for this option
|
defaultValue reflect.Value // default value for this option
|
||||||
defaultString string // default value for this option, in string form to be displayed in help text
|
defaultString string // default value for this option, in string form to be displayed in help text
|
||||||
placeholder string // name of the data in help
|
placeholder string // placeholder string in help
|
||||||
}
|
}
|
||||||
|
|
||||||
// command represents a named subcommand, or the top-level command
|
// command represents a named subcommand, or the top-level command
|
||||||
|
@ -76,8 +76,9 @@ var ErrHelp = errors.New("help requested by user")
|
||||||
// ErrVersion indicates that the builtin --version was provided
|
// ErrVersion indicates that the builtin --version was provided
|
||||||
var ErrVersion = errors.New("version requested by user")
|
var ErrVersion = errors.New("version requested by user")
|
||||||
|
|
||||||
// for monkey patching in example code
|
// for monkey patching in example and test code
|
||||||
var mustParseExit = os.Exit
|
var mustParseExit = os.Exit
|
||||||
|
var mustParseOut io.Writer = os.Stdout
|
||||||
|
|
||||||
// This stores the args sent from modules
|
// This stores the args sent from modules
|
||||||
var register []interface{}
|
var register []interface{}
|
||||||
|
@ -98,23 +99,16 @@ func Register(dest ...interface{}) {
|
||||||
// MustParse processes command line arguments and exits upon failure
|
// MustParse processes command line arguments and exits upon failure
|
||||||
func MustParse(dest ...interface{}) *Parser {
|
func MustParse(dest ...interface{}) *Parser {
|
||||||
register = append(register, dest...)
|
register = append(register, dest...)
|
||||||
return mustParse(Config{Exit: mustParseExit}, register...)
|
return mustParse(Config{Exit: mustParseExit, Out: mustParseOut}, register...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// mustParse is a helper that facilitates testing
|
// mustParse is a helper that facilitates testing
|
||||||
func mustParse(config Config, dest ...interface{}) *Parser {
|
func mustParse(config Config, dest ...interface{}) *Parser {
|
||||||
if config.Exit == nil {
|
|
||||||
config.Exit = os.Exit
|
|
||||||
}
|
|
||||||
if config.Out == nil {
|
|
||||||
config.Out = os.Stdout
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,6 +149,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)
|
||||||
|
|
||||||
|
@ -259,7 +256,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
|
||||||
}
|
}
|
||||||
|
@ -309,7 +306,7 @@ func NewParser(config Config, dests ...interface{}) (*Parser, error) {
|
||||||
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",
|
||||||
|
@ -360,9 +357,8 @@ func cmdFromStruct(name string, dest path, t reflect.Type) (*command, error) {
|
||||||
spec.help = help
|
spec.help = help
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look at the tag
|
// process each comma-separated part of the tag
|
||||||
var isSubcommand bool // tracks whether this field is a subcommand
|
var isSubcommand bool
|
||||||
|
|
||||||
for _, key := range strings.Split(tag, ",") {
|
for _, key := range strings.Split(tag, ",") {
|
||||||
if key == "" {
|
if key == "" {
|
||||||
continue
|
continue
|
||||||
|
@ -397,9 +393,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
|
||||||
|
@ -414,7 +410,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
|
||||||
|
@ -432,6 +428,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")
|
placeholder, hasPlaceholder := field.Tag.Lookup("placeholder")
|
||||||
if hasPlaceholder {
|
if hasPlaceholder {
|
||||||
spec.placeholder = placeholder
|
spec.placeholder = placeholder
|
||||||
|
@ -517,8 +514,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 {
|
||||||
|
@ -632,7 +636,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
|
||||||
}
|
}
|
||||||
|
|
|
@ -163,6 +163,7 @@ func Example_helpText() {
|
||||||
|
|
||||||
// This is only necessary when running inside golang's runnable example harness
|
// This is only necessary when running inside golang's runnable example harness
|
||||||
mustParseExit = func(int) {}
|
mustParseExit = func(int) {}
|
||||||
|
mustParseOut = os.Stdout
|
||||||
|
|
||||||
MustParse(&args)
|
MustParse(&args)
|
||||||
|
|
||||||
|
@ -195,17 +196,17 @@ func Example_helpPlaceholder() {
|
||||||
|
|
||||||
// This is only necessary when running inside golang's runnable example harness
|
// This is only necessary when running inside golang's runnable example harness
|
||||||
mustParseExit = func(int) {}
|
mustParseExit = func(int) {}
|
||||||
|
mustParseOut = os.Stdout
|
||||||
|
|
||||||
MustParse(&args)
|
MustParse(&args)
|
||||||
|
|
||||||
// output:
|
// output:
|
||||||
|
|
||||||
// Usage: example [--optimize LEVEL] [--maxjobs N] SRC [DST [DST ...]]
|
// Usage: example [--optimize LEVEL] [--maxjobs N] SRC [DST [DST ...]]
|
||||||
|
//
|
||||||
// Positional arguments:
|
// Positional arguments:
|
||||||
// SRC
|
// SRC
|
||||||
// DST
|
// DST
|
||||||
|
//
|
||||||
// Options:
|
// Options:
|
||||||
// --optimize LEVEL, -O LEVEL
|
// --optimize LEVEL, -O LEVEL
|
||||||
// optimization level
|
// optimization level
|
||||||
|
@ -235,6 +236,7 @@ func Example_helpTextWithSubcommand() {
|
||||||
|
|
||||||
// This is only necessary when running inside golang's runnable example harness
|
// This is only necessary when running inside golang's runnable example harness
|
||||||
mustParseExit = func(int) {}
|
mustParseExit = func(int) {}
|
||||||
|
mustParseOut = os.Stdout
|
||||||
|
|
||||||
MustParse(&args)
|
MustParse(&args)
|
||||||
|
|
||||||
|
@ -272,6 +274,7 @@ func Example_helpTextWhenUsingSubcommand() {
|
||||||
|
|
||||||
// This is only necessary when running inside golang's runnable example harness
|
// This is only necessary when running inside golang's runnable example harness
|
||||||
mustParseExit = func(int) {}
|
mustParseExit = func(int) {}
|
||||||
|
mustParseOut = os.Stdout
|
||||||
|
|
||||||
MustParse(&args)
|
MustParse(&args)
|
||||||
|
|
||||||
|
@ -392,6 +395,7 @@ func Example_errorText() {
|
||||||
|
|
||||||
// This is only necessary when running inside golang's runnable example harness
|
// This is only necessary when running inside golang's runnable example harness
|
||||||
mustParseExit = func(int) {}
|
mustParseExit = func(int) {}
|
||||||
|
mustParseOut = os.Stdout
|
||||||
|
|
||||||
MustParse(&args)
|
MustParse(&args)
|
||||||
|
|
||||||
|
@ -415,6 +419,7 @@ func Example_errorTextForSubcommand() {
|
||||||
|
|
||||||
// This is only necessary when running inside golang's runnable example harness
|
// This is only necessary when running inside golang's runnable example harness
|
||||||
mustParseExit = func(int) {}
|
mustParseExit = func(int) {}
|
||||||
|
mustParseOut = os.Stdout
|
||||||
|
|
||||||
MustParse(&args)
|
MustParse(&args)
|
||||||
|
|
||||||
|
@ -450,6 +455,7 @@ func Example_subcommand() {
|
||||||
|
|
||||||
// This is only necessary when running inside golang's runnable example harness
|
// This is only necessary when running inside golang's runnable example harness
|
||||||
mustParseExit = func(int) {}
|
mustParseExit = func(int) {}
|
||||||
|
mustParseOut = os.Stdout
|
||||||
|
|
||||||
MustParse(&args)
|
MustParse(&args)
|
||||||
|
|
||||||
|
|
|
@ -28,11 +28,11 @@ func parse(cmdline string, dest interface{}) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func pparse(cmdline string, dest interface{}) (*Parser, error) {
|
func pparse(cmdline string, dest interface{}) (*Parser, error) {
|
||||||
return parseWithEnv(cmdline, nil, dest)
|
return parseWithEnv(Config{}, cmdline, nil, dest)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseWithEnv(cmdline string, env []string, dest interface{}) (*Parser, error) {
|
func parseWithEnv(config Config, cmdline string, env []string, dest interface{}) (*Parser, error) {
|
||||||
p, err := NewParser(Config{}, dest)
|
p, err := NewParser(config, dest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -231,7 +231,7 @@ func TestRequiredWithEnvOnly(t *testing.T) {
|
||||||
var args struct {
|
var args struct {
|
||||||
Foo string `arg:"required,--,-,env:FOO"`
|
Foo string `arg:"required,--,-,env:FOO"`
|
||||||
}
|
}
|
||||||
_, err := parseWithEnv("", []string{}, &args)
|
_, err := parseWithEnv(Config{}, "", []string{}, &args)
|
||||||
require.Error(t, err, "environment variable FOO is required")
|
require.Error(t, err, "environment variable FOO is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -609,6 +609,15 @@ func TestNoMoreOptionsBeforeHelp(t *testing.T) {
|
||||||
assert.NotEqual(t, ErrHelp, err)
|
assert.NotEqual(t, ErrHelp, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNoMoreOptionsTwice(t *testing.T) {
|
||||||
|
var args struct {
|
||||||
|
X []string `arg:"positional"`
|
||||||
|
}
|
||||||
|
err := parse("-- --", &args)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, []string{"--"}, args.X)
|
||||||
|
}
|
||||||
|
|
||||||
func TestHelpFlag(t *testing.T) {
|
func TestHelpFlag(t *testing.T) {
|
||||||
var args struct {
|
var args struct {
|
||||||
Foo string
|
Foo string
|
||||||
|
@ -692,11 +701,26 @@ func TestMustParse(t *testing.T) {
|
||||||
assert.NotNil(t, parser)
|
assert.NotNil(t, parser)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMustParseError(t *testing.T) {
|
||||||
|
var args struct {
|
||||||
|
Foo []string `default:""`
|
||||||
|
}
|
||||||
|
var exitCode int
|
||||||
|
var stdout bytes.Buffer
|
||||||
|
mustParseExit = func(code int) { exitCode = code }
|
||||||
|
mustParseOut = &stdout
|
||||||
|
os.Args = []string{"example"}
|
||||||
|
parser := MustParse(&args)
|
||||||
|
assert.Nil(t, parser)
|
||||||
|
assert.Equal(t, 2, exitCode)
|
||||||
|
assert.Contains(t, stdout.String(), "default values are not supported for slice or map fields")
|
||||||
|
}
|
||||||
|
|
||||||
func TestEnvironmentVariable(t *testing.T) {
|
func TestEnvironmentVariable(t *testing.T) {
|
||||||
var args struct {
|
var args struct {
|
||||||
Foo string `arg:"env"`
|
Foo string `arg:"env"`
|
||||||
}
|
}
|
||||||
_, err := parseWithEnv("", []string{"FOO=bar"}, &args)
|
_, err := parseWithEnv(Config{}, "", []string{"FOO=bar"}, &args)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, "bar", args.Foo)
|
assert.Equal(t, "bar", args.Foo)
|
||||||
}
|
}
|
||||||
|
@ -705,7 +729,7 @@ func TestEnvironmentVariableNotPresent(t *testing.T) {
|
||||||
var args struct {
|
var args struct {
|
||||||
NotPresent string `arg:"env"`
|
NotPresent string `arg:"env"`
|
||||||
}
|
}
|
||||||
_, err := parseWithEnv("", nil, &args)
|
_, err := parseWithEnv(Config{}, "", nil, &args)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, "", args.NotPresent)
|
assert.Equal(t, "", args.NotPresent)
|
||||||
}
|
}
|
||||||
|
@ -714,7 +738,7 @@ func TestEnvironmentVariableOverrideName(t *testing.T) {
|
||||||
var args struct {
|
var args struct {
|
||||||
Foo string `arg:"env:BAZ"`
|
Foo string `arg:"env:BAZ"`
|
||||||
}
|
}
|
||||||
_, err := parseWithEnv("", []string{"BAZ=bar"}, &args)
|
_, err := parseWithEnv(Config{}, "", []string{"BAZ=bar"}, &args)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, "bar", args.Foo)
|
assert.Equal(t, "bar", args.Foo)
|
||||||
}
|
}
|
||||||
|
@ -723,7 +747,7 @@ func TestEnvironmentVariableOverrideArgument(t *testing.T) {
|
||||||
var args struct {
|
var args struct {
|
||||||
Foo string `arg:"env"`
|
Foo string `arg:"env"`
|
||||||
}
|
}
|
||||||
_, err := parseWithEnv("--foo zzz", []string{"FOO=bar"}, &args)
|
_, err := parseWithEnv(Config{}, "--foo zzz", []string{"FOO=bar"}, &args)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, "zzz", args.Foo)
|
assert.Equal(t, "zzz", args.Foo)
|
||||||
}
|
}
|
||||||
|
@ -732,7 +756,7 @@ func TestEnvironmentVariableError(t *testing.T) {
|
||||||
var args struct {
|
var args struct {
|
||||||
Foo int `arg:"env"`
|
Foo int `arg:"env"`
|
||||||
}
|
}
|
||||||
_, err := parseWithEnv("", []string{"FOO=bar"}, &args)
|
_, err := parseWithEnv(Config{}, "", []string{"FOO=bar"}, &args)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -740,7 +764,7 @@ func TestEnvironmentVariableRequired(t *testing.T) {
|
||||||
var args struct {
|
var args struct {
|
||||||
Foo string `arg:"env,required"`
|
Foo string `arg:"env,required"`
|
||||||
}
|
}
|
||||||
_, err := parseWithEnv("", []string{"FOO=bar"}, &args)
|
_, err := parseWithEnv(Config{}, "", []string{"FOO=bar"}, &args)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, "bar", args.Foo)
|
assert.Equal(t, "bar", args.Foo)
|
||||||
}
|
}
|
||||||
|
@ -749,7 +773,7 @@ func TestEnvironmentVariableSliceArgumentString(t *testing.T) {
|
||||||
var args struct {
|
var args struct {
|
||||||
Foo []string `arg:"env"`
|
Foo []string `arg:"env"`
|
||||||
}
|
}
|
||||||
_, err := parseWithEnv("", []string{`FOO=bar,"baz, qux"`}, &args)
|
_, err := parseWithEnv(Config{}, "", []string{`FOO=bar,"baz, qux"`}, &args)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, []string{"bar", "baz, qux"}, args.Foo)
|
assert.Equal(t, []string{"bar", "baz, qux"}, args.Foo)
|
||||||
}
|
}
|
||||||
|
@ -758,7 +782,7 @@ func TestEnvironmentVariableSliceEmpty(t *testing.T) {
|
||||||
var args struct {
|
var args struct {
|
||||||
Foo []string `arg:"env"`
|
Foo []string `arg:"env"`
|
||||||
}
|
}
|
||||||
_, err := parseWithEnv("", []string{`FOO=`}, &args)
|
_, err := parseWithEnv(Config{}, "", []string{`FOO=`}, &args)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Len(t, args.Foo, 0)
|
assert.Len(t, args.Foo, 0)
|
||||||
}
|
}
|
||||||
|
@ -767,7 +791,7 @@ func TestEnvironmentVariableSliceArgumentInteger(t *testing.T) {
|
||||||
var args struct {
|
var args struct {
|
||||||
Foo []int `arg:"env"`
|
Foo []int `arg:"env"`
|
||||||
}
|
}
|
||||||
_, err := parseWithEnv("", []string{`FOO=1,99`}, &args)
|
_, err := parseWithEnv(Config{}, "", []string{`FOO=1,99`}, &args)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, []int{1, 99}, args.Foo)
|
assert.Equal(t, []int{1, 99}, args.Foo)
|
||||||
}
|
}
|
||||||
|
@ -776,7 +800,7 @@ func TestEnvironmentVariableSliceArgumentFloat(t *testing.T) {
|
||||||
var args struct {
|
var args struct {
|
||||||
Foo []float32 `arg:"env"`
|
Foo []float32 `arg:"env"`
|
||||||
}
|
}
|
||||||
_, err := parseWithEnv("", []string{`FOO=1.1,99.9`}, &args)
|
_, err := parseWithEnv(Config{}, "", []string{`FOO=1.1,99.9`}, &args)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, []float32{1.1, 99.9}, args.Foo)
|
assert.Equal(t, []float32{1.1, 99.9}, args.Foo)
|
||||||
}
|
}
|
||||||
|
@ -785,7 +809,7 @@ func TestEnvironmentVariableSliceArgumentBool(t *testing.T) {
|
||||||
var args struct {
|
var args struct {
|
||||||
Foo []bool `arg:"env"`
|
Foo []bool `arg:"env"`
|
||||||
}
|
}
|
||||||
_, err := parseWithEnv("", []string{`FOO=true,false,0,1`}, &args)
|
_, err := parseWithEnv(Config{}, "", []string{`FOO=true,false,0,1`}, &args)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, []bool{true, false, false, true}, args.Foo)
|
assert.Equal(t, []bool{true, false, false, true}, args.Foo)
|
||||||
}
|
}
|
||||||
|
@ -794,7 +818,7 @@ func TestEnvironmentVariableSliceArgumentWrongCsv(t *testing.T) {
|
||||||
var args struct {
|
var args struct {
|
||||||
Foo []int `arg:"env"`
|
Foo []int `arg:"env"`
|
||||||
}
|
}
|
||||||
_, err := parseWithEnv("", []string{`FOO=1,99\"`}, &args)
|
_, err := parseWithEnv(Config{}, "", []string{`FOO=1,99\"`}, &args)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -802,7 +826,7 @@ func TestEnvironmentVariableSliceArgumentWrongType(t *testing.T) {
|
||||||
var args struct {
|
var args struct {
|
||||||
Foo []bool `arg:"env"`
|
Foo []bool `arg:"env"`
|
||||||
}
|
}
|
||||||
_, err := parseWithEnv("", []string{`FOO=one,two`}, &args)
|
_, err := parseWithEnv(Config{}, "", []string{`FOO=one,two`}, &args)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -810,7 +834,7 @@ func TestEnvironmentVariableMap(t *testing.T) {
|
||||||
var args struct {
|
var args struct {
|
||||||
Foo map[int]string `arg:"env"`
|
Foo map[int]string `arg:"env"`
|
||||||
}
|
}
|
||||||
_, err := parseWithEnv("", []string{`FOO=1=one,99=ninetynine`}, &args)
|
_, err := parseWithEnv(Config{}, "", []string{`FOO=1=one,99=ninetynine`}, &args)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Len(t, args.Foo, 2)
|
assert.Len(t, args.Foo, 2)
|
||||||
assert.Equal(t, "one", args.Foo[1])
|
assert.Equal(t, "one", args.Foo[1])
|
||||||
|
@ -821,11 +845,21 @@ func TestEnvironmentVariableEmptyMap(t *testing.T) {
|
||||||
var args struct {
|
var args struct {
|
||||||
Foo map[int]string `arg:"env"`
|
Foo map[int]string `arg:"env"`
|
||||||
}
|
}
|
||||||
_, err := parseWithEnv("", []string{`FOO=`}, &args)
|
_, err := parseWithEnv(Config{}, "", []string{`FOO=`}, &args)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Len(t, args.Foo, 0)
|
assert.Len(t, args.Foo, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEnvironmentVariableWithPrefix(t *testing.T) {
|
||||||
|
var args struct {
|
||||||
|
Foo string `arg:"env"`
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := parseWithEnv(Config{EnvPrefix: "MYAPP_"}, "", []string{"MYAPP_FOO=bar"}, &args)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, "bar", args.Foo)
|
||||||
|
}
|
||||||
|
|
||||||
func TestEnvironmentVariableIgnored(t *testing.T) {
|
func TestEnvironmentVariableIgnored(t *testing.T) {
|
||||||
var args struct {
|
var args struct {
|
||||||
Foo string `arg:"env"`
|
Foo string `arg:"env"`
|
||||||
|
@ -858,7 +892,7 @@ func TestRequiredEnvironmentOnlyVariableIsMissing(t *testing.T) {
|
||||||
Foo string `arg:"required,--,env:FOO"`
|
Foo string `arg:"required,--,env:FOO"`
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := parseWithEnv("", []string{""}, &args)
|
_, err := parseWithEnv(Config{}, "", []string{""}, &args)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -867,7 +901,7 @@ func TestOptionalEnvironmentOnlyVariable(t *testing.T) {
|
||||||
Foo string `arg:"env:FOO"`
|
Foo string `arg:"env:FOO"`
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := parseWithEnv("", []string{}, &args)
|
_, err := parseWithEnv(Config{}, "", []string{}, &args)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -906,7 +940,7 @@ func TestParserMustParse(t *testing.T) {
|
||||||
}{
|
}{
|
||||||
{name: "help", args: struct{}{}, cmdLine: []string{"--help"}, code: 0, output: "display this help and exit"},
|
{name: "help", args: struct{}{}, cmdLine: []string{"--help"}, code: 0, output: "display this help and exit"},
|
||||||
{name: "version", args: versioned{}, cmdLine: []string{"--version"}, code: 0, output: "example 3.2.1"},
|
{name: "version", args: versioned{}, cmdLine: []string{"--version"}, code: 0, output: "example 3.2.1"},
|
||||||
{name: "invalid", args: struct{}{}, cmdLine: []string{"invalid"}, code: -1, output: ""},
|
{name: "invalid", args: struct{}{}, cmdLine: []string{"invalid"}, code: 2, output: ""},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
@ -1556,7 +1590,7 @@ func TestMustParseInvalidParser(t *testing.T) {
|
||||||
}
|
}
|
||||||
parser := mustParse(Config{Out: &stdout, Exit: exit}, &args)
|
parser := mustParse(Config{Out: &stdout, Exit: exit}, &args)
|
||||||
assert.Nil(t, parser)
|
assert.Nil(t, parser)
|
||||||
assert.Equal(t, -1, exitCode)
|
assert.Equal(t, 2, exitCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMustParsePrintsHelp(t *testing.T) {
|
func TestMustParsePrintsHelp(t *testing.T) {
|
||||||
|
@ -1737,3 +1771,11 @@ func TestSubcommandGlobalFlag_InCommand_Strict_Inner(t *testing.T) {
|
||||||
require.NotNil(t, args.Sub)
|
require.NotNil(t, args.Sub)
|
||||||
assert.True(t, args.Sub.Guard)
|
assert.True(t, args.Sub.Guard)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestExitFunctionAndOutStreamGetFilledIn(t *testing.T) {
|
||||||
|
var args struct{}
|
||||||
|
p, err := NewParser(Config{}, &args)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.NotNil(t, p.config.Exit) // go prohibits function pointer comparison
|
||||||
|
assert.Equal(t, p.config.Out, os.Stdout)
|
||||||
|
}
|
||||||
|
|
|
@ -237,7 +237,7 @@ func (versioned) Version() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUsageWithVersion(t *testing.T) {
|
func TestUsageWithVersion(t *testing.T) {
|
||||||
expectedUsage := "example 3.2.1\nUsage: example"
|
expectedUsage := "Usage: example"
|
||||||
|
|
||||||
expectedHelp := `
|
expectedHelp := `
|
||||||
example 3.2.1
|
example 3.2.1
|
||||||
|
@ -260,6 +260,233 @@ Options:
|
||||||
assert.Equal(t, expectedUsage, strings.TrimSpace(usage.String()))
|
assert.Equal(t, expectedUsage, strings.TrimSpace(usage.String()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUsageWithUserDefinedVersionFlag(t *testing.T) {
|
||||||
|
expectedUsage := "Usage: example [--version]"
|
||||||
|
|
||||||
|
expectedHelp := `
|
||||||
|
Usage: example [--version]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--version this is a user-defined version flag
|
||||||
|
--help, -h display this help and exit
|
||||||
|
`
|
||||||
|
|
||||||
|
var args struct {
|
||||||
|
ShowVersion bool `arg:"--version" help:"this is a user-defined version flag"`
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Args[0] = "example"
|
||||||
|
p, err := NewParser(Config{}, &args)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var help bytes.Buffer
|
||||||
|
p.WriteHelp(&help)
|
||||||
|
assert.Equal(t, expectedHelp[1:], help.String())
|
||||||
|
|
||||||
|
var usage bytes.Buffer
|
||||||
|
p.WriteUsage(&usage)
|
||||||
|
assert.Equal(t, expectedUsage, strings.TrimSpace(usage.String()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUsageWithVersionAndUserDefinedVersionFlag(t *testing.T) {
|
||||||
|
expectedUsage := "Usage: example [--version]"
|
||||||
|
|
||||||
|
expectedHelp := `
|
||||||
|
Usage: example [--version]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--version this is a user-defined version flag
|
||||||
|
--help, -h display this help and exit
|
||||||
|
`
|
||||||
|
|
||||||
|
var args struct {
|
||||||
|
versioned
|
||||||
|
ShowVersion bool `arg:"--version" help:"this is a user-defined version flag"`
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Args[0] = "example"
|
||||||
|
p, err := NewParser(Config{}, &args)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var help bytes.Buffer
|
||||||
|
p.WriteHelp(&help)
|
||||||
|
assert.Equal(t, expectedHelp[1:], help.String())
|
||||||
|
|
||||||
|
var usage bytes.Buffer
|
||||||
|
p.WriteUsage(&usage)
|
||||||
|
assert.Equal(t, expectedUsage, strings.TrimSpace(usage.String()))
|
||||||
|
}
|
||||||
|
|
||||||
|
type subcommand struct {
|
||||||
|
Number int `arg:"-n,--number" help:"compute something on the given number"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUsageWithVersionAndSubcommand(t *testing.T) {
|
||||||
|
expectedUsage := "Usage: example <command> [<args>]"
|
||||||
|
|
||||||
|
expectedHelp := `
|
||||||
|
example 3.2.1
|
||||||
|
Usage: example <command> [<args>]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--help, -h display this help and exit
|
||||||
|
--version display version and exit
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
cmd
|
||||||
|
`
|
||||||
|
|
||||||
|
var args struct {
|
||||||
|
versioned
|
||||||
|
Cmd *subcommand `arg:"subcommand"`
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Args[0] = "example"
|
||||||
|
p, err := NewParser(Config{}, &args)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var help bytes.Buffer
|
||||||
|
p.WriteHelp(&help)
|
||||||
|
assert.Equal(t, expectedHelp[1:], help.String())
|
||||||
|
|
||||||
|
var usage bytes.Buffer
|
||||||
|
p.WriteUsage(&usage)
|
||||||
|
assert.Equal(t, expectedUsage, strings.TrimSpace(usage.String()))
|
||||||
|
|
||||||
|
expectedUsage = "Usage: example cmd [--number NUMBER]"
|
||||||
|
|
||||||
|
expectedHelp = `
|
||||||
|
example 3.2.1
|
||||||
|
Usage: example cmd [--number NUMBER]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--number NUMBER, -n NUMBER
|
||||||
|
compute something on the given number
|
||||||
|
--help, -h display this help and exit
|
||||||
|
--version display version and exit
|
||||||
|
`
|
||||||
|
_ = p.Parse([]string{"cmd"})
|
||||||
|
|
||||||
|
help = bytes.Buffer{}
|
||||||
|
p.WriteHelp(&help)
|
||||||
|
assert.Equal(t, expectedHelp[1:], help.String())
|
||||||
|
|
||||||
|
usage = bytes.Buffer{}
|
||||||
|
p.WriteUsage(&usage)
|
||||||
|
assert.Equal(t, expectedUsage, strings.TrimSpace(usage.String()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUsageWithUserDefinedVersionFlagAndSubcommand(t *testing.T) {
|
||||||
|
expectedUsage := "Usage: example [--version] <command> [<args>]"
|
||||||
|
|
||||||
|
expectedHelp := `
|
||||||
|
Usage: example [--version] <command> [<args>]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--version this is a user-defined version flag
|
||||||
|
--help, -h display this help and exit
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
cmd
|
||||||
|
`
|
||||||
|
|
||||||
|
var args struct {
|
||||||
|
Cmd *subcommand `arg:"subcommand"`
|
||||||
|
ShowVersion bool `arg:"--version" help:"this is a user-defined version flag"`
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Args[0] = "example"
|
||||||
|
p, err := NewParser(Config{}, &args)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var help bytes.Buffer
|
||||||
|
p.WriteHelp(&help)
|
||||||
|
assert.Equal(t, expectedHelp[1:], help.String())
|
||||||
|
|
||||||
|
var usage bytes.Buffer
|
||||||
|
p.WriteUsage(&usage)
|
||||||
|
assert.Equal(t, expectedUsage, strings.TrimSpace(usage.String()))
|
||||||
|
|
||||||
|
expectedUsage = "Usage: example cmd [--number NUMBER]"
|
||||||
|
|
||||||
|
expectedHelp = `
|
||||||
|
Usage: example cmd [--number NUMBER]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--number NUMBER, -n NUMBER
|
||||||
|
compute something on the given number
|
||||||
|
|
||||||
|
Global options:
|
||||||
|
--version this is a user-defined version flag
|
||||||
|
--help, -h display this help and exit
|
||||||
|
`
|
||||||
|
_ = p.Parse([]string{"cmd"})
|
||||||
|
|
||||||
|
help = bytes.Buffer{}
|
||||||
|
p.WriteHelp(&help)
|
||||||
|
assert.Equal(t, expectedHelp[1:], help.String())
|
||||||
|
|
||||||
|
usage = bytes.Buffer{}
|
||||||
|
p.WriteUsage(&usage)
|
||||||
|
assert.Equal(t, expectedUsage, strings.TrimSpace(usage.String()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUsageWithVersionAndUserDefinedVersionFlagAndSubcommand(t *testing.T) {
|
||||||
|
expectedUsage := "Usage: example [--version] <command> [<args>]"
|
||||||
|
|
||||||
|
expectedHelp := `
|
||||||
|
Usage: example [--version] <command> [<args>]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--version this is a user-defined version flag
|
||||||
|
--help, -h display this help and exit
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
cmd
|
||||||
|
`
|
||||||
|
|
||||||
|
var args struct {
|
||||||
|
versioned
|
||||||
|
Cmd *subcommand `arg:"subcommand"`
|
||||||
|
ShowVersion bool `arg:"--version" help:"this is a user-defined version flag"`
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Args[0] = "example"
|
||||||
|
p, err := NewParser(Config{}, &args)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var help bytes.Buffer
|
||||||
|
p.WriteHelp(&help)
|
||||||
|
assert.Equal(t, expectedHelp[1:], help.String())
|
||||||
|
|
||||||
|
var usage bytes.Buffer
|
||||||
|
p.WriteUsage(&usage)
|
||||||
|
assert.Equal(t, expectedUsage, strings.TrimSpace(usage.String()))
|
||||||
|
|
||||||
|
expectedUsage = "Usage: example cmd [--number NUMBER]"
|
||||||
|
|
||||||
|
expectedHelp = `
|
||||||
|
Usage: example cmd [--number NUMBER]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--number NUMBER, -n NUMBER
|
||||||
|
compute something on the given number
|
||||||
|
|
||||||
|
Global options:
|
||||||
|
--version this is a user-defined version flag
|
||||||
|
--help, -h display this help and exit
|
||||||
|
`
|
||||||
|
_ = p.Parse([]string{"cmd"})
|
||||||
|
|
||||||
|
help = bytes.Buffer{}
|
||||||
|
p.WriteHelp(&help)
|
||||||
|
assert.Equal(t, expectedHelp[1:], help.String())
|
||||||
|
|
||||||
|
usage = bytes.Buffer{}
|
||||||
|
p.WriteUsage(&usage)
|
||||||
|
assert.Equal(t, expectedUsage, strings.TrimSpace(usage.String()))
|
||||||
|
}
|
||||||
|
|
||||||
type described struct{}
|
type described struct{}
|
||||||
|
|
||||||
// Described returns the description for this program
|
// Described returns the description for this program
|
||||||
|
@ -415,6 +642,50 @@ Options:
|
||||||
assert.Equal(t, expectedUsage, usage.String())
|
assert.Equal(t, expectedUsage, usage.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUsageWithSubcommands(t *testing.T) {
|
||||||
|
expectedUsage := "Usage: example child [--values VALUES]"
|
||||||
|
|
||||||
|
expectedHelp := `
|
||||||
|
Usage: example child [--values VALUES]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--values VALUES Values
|
||||||
|
|
||||||
|
Global options:
|
||||||
|
--verbose, -v verbosity level
|
||||||
|
--help, -h display this help and exit
|
||||||
|
`
|
||||||
|
|
||||||
|
var args struct {
|
||||||
|
Verbose bool `arg:"-v" help:"verbosity level"`
|
||||||
|
Child *struct {
|
||||||
|
Values []float64 `help:"Values"`
|
||||||
|
} `arg:"subcommand:child"`
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Args[0] = "example"
|
||||||
|
p, err := NewParser(Config{}, &args)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
_ = p.Parse([]string{"child"})
|
||||||
|
|
||||||
|
var help bytes.Buffer
|
||||||
|
p.WriteHelp(&help)
|
||||||
|
assert.Equal(t, expectedHelp[1:], help.String())
|
||||||
|
|
||||||
|
var help2 bytes.Buffer
|
||||||
|
p.WriteHelpForSubcommand(&help2, "child")
|
||||||
|
assert.Equal(t, expectedHelp[1:], help2.String())
|
||||||
|
|
||||||
|
var usage bytes.Buffer
|
||||||
|
p.WriteUsage(&usage)
|
||||||
|
assert.Equal(t, expectedUsage, strings.TrimSpace(usage.String()))
|
||||||
|
|
||||||
|
var usage2 bytes.Buffer
|
||||||
|
p.WriteUsageForSubcommand(&usage2, "child")
|
||||||
|
assert.Equal(t, expectedUsage, strings.TrimSpace(usage2.String()))
|
||||||
|
}
|
||||||
|
|
||||||
func TestUsageWithNestedSubcommands(t *testing.T) {
|
func TestUsageWithNestedSubcommands(t *testing.T) {
|
||||||
expectedUsage := "Usage: example child nested [--enable] OUTPUT"
|
expectedUsage := "Usage: example child nested [--enable] OUTPUT"
|
||||||
|
|
||||||
|
@ -524,6 +795,35 @@ Options:
|
||||||
assert.Equal(t, expectedUsage, strings.TrimSpace(usage.String()))
|
assert.Equal(t, expectedUsage, strings.TrimSpace(usage.String()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUsageWithEmptyPlaceholder(t *testing.T) {
|
||||||
|
expectedUsage := "Usage: example [-a] [--b] [--c]"
|
||||||
|
|
||||||
|
expectedHelp := `
|
||||||
|
Usage: example [-a] [--b] [--c]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-a some help for a
|
||||||
|
--b some help for b
|
||||||
|
--c, -c some help for c
|
||||||
|
--help, -h display this help and exit
|
||||||
|
`
|
||||||
|
var args struct {
|
||||||
|
ShortOnly string `arg:"-a,--" placeholder:"" help:"some help for a"`
|
||||||
|
LongOnly string `arg:"--b" placeholder:"" help:"some help for b"`
|
||||||
|
Both string `arg:"-c,--c" placeholder:"" help:"some help for c"`
|
||||||
|
}
|
||||||
|
p, err := NewParser(Config{Program: "example"}, &args)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var help bytes.Buffer
|
||||||
|
p.WriteHelp(&help)
|
||||||
|
assert.Equal(t, expectedHelp[1:], help.String())
|
||||||
|
|
||||||
|
var usage bytes.Buffer
|
||||||
|
p.WriteUsage(&usage)
|
||||||
|
assert.Equal(t, expectedUsage, strings.TrimSpace(usage.String()))
|
||||||
|
}
|
||||||
|
|
||||||
func TestUsageWithShortFirst(t *testing.T) {
|
func TestUsageWithShortFirst(t *testing.T) {
|
||||||
expectedUsage := "Usage: example [-c CAT] [--dog DOG]"
|
expectedUsage := "Usage: example [-c CAT] [--dog DOG]"
|
||||||
|
|
||||||
|
@ -632,7 +932,7 @@ error: something went wrong
|
||||||
p.Fail("something went wrong")
|
p.Fail("something went wrong")
|
||||||
|
|
||||||
assert.Equal(t, expectedStdout[1:], stdout.String())
|
assert.Equal(t, expectedStdout[1:], stdout.String())
|
||||||
assert.Equal(t, -1, exitCode)
|
assert.Equal(t, 2, exitCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFailSubcommand(t *testing.T) {
|
func TestFailSubcommand(t *testing.T) {
|
||||||
|
@ -655,7 +955,7 @@ error: something went wrong
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, expectedStdout[1:], stdout.String())
|
assert.Equal(t, expectedStdout[1:], stdout.String())
|
||||||
assert.Equal(t, -1, exitCode)
|
assert.Equal(t, 2, exitCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
type lengthOf struct {
|
type lengthOf struct {
|
||||||
|
@ -715,3 +1015,72 @@ Commands:
|
||||||
p.WriteHelp(&help)
|
p.WriteHelp(&help)
|
||||||
assert.Equal(t, expectedHelp[1:], help.String())
|
assert.Equal(t, expectedHelp[1:], help.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHelpShowsPositionalWithDefault(t *testing.T) {
|
||||||
|
expectedHelp := `
|
||||||
|
Usage: example [FOO]
|
||||||
|
|
||||||
|
Positional arguments:
|
||||||
|
FOO this is a positional with a default [default: bar]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--help, -h display this help and exit
|
||||||
|
`
|
||||||
|
|
||||||
|
var args struct {
|
||||||
|
Foo string `arg:"positional" default:"bar" help:"this is a positional with a default"`
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := NewParser(Config{Program: "example"}, &args)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var help bytes.Buffer
|
||||||
|
p.WriteHelp(&help)
|
||||||
|
assert.Equal(t, expectedHelp[1:], help.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHelpShowsPositionalWithEnv(t *testing.T) {
|
||||||
|
expectedHelp := `
|
||||||
|
Usage: example [FOO]
|
||||||
|
|
||||||
|
Positional arguments:
|
||||||
|
FOO this is a positional with an env variable [env: FOO]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--help, -h display this help and exit
|
||||||
|
`
|
||||||
|
|
||||||
|
var args struct {
|
||||||
|
Foo string `arg:"positional,env:FOO" help:"this is a positional with an env variable"`
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := NewParser(Config{Program: "example"}, &args)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var help bytes.Buffer
|
||||||
|
p.WriteHelp(&help)
|
||||||
|
assert.Equal(t, expectedHelp[1:], help.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHelpShowsPositionalWithDefaultAndEnv(t *testing.T) {
|
||||||
|
expectedHelp := `
|
||||||
|
Usage: example [FOO]
|
||||||
|
|
||||||
|
Positional arguments:
|
||||||
|
FOO this is a positional with a default and an env variable [default: bar, env: FOO]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--help, -h display this help and exit
|
||||||
|
`
|
||||||
|
|
||||||
|
var args struct {
|
||||||
|
Foo string `arg:"positional,env:FOO" default:"bar" help:"this is a positional with a default and an env variable"`
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := NewParser(Config{Program: "example"}, &args)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var help bytes.Buffer
|
||||||
|
p.WriteHelp(&help)
|
||||||
|
assert.Equal(t, expectedHelp[1:], help.String())
|
||||||
|
}
|
||||||
|
|
56
usage.go
56
usage.go
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -328,7 +333,10 @@ func (p *Parser) printEnvOnlyVar(w io.Writer, spec *spec) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func synopsis(spec *spec, form string) string {
|
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
|
||||||
}
|
}
|
||||||
return form + " " + spec.placeholder
|
return form + " " + spec.placeholder
|
||||||
|
|
Loading…
Reference in New Issue