Compare commits
No commits in common. "guimaster" and "v1.4.3" have entirely different histories.
|
@ -1 +0,0 @@
|
||||||
github: [alexflint]
|
|
|
@ -15,17 +15,17 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
go: ['1.20', '1.21', '1.22']
|
go: ['1.13', '1.14', '1.15', '1.16']
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- id: go
|
- id: go
|
||||||
name: Set up Go
|
name: Set up Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v1
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go }}
|
go-version: ${{ matrix.go }}
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: go build -v .
|
run: go build -v .
|
||||||
|
|
19
Makefile
19
Makefile
|
@ -1,19 +0,0 @@
|
||||||
all: goimports vet
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -f go.*
|
|
||||||
|
|
||||||
redomod:
|
|
||||||
rm -f go.*
|
|
||||||
GO111MODULE= go mod init
|
|
||||||
GO111MODULE= go mod tidy
|
|
||||||
|
|
||||||
test:
|
|
||||||
go test
|
|
||||||
|
|
||||||
vet:
|
|
||||||
@GO111MODULE=off go vet
|
|
||||||
@echo this go binary package builds okay
|
|
||||||
|
|
||||||
goimports:
|
|
||||||
goimports -w *.go
|
|
342
README.md
342
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 in environment variables using commas:
|
You can provide multiple values using the CSV (RFC 4180) format:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
var args struct {
|
var args struct {
|
||||||
|
@ -115,50 +115,12 @@ 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"`
|
||||||
|
@ -196,7 +158,20 @@ var args struct {
|
||||||
arg.MustParse(&args)
|
arg.MustParse(&args)
|
||||||
```
|
```
|
||||||
|
|
||||||
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.
|
### Default values (before v1.2)
|
||||||
|
|
||||||
|
```go
|
||||||
|
var args struct {
|
||||||
|
Foo string
|
||||||
|
Bar bool
|
||||||
|
}
|
||||||
|
arg.Foo = "abc"
|
||||||
|
arg.MustParse(&args)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Combining command line options, environment variables, and default values
|
||||||
|
|
||||||
|
You can combine command line arguments, environment variables, and default values. Command line arguments take precedence over environment variables, which take precedence over default values. This means that we check whether a certain option was provided on the command line, then if not, we check for an environment variable (only if an `env` tag was provided), then if none is found, we check for a `default` tag containing a default value.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
var args struct {
|
var args struct {
|
||||||
|
@ -205,23 +180,7 @@ var args struct {
|
||||||
arg.MustParse(&args)
|
arg.MustParse(&args)
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Ignoring environment variables and/or default values
|
|
||||||
|
|
||||||
```go
|
|
||||||
var args struct {
|
|
||||||
Test string `arg:"-t,env:TEST" default:"something"`
|
|
||||||
}
|
|
||||||
|
|
||||||
p, err := arg.NewParser(arg.Config{
|
|
||||||
IgnoreEnv: true,
|
|
||||||
IgnoreDefault: true,
|
|
||||||
}, &args)
|
|
||||||
|
|
||||||
err = p.Parse(os.Args[1:])
|
|
||||||
```
|
|
||||||
|
|
||||||
### Arguments with multiple values
|
### Arguments with multiple values
|
||||||
|
|
||||||
```go
|
```go
|
||||||
var args struct {
|
var args struct {
|
||||||
Database string
|
Database string
|
||||||
|
@ -237,7 +196,6 @@ 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"`
|
||||||
|
@ -255,7 +213,6 @@ 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
|
||||||
|
@ -269,6 +226,24 @@ 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
|
||||||
|
@ -291,28 +266,6 @@ $ ./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
|
||||||
|
@ -337,11 +290,13 @@ 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
|
||||||
|
@ -409,7 +364,6 @@ 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"}
|
||||||
|
@ -446,7 +400,6 @@ 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]
|
||||||
|
@ -461,6 +414,8 @@ 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
|
||||||
|
@ -472,7 +427,6 @@ 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 ...]]
|
||||||
|
@ -490,9 +444,6 @@ Options:
|
||||||
|
|
||||||
### Description strings
|
### Description strings
|
||||||
|
|
||||||
A descriptive message can be added at the top of the help text by implementing
|
|
||||||
a `Description` function that returns a string.
|
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type args struct {
|
type args struct {
|
||||||
Foo string
|
Foo string
|
||||||
|
@ -518,37 +469,10 @@ Options:
|
||||||
--help, -h display this help and exit
|
--help, -h display this help and exit
|
||||||
```
|
```
|
||||||
|
|
||||||
Similarly an epilogue can be added at the end of the help text by implementing
|
|
||||||
the `Epilogue` function.
|
|
||||||
|
|
||||||
```go
|
|
||||||
type args struct {
|
|
||||||
Foo string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (args) Epilogue() string {
|
|
||||||
return "For more information visit github.com/alexflint/go-arg"
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
var args args
|
|
||||||
arg.MustParse(&args)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```shell
|
|
||||||
$ ./example -h
|
|
||||||
Usage: example [--foo FOO]
|
|
||||||
|
|
||||||
Options:
|
|
||||||
--foo FOO
|
|
||||||
--help, -h display this help and exit
|
|
||||||
|
|
||||||
For more information visit github.com/alexflint/go-arg
|
|
||||||
```
|
|
||||||
|
|
||||||
### Subcommands
|
### 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]
|
||||||
|
@ -609,187 +533,15 @@ 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://pkg.go.dev/github.com/alexflint/go-arg
|
https://godoc.org/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 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.
|
The `flag` library that ships in the standard library seems awkward to me. Positional arguments must preceed options, so `./prog x --foo=1` does what you expect but `./prog --foo=1 x` does not. It also does not allow arguments to have both long (`--foo`) and short (`-f`) forms.
|
||||||
|
|
||||||
Many third-party argument parsing libraries are great for writing sophisticated command line interfaces, but feel to me like overkill for a simple script with a few flags.
|
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.
|
||||||
|
|
||||||
|
@ -797,4 +549,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 makes it possible to include commas inside help text.
|
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.
|
||||||
|
|
|
@ -162,8 +162,8 @@ 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) {}
|
osExit = func(int) {}
|
||||||
mustParseOut = os.Stdout
|
stdout = os.Stdout
|
||||||
|
|
||||||
MustParse(&args)
|
MustParse(&args)
|
||||||
|
|
||||||
|
@ -195,18 +195,19 @@ 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) {}
|
osExit = func(int) {}
|
||||||
mustParseOut = os.Stdout
|
stdout = 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,8 +236,8 @@ 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) {}
|
osExit = func(int) {}
|
||||||
mustParseOut = os.Stdout
|
stdout = os.Stdout
|
||||||
|
|
||||||
MustParse(&args)
|
MustParse(&args)
|
||||||
|
|
||||||
|
@ -273,8 +274,8 @@ 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) {}
|
osExit = func(int) {}
|
||||||
mustParseOut = os.Stdout
|
stdout = os.Stdout
|
||||||
|
|
||||||
MustParse(&args)
|
MustParse(&args)
|
||||||
|
|
||||||
|
@ -310,9 +311,10 @@ func Example_writeHelpForSubcommand() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is only necessary when running inside golang's runnable example harness
|
// This is only necessary when running inside golang's runnable example harness
|
||||||
exit := func(int) {}
|
osExit = func(int) {}
|
||||||
|
stdout = os.Stdout
|
||||||
|
|
||||||
p, err := NewParser(Config{Exit: exit}, &args)
|
p, err := NewParser(Config{}, &args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
@ -358,9 +360,10 @@ func Example_writeHelpForSubcommandNested() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is only necessary when running inside golang's runnable example harness
|
// This is only necessary when running inside golang's runnable example harness
|
||||||
exit := func(int) {}
|
osExit = func(int) {}
|
||||||
|
stdout = os.Stdout
|
||||||
|
|
||||||
p, err := NewParser(Config{Exit: exit}, &args)
|
p, err := NewParser(Config{}, &args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
@ -394,8 +397,8 @@ 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) {}
|
osExit = func(int) {}
|
||||||
mustParseOut = os.Stdout
|
stderr = os.Stdout
|
||||||
|
|
||||||
MustParse(&args)
|
MustParse(&args)
|
||||||
|
|
||||||
|
@ -418,8 +421,8 @@ 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) {}
|
osExit = func(int) {}
|
||||||
mustParseOut = os.Stdout
|
stderr = os.Stdout
|
||||||
|
|
||||||
MustParse(&args)
|
MustParse(&args)
|
||||||
|
|
||||||
|
@ -454,8 +457,8 @@ 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) {}
|
osExit = func(int) {}
|
||||||
mustParseOut = os.Stdout
|
stderr = os.Stdout
|
||||||
|
|
||||||
MustParse(&args)
|
MustParse(&args)
|
||||||
|
|
||||||
|
@ -502,45 +505,3 @@ func Example_allSupportedTypes() {
|
||||||
|
|
||||||
// output:
|
// output:
|
||||||
}
|
}
|
||||||
|
|
||||||
func Example_envVarOnly() {
|
|
||||||
os.Args = split("./example")
|
|
||||||
_ = os.Setenv("AUTH_KEY", "my_key")
|
|
||||||
|
|
||||||
defer os.Unsetenv("AUTH_KEY")
|
|
||||||
|
|
||||||
var args struct {
|
|
||||||
AuthKey string `arg:"--,env:AUTH_KEY"`
|
|
||||||
}
|
|
||||||
|
|
||||||
MustParse(&args)
|
|
||||||
|
|
||||||
fmt.Println(args.AuthKey)
|
|
||||||
// output: my_key
|
|
||||||
}
|
|
||||||
|
|
||||||
func Example_envVarOnlyShouldIgnoreFlag() {
|
|
||||||
os.Args = split("./example --=my_key")
|
|
||||||
|
|
||||||
var args struct {
|
|
||||||
AuthKey string `arg:"--,env:AUTH_KEY"`
|
|
||||||
}
|
|
||||||
|
|
||||||
err := Parse(&args)
|
|
||||||
|
|
||||||
fmt.Println(err)
|
|
||||||
// output: unknown argument --=my_key
|
|
||||||
}
|
|
||||||
|
|
||||||
func Example_envVarOnlyShouldIgnoreShortFlag() {
|
|
||||||
os.Args = split("./example -=my_key")
|
|
||||||
|
|
||||||
var args struct {
|
|
||||||
AuthKey string `arg:"--,env:AUTH_KEY"`
|
|
||||||
}
|
|
||||||
|
|
||||||
err := Parse(&args)
|
|
||||||
|
|
||||||
fmt.Println(err)
|
|
||||||
// output: unknown argument -=my_key
|
|
||||||
}
|
|
||||||
|
|
12
go.mod
12
go.mod
|
@ -1,14 +1,8 @@
|
||||||
module go.wit.com/dev/alexflint/arg
|
module github.com/alexflint/go-arg
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/alexflint/go-scalar v1.2.0
|
github.com/alexflint/go-scalar v1.1.0
|
||||||
github.com/stretchr/testify v1.7.0
|
github.com/stretchr/testify v1.7.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
go 1.13
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
|
||||||
gopkg.in/yaml.v3 v3.0.0 // indirect
|
|
||||||
)
|
|
||||||
|
|
||||||
go 1.18
|
|
||||||
|
|
7
go.sum
7
go.sum
|
@ -1,5 +1,5 @@
|
||||||
github.com/alexflint/go-scalar v1.2.0 h1:WR7JPKkeNpnYIOfHRa7ivM21aWAdHD0gEWHCx+WQBRw=
|
github.com/alexflint/go-scalar v1.1.0 h1:aaAouLLzI9TChcPXotr6gUhq+Scr8rl0P9P4PnltbhM=
|
||||||
github.com/alexflint/go-scalar v1.2.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o=
|
github.com/alexflint/go-scalar v1.1.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
@ -11,6 +11,5 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
|
|
344
parse.go
344
parse.go
|
@ -5,7 +5,6 @@ import (
|
||||||
"encoding/csv"
|
"encoding/csv"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
@ -54,15 +53,13 @@ type spec struct {
|
||||||
separate bool // if true, each slice and map entry will have its own --flag
|
separate bool // if true, each slice and map entry will have its own --flag
|
||||||
help string // the help text for this option
|
help string // the help text for this option
|
||||||
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
|
defaultVal string // default value for this option
|
||||||
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
|
||||||
type command struct {
|
type command struct {
|
||||||
name string
|
name string
|
||||||
aliases []string
|
|
||||||
help string
|
help string
|
||||||
dest path
|
dest path
|
||||||
specs []*spec
|
specs []*spec
|
||||||
|
@ -70,48 +67,33 @@ type command struct {
|
||||||
parent *command
|
parent *command
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrHelp indicates that the builtin -h or --help were provided
|
// ErrHelp indicates that -h or --help were provided
|
||||||
var ErrHelp = errors.New("help requested by user")
|
var ErrHelp = errors.New("help requested by user")
|
||||||
|
|
||||||
// ErrVersion indicates that the builtin --version was provided
|
// ErrVersion indicates that --version was provided
|
||||||
var ErrVersion = errors.New("version requested by user")
|
var ErrVersion = errors.New("version requested by user")
|
||||||
|
|
||||||
// for monkey patching in example and test code
|
|
||||||
var mustParseExit = os.Exit
|
|
||||||
var mustParseOut io.Writer = os.Stdout
|
|
||||||
|
|
||||||
/*
|
|
||||||
This allows you to have common arg values defined in a GO package
|
|
||||||
|
|
||||||
package 'foo'
|
|
||||||
function init() {
|
|
||||||
args.Register(&argsFoo)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
// This stores the args sent from the GO packages
|
|
||||||
var register []interface{}
|
|
||||||
|
|
||||||
func Register(dest ...interface{}) {
|
|
||||||
register = append(register, dest...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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...)
|
p, err := NewParser(Config{}, dest...)
|
||||||
return mustParse(Config{Exit: mustParseExit, Out: mustParseOut}, register...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// mustParse is a helper that facilitates testing
|
|
||||||
func mustParse(config Config, dest ...interface{}) *Parser {
|
|
||||||
p, err := NewParser(config, dest...)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(config.Out, err)
|
fmt.Fprintln(stdout, err)
|
||||||
config.Exit(2)
|
osExit(-1)
|
||||||
return nil
|
return nil // just in case osExit was monkey-patched
|
||||||
|
}
|
||||||
|
|
||||||
|
err = p.Parse(flags())
|
||||||
|
switch {
|
||||||
|
case err == ErrHelp:
|
||||||
|
p.writeHelpForSubcommand(stdout, p.lastCmd)
|
||||||
|
osExit(0)
|
||||||
|
case err == ErrVersion:
|
||||||
|
fmt.Fprintln(stdout, p.version)
|
||||||
|
osExit(0)
|
||||||
|
case err != nil:
|
||||||
|
p.failWithSubcommand(err.Error(), p.lastCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
p.MustParse(flags())
|
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,24 +106,8 @@ func Parse(dest ...interface{}) error {
|
||||||
return p.Parse(flags())
|
return p.Parse(flags())
|
||||||
}
|
}
|
||||||
|
|
||||||
// pass in a "pretend" os.Args. Used for bash autocomplete
|
|
||||||
func ParseFlags(flags []string, dest ...interface{}) (*Parser, error) {
|
|
||||||
p, err := NewParser(Config{}, dest...)
|
|
||||||
if err != nil {
|
|
||||||
return p, err
|
|
||||||
}
|
|
||||||
overrideFlags = append(overrideFlags, flags...)
|
|
||||||
err = p.Parse(flags)
|
|
||||||
return p, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var overrideFlags []string
|
|
||||||
|
|
||||||
// flags gets all command line arguments other than the first (program name)
|
// flags gets all command line arguments other than the first (program name)
|
||||||
func flags() []string {
|
func flags() []string {
|
||||||
if len(overrideFlags) > 0 {
|
|
||||||
return overrideFlags
|
|
||||||
}
|
|
||||||
if len(os.Args) == 0 { // os.Args could be empty
|
if len(os.Args) == 0 { // os.Args could be empty
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -155,23 +121,6 @@ type Config struct {
|
||||||
|
|
||||||
// IgnoreEnv instructs the library not to read environment variables
|
// IgnoreEnv instructs the library not to read environment variables
|
||||||
IgnoreEnv bool
|
IgnoreEnv bool
|
||||||
|
|
||||||
// IgnoreDefault instructs the library not to reset the variables to the
|
|
||||||
// default values, including pointers to sub commands
|
|
||||||
IgnoreDefault bool
|
|
||||||
|
|
||||||
// StrictSubcommands intructs the library not to allow global commands after
|
|
||||||
// subcommand
|
|
||||||
StrictSubcommands bool
|
|
||||||
|
|
||||||
// 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 func(int)
|
|
||||||
|
|
||||||
// Out is where help text, usage text, and failure messages are printed (defaults to os.Stdout)
|
|
||||||
Out io.Writer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parser represents a set of command line options with destination values
|
// Parser represents a set of command line options with destination values
|
||||||
|
@ -181,10 +130,9 @@ type Parser struct {
|
||||||
config Config
|
config Config
|
||||||
version string
|
version string
|
||||||
description string
|
description string
|
||||||
epilogue string
|
|
||||||
|
|
||||||
// the following field changes during processing of command line arguments
|
// the following field changes during processing of command line arguments
|
||||||
subcommand []string
|
lastCmd *command
|
||||||
}
|
}
|
||||||
|
|
||||||
// Versioned is the interface that the destination struct should implement to
|
// Versioned is the interface that the destination struct should implement to
|
||||||
|
@ -203,14 +151,6 @@ type Described interface {
|
||||||
Description() string
|
Description() string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Epilogued is the interface that the destination struct should implement to
|
|
||||||
// add an epilogue string at the bottom of the help message.
|
|
||||||
type Epilogued interface {
|
|
||||||
// Epilogue returns the string that will be printed on a line by itself
|
|
||||||
// at the end of the help message.
|
|
||||||
Epilogue() string
|
|
||||||
}
|
|
||||||
|
|
||||||
// walkFields calls a function for each field of a struct, recursively expanding struct fields.
|
// walkFields calls a function for each field of a struct, recursively expanding struct fields.
|
||||||
func walkFields(t reflect.Type, visit func(field reflect.StructField, owner reflect.Type) bool) {
|
func walkFields(t reflect.Type, visit func(field reflect.StructField, owner reflect.Type) bool) {
|
||||||
walkFieldsImpl(t, visit, nil)
|
walkFieldsImpl(t, visit, nil)
|
||||||
|
@ -234,14 +174,6 @@ func walkFieldsImpl(t reflect.Type, visit func(field reflect.StructField, owner
|
||||||
|
|
||||||
// NewParser constructs a parser from a list of destination structs
|
// NewParser constructs a parser from a list of destination structs
|
||||||
func NewParser(config Config, dests ...interface{}) (*Parser, error) {
|
func NewParser(config Config, dests ...interface{}) (*Parser, error) {
|
||||||
// fill in defaults
|
|
||||||
if config.Exit == nil {
|
|
||||||
config.Exit = os.Exit
|
|
||||||
}
|
|
||||||
if config.Out == nil {
|
|
||||||
config.Out = os.Stdout
|
|
||||||
}
|
|
||||||
|
|
||||||
// first pick a name for the command for use in the usage text
|
// first pick a name for the command for use in the usage text
|
||||||
var name string
|
var name string
|
||||||
switch {
|
switch {
|
||||||
|
@ -271,36 +203,23 @@ 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, config.EnvPrefix)
|
cmd, err := cmdFromStruct(name, path{root: i}, t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// for backwards compatibility, add nonzero field values as defaults
|
// add nonzero field values as defaults
|
||||||
// this applies only to the top-level command, not to subcommands (this inconsistency
|
|
||||||
// is the reason that this method for setting default values was deprecated)
|
|
||||||
for _, spec := range cmd.specs {
|
for _, spec := range cmd.specs {
|
||||||
// get the value
|
if v := p.val(spec.dest); v.IsValid() && !isZero(v) {
|
||||||
v := p.val(spec.dest)
|
if defaultVal, ok := v.Interface().(encoding.TextMarshaler); ok {
|
||||||
|
str, err := defaultVal.MarshalText()
|
||||||
// if the value is the "zero value" (e.g. nil pointer, empty struct) then ignore
|
|
||||||
if isZero(v) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// store as a default
|
|
||||||
spec.defaultValue = v
|
|
||||||
|
|
||||||
// we need a string to display in help text
|
|
||||||
// if MarshalText is implemented then use that
|
|
||||||
if m, ok := v.Interface().(encoding.TextMarshaler); ok {
|
|
||||||
s, err := m.MarshalText()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%v: error marshaling default value to string: %v", spec.dest, err)
|
return nil, fmt.Errorf("%v: error marshaling default value to string: %v", spec.dest, err)
|
||||||
}
|
}
|
||||||
spec.defaultString = string(s)
|
spec.defaultVal = string(str)
|
||||||
} else {
|
} else {
|
||||||
spec.defaultString = fmt.Sprintf("%v", v)
|
spec.defaultVal = fmt.Sprintf("%v", v)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -313,22 +232,12 @@ func NewParser(config Config, dests ...interface{}) (*Parser, error) {
|
||||||
if dest, ok := dest.(Described); ok {
|
if dest, ok := dest.(Described); ok {
|
||||||
p.description = dest.Description()
|
p.description = dest.Description()
|
||||||
}
|
}
|
||||||
if dest, ok := dest.(Epilogued); ok {
|
|
||||||
p.epilogue = dest.Epilogue()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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, envPrefix string) (*command, error) {
|
func cmdFromStruct(name string, dest path, t reflect.Type) (*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",
|
||||||
|
@ -379,8 +288,13 @@ func cmdFromStruct(name string, dest path, t reflect.Type, envPrefix string) (*c
|
||||||
spec.help = help
|
spec.help = help
|
||||||
}
|
}
|
||||||
|
|
||||||
// process each comma-separated part of the tag
|
defaultVal, hasDefault := field.Tag.Lookup("default")
|
||||||
var isSubcommand bool
|
if hasDefault {
|
||||||
|
spec.defaultVal = defaultVal
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look at the tag
|
||||||
|
var isSubcommand bool // tracks whether this field is a subcommand
|
||||||
for _, key := range strings.Split(tag, ",") {
|
for _, key := range strings.Split(tag, ",") {
|
||||||
if key == "" {
|
if key == "" {
|
||||||
continue
|
continue
|
||||||
|
@ -398,13 +312,18 @@ func cmdFromStruct(name string, dest path, t reflect.Type, envPrefix string) (*c
|
||||||
case strings.HasPrefix(key, "--"):
|
case strings.HasPrefix(key, "--"):
|
||||||
spec.long = key[2:]
|
spec.long = key[2:]
|
||||||
case strings.HasPrefix(key, "-"):
|
case strings.HasPrefix(key, "-"):
|
||||||
if len(key) > 2 {
|
if len(key) != 2 {
|
||||||
errs = append(errs, fmt.Sprintf("%s.%s: short arguments must be one character only",
|
errs = append(errs, fmt.Sprintf("%s.%s: short arguments must be one character only",
|
||||||
t.Name(), field.Name))
|
t.Name(), field.Name))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
spec.short = key[1:]
|
spec.short = key[1:]
|
||||||
case key == "required":
|
case key == "required":
|
||||||
|
if hasDefault {
|
||||||
|
errs = append(errs, fmt.Sprintf("%s.%s: 'required' cannot be used when a default value is specified",
|
||||||
|
t.Name(), field.Name))
|
||||||
|
return false
|
||||||
|
}
|
||||||
spec.required = true
|
spec.required = true
|
||||||
case key == "positional":
|
case key == "positional":
|
||||||
spec.positional = true
|
spec.positional = true
|
||||||
|
@ -415,30 +334,24 @@ func cmdFromStruct(name string, dest path, t reflect.Type, envPrefix string) (*c
|
||||||
case key == "env":
|
case key == "env":
|
||||||
// Use override name if provided
|
// Use override name if provided
|
||||||
if value != "" {
|
if value != "" {
|
||||||
spec.env = envPrefix + value
|
spec.env = value
|
||||||
} else {
|
} else {
|
||||||
spec.env = envPrefix + strings.ToUpper(field.Name)
|
spec.env = strings.ToUpper(field.Name)
|
||||||
}
|
}
|
||||||
case key == "subcommand":
|
case key == "subcommand":
|
||||||
// decide on a name for the subcommand
|
// decide on a name for the subcommand
|
||||||
var cmdnames []string
|
cmdname := value
|
||||||
if value == "" {
|
if cmdname == "" {
|
||||||
cmdnames = []string{strings.ToLower(field.Name)}
|
cmdname = strings.ToLower(field.Name)
|
||||||
} else {
|
|
||||||
cmdnames = strings.Split(value, "|")
|
|
||||||
}
|
|
||||||
for i := range cmdnames {
|
|
||||||
cmdnames[i] = strings.TrimSpace(cmdnames[i])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse the subcommand recursively
|
// parse the subcommand recursively
|
||||||
subcmd, err := cmdFromStruct(cmdnames[0], subdest, field.Type, envPrefix)
|
subcmd, err := cmdFromStruct(cmdname, subdest, field.Type)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errs = append(errs, err.Error())
|
errs = append(errs, err.Error())
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
subcmd.aliases = cmdnames[1:]
|
|
||||||
subcmd.parent = &cmd
|
subcmd.parent = &cmd
|
||||||
subcmd.help = field.Tag.Get("help")
|
subcmd.help = field.Tag.Get("help")
|
||||||
|
|
||||||
|
@ -450,7 +363,6 @@ func cmdFromStruct(name string, dest path, t reflect.Type, envPrefix string) (*c
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
|
@ -460,15 +372,13 @@ func cmdFromStruct(name string, dest path, t reflect.Type, envPrefix string) (*c
|
||||||
spec.placeholder = strings.ToUpper(spec.field.Name)
|
spec.placeholder = strings.ToUpper(spec.field.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// if this is a subcommand then we've done everything we need to do
|
// Check whether this field is supported. It's good to do this here rather than
|
||||||
if isSubcommand {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// check whether this field is supported. It's good to do this here rather than
|
|
||||||
// wait until ParseValue because it means that a program with invalid argument
|
// wait until ParseValue because it means that a program with invalid argument
|
||||||
// fields will always fail regardless of whether the arguments it received
|
// fields will always fail regardless of whether the arguments it received
|
||||||
// exercised those fields.
|
// exercised those fields.
|
||||||
|
if !isSubcommand {
|
||||||
|
cmd.specs = append(cmd.specs, &spec)
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
spec.cardinality, err = cardinalityOf(field.Type)
|
spec.cardinality, err = cardinalityOf(field.Type)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -476,44 +386,13 @@ func cmdFromStruct(name string, dest path, t reflect.Type, envPrefix string) (*c
|
||||||
t.Name(), field.Name, field.Type.String()))
|
t.Name(), field.Name, field.Type.String()))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if spec.cardinality == multiple && hasDefault {
|
||||||
defaultString, hasDefault := field.Tag.Lookup("default")
|
|
||||||
if hasDefault {
|
|
||||||
// we do not support default values for maps and slices
|
|
||||||
if spec.cardinality == multiple {
|
|
||||||
errs = append(errs, fmt.Sprintf("%s.%s: default values are not supported for slice or map fields",
|
errs = append(errs, fmt.Sprintf("%s.%s: default values are not supported for slice or map fields",
|
||||||
t.Name(), field.Name))
|
t.Name(), field.Name))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// a required field cannot also have a default value
|
|
||||||
if spec.required {
|
|
||||||
errs = append(errs, fmt.Sprintf("%s.%s: 'required' cannot be used when a default value is specified",
|
|
||||||
t.Name(), field.Name))
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse the default value
|
|
||||||
spec.defaultString = defaultString
|
|
||||||
if field.Type.Kind() == reflect.Ptr {
|
|
||||||
// here we have a field of type *T and we create a new T, no need to dereference
|
|
||||||
// in order for the value to be settable
|
|
||||||
spec.defaultValue = reflect.New(field.Type.Elem())
|
|
||||||
} else {
|
|
||||||
// here we have a field of type T and we create a new T and then dereference it
|
|
||||||
// so that the resulting value is settable
|
|
||||||
spec.defaultValue = reflect.New(field.Type).Elem()
|
|
||||||
}
|
|
||||||
err := scalar.ParseValue(spec.defaultValue, defaultString)
|
|
||||||
if err != nil {
|
|
||||||
errs = append(errs, fmt.Sprintf("%s.%s: error processing default value: %v", t.Name(), field.Name, err))
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// add the spec to the list of specs
|
|
||||||
cmd.specs = append(cmd.specs, &spec)
|
|
||||||
|
|
||||||
// if this was an embedded field then we already returned true up above
|
// if this was an embedded field then we already returned true up above
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
|
@ -536,15 +415,8 @@ func cmdFromStruct(name string, dest path, t reflect.Type, envPrefix string) (*c
|
||||||
return &cmd, nil
|
return &cmd, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse processes the given command line option, storing the results in the fields
|
// Parse processes the given command line option, storing the results in the field
|
||||||
// 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 {
|
||||||
|
@ -561,20 +433,6 @@ func (p *Parser) Parse(args []string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Parser) MustParse(args []string) {
|
|
||||||
err := p.Parse(args)
|
|
||||||
switch {
|
|
||||||
case err == ErrHelp:
|
|
||||||
p.WriteHelpForSubcommand(p.config.Out, p.subcommand...)
|
|
||||||
p.config.Exit(0)
|
|
||||||
case err == ErrVersion:
|
|
||||||
fmt.Fprintln(p.config.Out, p.version)
|
|
||||||
p.config.Exit(0)
|
|
||||||
case err != nil:
|
|
||||||
p.FailSubcommand(err.Error(), p.subcommand...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// process environment vars for the given arguments
|
// process environment vars for the given arguments
|
||||||
func (p *Parser) captureEnvVars(specs []*spec, wasPresent map[*spec]bool) error {
|
func (p *Parser) captureEnvVars(specs []*spec, wasPresent map[*spec]bool) error {
|
||||||
for _, spec := range specs {
|
for _, spec := range specs {
|
||||||
|
@ -628,7 +486,7 @@ func (p *Parser) process(args []string) error {
|
||||||
|
|
||||||
// union of specs for the chain of subcommands encountered so far
|
// union of specs for the chain of subcommands encountered so far
|
||||||
curCmd := p.cmd
|
curCmd := p.cmd
|
||||||
p.subcommand = nil
|
p.lastCmd = curCmd
|
||||||
|
|
||||||
// make a copy of the specs because we will add to this list each time we expand a subcommand
|
// make a copy of the specs because we will add to this list each time we expand a subcommand
|
||||||
specs := make([]*spec, len(curCmd.specs))
|
specs := make([]*spec, len(curCmd.specs))
|
||||||
|
@ -642,15 +500,6 @@ func (p *Parser) process(args []string) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// determine if the current command has a version option spec
|
|
||||||
var hasVersionOption bool
|
|
||||||
for _, spec := range curCmd.specs {
|
|
||||||
if spec.long == "version" {
|
|
||||||
hasVersionOption = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// process each string from the command line
|
// process each string from the command line
|
||||||
var allpositional bool
|
var allpositional bool
|
||||||
var positionals []string
|
var positionals []string
|
||||||
|
@ -658,7 +507,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 == "--" && !allpositional {
|
if arg == "--" {
|
||||||
allpositional = true
|
allpositional = true
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -678,17 +527,10 @@ func (p *Parser) process(args []string) error {
|
||||||
|
|
||||||
// instantiate the field to point to a new struct
|
// instantiate the field to point to a new struct
|
||||||
v := p.val(subcmd.dest)
|
v := p.val(subcmd.dest)
|
||||||
if v.IsNil() {
|
|
||||||
v.Set(reflect.New(v.Type().Elem())) // we already checked that all subcommands are struct pointers
|
v.Set(reflect.New(v.Type().Elem())) // we already checked that all subcommands are struct pointers
|
||||||
}
|
|
||||||
|
|
||||||
// add the new options to the set of allowed options
|
// add the new options to the set of allowed options
|
||||||
if p.config.StrictSubcommands {
|
|
||||||
specs = make([]*spec, len(subcmd.specs))
|
|
||||||
copy(specs, subcmd.specs)
|
|
||||||
} else {
|
|
||||||
specs = append(specs, subcmd.specs...)
|
specs = append(specs, subcmd.specs...)
|
||||||
}
|
|
||||||
|
|
||||||
// capture environment vars for these new options
|
// capture environment vars for these new options
|
||||||
if !p.config.IgnoreEnv {
|
if !p.config.IgnoreEnv {
|
||||||
|
@ -699,7 +541,7 @@ func (p *Parser) process(args []string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
curCmd = subcmd
|
curCmd = subcmd
|
||||||
p.subcommand = append(p.subcommand, arg)
|
p.lastCmd = curCmd
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -708,10 +550,8 @@ func (p *Parser) process(args []string) error {
|
||||||
case "-h", "--help":
|
case "-h", "--help":
|
||||||
return ErrHelp
|
return ErrHelp
|
||||||
case "--version":
|
case "--version":
|
||||||
if !hasVersionOption && p.version != "" {
|
|
||||||
return ErrVersion
|
return ErrVersion
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// check for an equals sign, as in "--foo=bar"
|
// check for an equals sign, as in "--foo=bar"
|
||||||
var value string
|
var value string
|
||||||
|
@ -724,7 +564,7 @@ func (p *Parser) process(args []string) error {
|
||||||
// lookup the spec for this option (note that the "specs" slice changes as
|
// lookup the spec for this option (note that the "specs" slice changes as
|
||||||
// we expand subcommands so it is better not to use a map)
|
// we expand subcommands so it is better not to use a map)
|
||||||
spec := findOption(specs, opt)
|
spec := findOption(specs, opt)
|
||||||
if spec == nil || opt == "" {
|
if spec == nil {
|
||||||
return fmt.Errorf("unknown argument %s", arg)
|
return fmt.Errorf("unknown argument %s", arg)
|
||||||
}
|
}
|
||||||
wasPresent[spec] = true
|
wasPresent[spec] = true
|
||||||
|
@ -733,7 +573,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) && isValue(args[i+1], spec.field.Type, specs) && args[i+1] != "--" {
|
for i+1 < len(args) && !isFlag(args[i+1]) && args[i+1] != "--" {
|
||||||
values = append(values, args[i+1])
|
values = append(values, args[i+1])
|
||||||
i++
|
i++
|
||||||
if spec.separate {
|
if spec.separate {
|
||||||
|
@ -761,7 +601,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 !isValue(args[i+1], spec.field.Type, specs) {
|
if !nextIsNumeric(spec.field.Type, args[i+1]) && isFlag(args[i+1]) {
|
||||||
return fmt.Errorf("missing value for %s", arg)
|
return fmt.Errorf("missing value for %s", arg)
|
||||||
}
|
}
|
||||||
value = args[i+1]
|
value = args[i+1]
|
||||||
|
@ -786,13 +626,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.placeholder, err)
|
return fmt.Errorf("error processing %s: %v", spec.field.Name, 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.placeholder, err)
|
return fmt.Errorf("error processing %s: %v", spec.field.Name, err)
|
||||||
}
|
}
|
||||||
positionals = positionals[1:]
|
positionals = positionals[1:]
|
||||||
}
|
}
|
||||||
|
@ -807,56 +647,45 @@ func (p *Parser) process(args []string) error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if spec.required {
|
name := strings.ToLower(spec.field.Name)
|
||||||
if spec.short == "" && spec.long == "" {
|
if spec.long != "" && !spec.positional {
|
||||||
msg := fmt.Sprintf("environment variable %s is required", spec.env)
|
name = "--" + spec.long
|
||||||
return errors.New(msg)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
msg := fmt.Sprintf("%s is required", spec.placeholder)
|
if spec.required {
|
||||||
|
msg := fmt.Sprintf("%s is required", name)
|
||||||
if spec.env != "" {
|
if spec.env != "" {
|
||||||
msg += " (or environment variable " + spec.env + ")"
|
msg += " (or environment variable " + spec.env + ")"
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.New(msg)
|
return errors.New(msg)
|
||||||
}
|
}
|
||||||
|
if spec.defaultVal != "" {
|
||||||
if spec.defaultValue.IsValid() && !p.config.IgnoreDefault {
|
err := scalar.ParseValue(p.val(spec.dest), spec.defaultVal)
|
||||||
// One issue here is that if the user now modifies the value then
|
if err != nil {
|
||||||
// the default value stored in the spec will be corrupted. There
|
return fmt.Errorf("error processing default value for %s: %v", name, err)
|
||||||
// is no general way to "deep-copy" values in Go, and we still
|
}
|
||||||
// support the old-style method for specifying defaults as
|
|
||||||
// Go values assigned directly to the struct field, so we are stuck.
|
|
||||||
p.val(spec.dest).Set(spec.defaultValue)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// isFlag returns true if a token is a flag such as "-v" or "--user" but not "-" or "--"
|
func nextIsNumeric(t reflect.Type, s string) bool {
|
||||||
func isFlag(s string) bool {
|
|
||||||
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() {
|
switch t.Kind() {
|
||||||
case reflect.Ptr, reflect.Slice:
|
case reflect.Ptr:
|
||||||
return isValue(s, t.Elem(), specs)
|
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:
|
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)
|
v := reflect.New(t)
|
||||||
err := scalar.ParseValue(v, s)
|
err := scalar.ParseValue(v, s)
|
||||||
// if value can be parsed and is not an explicit option declared elsewhere, then use it as a value
|
return err == nil
|
||||||
if err == nil && (!strings.HasPrefix(s, "-") || findOption(specs, strings.TrimPrefix(s, "-")) == nil) {
|
default:
|
||||||
return true
|
return false
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// default case that is used in all cases other than negative numbers: inverse of isFlag
|
// isFlag returns true if a token is a flag such as "-v" or "--user" but not "-" or "--"
|
||||||
return !isFlag(s)
|
func isFlag(s string) bool {
|
||||||
|
return strings.HasPrefix(s, "-") && strings.TrimLeft(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
|
||||||
|
@ -895,11 +724,6 @@ func findSubcommand(cmds []*command, name string) *command {
|
||||||
if cmd.name == name {
|
if cmd.name == name {
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
for _, alias := range cmd.aliases {
|
|
||||||
if alias == name {
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
530
parse_test.go
530
parse_test.go
|
@ -2,7 +2,6 @@ package arg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/mail"
|
"net/mail"
|
||||||
|
@ -28,11 +27,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(Config{}, cmdline, nil, dest)
|
return parseWithEnv(cmdline, nil, dest)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseWithEnv(config Config, cmdline string, env []string, dest interface{}) (*Parser, error) {
|
func parseWithEnv(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
|
||||||
}
|
}
|
||||||
|
@ -96,21 +95,6 @@ func TestInt(t *testing.T) {
|
||||||
assert.EqualValues(t, 8, *args.Ptr)
|
assert.EqualValues(t, 8, *args.Ptr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHexOctBin(t *testing.T) {
|
|
||||||
var args struct {
|
|
||||||
Hex int
|
|
||||||
Oct int
|
|
||||||
Bin int
|
|
||||||
Underscored int
|
|
||||||
}
|
|
||||||
err := parse("--hex 0xA --oct 0o10 --bin 0b101 --underscored 123_456", &args)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.EqualValues(t, 10, args.Hex)
|
|
||||||
assert.EqualValues(t, 8, args.Oct)
|
|
||||||
assert.EqualValues(t, 5, args.Bin)
|
|
||||||
assert.EqualValues(t, 123456, args.Underscored)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNegativeInt(t *testing.T) {
|
func TestNegativeInt(t *testing.T) {
|
||||||
var args struct {
|
var args struct {
|
||||||
Foo int
|
Foo int
|
||||||
|
@ -120,91 +104,17 @@ func TestNegativeInt(t *testing.T) {
|
||||||
assert.EqualValues(t, args.Foo, -100)
|
assert.EqualValues(t, args.Foo, -100)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNegativeFloat(t *testing.T) {
|
|
||||||
var args struct {
|
|
||||||
Foo float64
|
|
||||||
}
|
|
||||||
err := parse("-foo -99", &args)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.EqualValues(t, args.Foo, -99)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNumericFlag(t *testing.T) {
|
|
||||||
var args struct {
|
|
||||||
UseIPv6 bool `arg:"-6"`
|
|
||||||
Foo int
|
|
||||||
}
|
|
||||||
err := parse("-6", &args)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.EqualValues(t, args.UseIPv6, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNumericFlagTakesPrecedence(t *testing.T) {
|
|
||||||
var args struct {
|
|
||||||
UseIPv6 bool `arg:"-6"`
|
|
||||||
Foo int
|
|
||||||
}
|
|
||||||
err := parse("-foo -6", &args)
|
|
||||||
require.Error(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRepeatedNegativeInts(t *testing.T) {
|
|
||||||
var args struct {
|
|
||||||
Ints []int `arg:"--numbers"`
|
|
||||||
}
|
|
||||||
err := parse("--numbers -1 -2 -6", &args)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.EqualValues(t, args.Ints, []int{-1, -2, -6})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRepeatedNegativeFloats(t *testing.T) {
|
|
||||||
var args struct {
|
|
||||||
Floats []float32 `arg:"--numbers"`
|
|
||||||
}
|
|
||||||
err := parse("--numbers -1 -2 -6", &args)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.EqualValues(t, args.Floats, []float32{-1, -2, -6})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRepeatedNegativeFloatsThenNumericFlag(t *testing.T) {
|
|
||||||
var args struct {
|
|
||||||
Floats []float32 `arg:"--numbers"`
|
|
||||||
UseIPv6 bool `arg:"-6"`
|
|
||||||
}
|
|
||||||
err := parse("--numbers -1 -2 -6", &args)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.EqualValues(t, args.Floats, []float32{-1, -2})
|
|
||||||
assert.True(t, args.UseIPv6)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRepeatedNegativeFloatsThenNonexistentFlag(t *testing.T) {
|
|
||||||
var args struct {
|
|
||||||
Floats []float32 `arg:"--numbers"`
|
|
||||||
UseIPv6 bool `arg:"-6"`
|
|
||||||
}
|
|
||||||
err := parse("--numbers -1 -2 -n", &args)
|
|
||||||
require.Error(t, err, "unknown argument -n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRepeatedNegativeIntsThenFloat(t *testing.T) {
|
|
||||||
var args struct {
|
|
||||||
Ints []int `arg:"--numbers"`
|
|
||||||
}
|
|
||||||
err := parse("--numbers -1 -2 -0.1", &args)
|
|
||||||
require.Error(t, err, "unknown argument -0.1")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNegativeIntAndFloatAndTricks(t *testing.T) {
|
func TestNegativeIntAndFloatAndTricks(t *testing.T) {
|
||||||
var args struct {
|
var args struct {
|
||||||
Foo int
|
Foo int
|
||||||
Bar float64
|
Bar float64
|
||||||
N int `arg:"--100"`
|
N int `arg:"--100"`
|
||||||
}
|
}
|
||||||
err := parse("-foo -99 -bar -60.14 -100 -101", &args)
|
err := parse("-foo -100 -bar -60.14 -100 -100", &args)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.EqualValues(t, args.Foo, -99)
|
assert.EqualValues(t, args.Foo, -100)
|
||||||
assert.EqualValues(t, args.Bar, -60.14)
|
assert.EqualValues(t, args.Bar, -60.14)
|
||||||
assert.EqualValues(t, args.N, -101)
|
assert.EqualValues(t, args.N, -100)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUint(t *testing.T) {
|
func TestUint(t *testing.T) {
|
||||||
|
@ -301,14 +211,6 @@ func TestRequiredWithEnv(t *testing.T) {
|
||||||
require.Error(t, err, "--foo is required (or environment variable FOO)")
|
require.Error(t, err, "--foo is required (or environment variable FOO)")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRequiredWithEnvOnly(t *testing.T) {
|
|
||||||
var args struct {
|
|
||||||
Foo string `arg:"required,--,-,env:FOO"`
|
|
||||||
}
|
|
||||||
_, err := parseWithEnv(Config{}, "", []string{}, &args)
|
|
||||||
require.Error(t, err, "environment variable FOO is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestShortFlag(t *testing.T) {
|
func TestShortFlag(t *testing.T) {
|
||||||
var args struct {
|
var args struct {
|
||||||
Foo string `arg:"-f"`
|
Foo string `arg:"-f"`
|
||||||
|
@ -599,6 +501,15 @@ func TestMissingValueInMiddle(t *testing.T) {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNegativeValue(t *testing.T) {
|
||||||
|
var args struct {
|
||||||
|
Foo int
|
||||||
|
}
|
||||||
|
err := parse("--foo -123", &args)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, -123, args.Foo)
|
||||||
|
}
|
||||||
|
|
||||||
func TestInvalidInt(t *testing.T) {
|
func TestInvalidInt(t *testing.T) {
|
||||||
var args struct {
|
var args struct {
|
||||||
Foo int
|
Foo int
|
||||||
|
@ -674,15 +585,6 @@ 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
|
||||||
|
@ -766,26 +668,11 @@ 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(Config{}, "", []string{"FOO=bar"}, &args)
|
_, err := parseWithEnv("", []string{"FOO=bar"}, &args)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, "bar", args.Foo)
|
assert.Equal(t, "bar", args.Foo)
|
||||||
}
|
}
|
||||||
|
@ -794,7 +681,7 @@ func TestEnvironmentVariableNotPresent(t *testing.T) {
|
||||||
var args struct {
|
var args struct {
|
||||||
NotPresent string `arg:"env"`
|
NotPresent string `arg:"env"`
|
||||||
}
|
}
|
||||||
_, err := parseWithEnv(Config{}, "", nil, &args)
|
_, err := parseWithEnv("", nil, &args)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, "", args.NotPresent)
|
assert.Equal(t, "", args.NotPresent)
|
||||||
}
|
}
|
||||||
|
@ -803,7 +690,7 @@ func TestEnvironmentVariableOverrideName(t *testing.T) {
|
||||||
var args struct {
|
var args struct {
|
||||||
Foo string `arg:"env:BAZ"`
|
Foo string `arg:"env:BAZ"`
|
||||||
}
|
}
|
||||||
_, err := parseWithEnv(Config{}, "", []string{"BAZ=bar"}, &args)
|
_, err := parseWithEnv("", []string{"BAZ=bar"}, &args)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, "bar", args.Foo)
|
assert.Equal(t, "bar", args.Foo)
|
||||||
}
|
}
|
||||||
|
@ -812,7 +699,7 @@ func TestEnvironmentVariableOverrideArgument(t *testing.T) {
|
||||||
var args struct {
|
var args struct {
|
||||||
Foo string `arg:"env"`
|
Foo string `arg:"env"`
|
||||||
}
|
}
|
||||||
_, err := parseWithEnv(Config{}, "--foo zzz", []string{"FOO=bar"}, &args)
|
_, err := parseWithEnv("--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)
|
||||||
}
|
}
|
||||||
|
@ -821,7 +708,7 @@ func TestEnvironmentVariableError(t *testing.T) {
|
||||||
var args struct {
|
var args struct {
|
||||||
Foo int `arg:"env"`
|
Foo int `arg:"env"`
|
||||||
}
|
}
|
||||||
_, err := parseWithEnv(Config{}, "", []string{"FOO=bar"}, &args)
|
_, err := parseWithEnv("", []string{"FOO=bar"}, &args)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -829,7 +716,7 @@ func TestEnvironmentVariableRequired(t *testing.T) {
|
||||||
var args struct {
|
var args struct {
|
||||||
Foo string `arg:"env,required"`
|
Foo string `arg:"env,required"`
|
||||||
}
|
}
|
||||||
_, err := parseWithEnv(Config{}, "", []string{"FOO=bar"}, &args)
|
_, err := parseWithEnv("", []string{"FOO=bar"}, &args)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, "bar", args.Foo)
|
assert.Equal(t, "bar", args.Foo)
|
||||||
}
|
}
|
||||||
|
@ -838,7 +725,7 @@ func TestEnvironmentVariableSliceArgumentString(t *testing.T) {
|
||||||
var args struct {
|
var args struct {
|
||||||
Foo []string `arg:"env"`
|
Foo []string `arg:"env"`
|
||||||
}
|
}
|
||||||
_, err := parseWithEnv(Config{}, "", []string{`FOO=bar,"baz, qux"`}, &args)
|
_, err := parseWithEnv("", []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)
|
||||||
}
|
}
|
||||||
|
@ -847,7 +734,7 @@ func TestEnvironmentVariableSliceEmpty(t *testing.T) {
|
||||||
var args struct {
|
var args struct {
|
||||||
Foo []string `arg:"env"`
|
Foo []string `arg:"env"`
|
||||||
}
|
}
|
||||||
_, err := parseWithEnv(Config{}, "", []string{`FOO=`}, &args)
|
_, err := parseWithEnv("", []string{`FOO=`}, &args)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Len(t, args.Foo, 0)
|
assert.Len(t, args.Foo, 0)
|
||||||
}
|
}
|
||||||
|
@ -856,7 +743,7 @@ func TestEnvironmentVariableSliceArgumentInteger(t *testing.T) {
|
||||||
var args struct {
|
var args struct {
|
||||||
Foo []int `arg:"env"`
|
Foo []int `arg:"env"`
|
||||||
}
|
}
|
||||||
_, err := parseWithEnv(Config{}, "", []string{`FOO=1,99`}, &args)
|
_, err := parseWithEnv("", []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)
|
||||||
}
|
}
|
||||||
|
@ -865,7 +752,7 @@ func TestEnvironmentVariableSliceArgumentFloat(t *testing.T) {
|
||||||
var args struct {
|
var args struct {
|
||||||
Foo []float32 `arg:"env"`
|
Foo []float32 `arg:"env"`
|
||||||
}
|
}
|
||||||
_, err := parseWithEnv(Config{}, "", []string{`FOO=1.1,99.9`}, &args)
|
_, err := parseWithEnv("", []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)
|
||||||
}
|
}
|
||||||
|
@ -874,7 +761,7 @@ func TestEnvironmentVariableSliceArgumentBool(t *testing.T) {
|
||||||
var args struct {
|
var args struct {
|
||||||
Foo []bool `arg:"env"`
|
Foo []bool `arg:"env"`
|
||||||
}
|
}
|
||||||
_, err := parseWithEnv(Config{}, "", []string{`FOO=true,false,0,1`}, &args)
|
_, err := parseWithEnv("", []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)
|
||||||
}
|
}
|
||||||
|
@ -883,7 +770,7 @@ func TestEnvironmentVariableSliceArgumentWrongCsv(t *testing.T) {
|
||||||
var args struct {
|
var args struct {
|
||||||
Foo []int `arg:"env"`
|
Foo []int `arg:"env"`
|
||||||
}
|
}
|
||||||
_, err := parseWithEnv(Config{}, "", []string{`FOO=1,99\"`}, &args)
|
_, err := parseWithEnv("", []string{`FOO=1,99\"`}, &args)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -891,7 +778,7 @@ func TestEnvironmentVariableSliceArgumentWrongType(t *testing.T) {
|
||||||
var args struct {
|
var args struct {
|
||||||
Foo []bool `arg:"env"`
|
Foo []bool `arg:"env"`
|
||||||
}
|
}
|
||||||
_, err := parseWithEnv(Config{}, "", []string{`FOO=one,two`}, &args)
|
_, err := parseWithEnv("", []string{`FOO=one,two`}, &args)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -899,7 +786,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(Config{}, "", []string{`FOO=1=one,99=ninetynine`}, &args)
|
_, err := parseWithEnv("", []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])
|
||||||
|
@ -910,21 +797,11 @@ 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(Config{}, "", []string{`FOO=`}, &args)
|
_, err := parseWithEnv("", []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"`
|
||||||
|
@ -939,37 +816,6 @@ func TestEnvironmentVariableIgnored(t *testing.T) {
|
||||||
assert.Equal(t, "", args.Foo)
|
assert.Equal(t, "", args.Foo)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDefaultValuesIgnored(t *testing.T) {
|
|
||||||
var args struct {
|
|
||||||
Foo string `default:"bad"`
|
|
||||||
}
|
|
||||||
|
|
||||||
p, err := NewParser(Config{IgnoreDefault: true}, &args)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = p.Parse(nil)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, "", args.Foo)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRequiredEnvironmentOnlyVariableIsMissing(t *testing.T) {
|
|
||||||
var args struct {
|
|
||||||
Foo string `arg:"required,--,env:FOO"`
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := parseWithEnv(Config{}, "", []string{""}, &args)
|
|
||||||
assert.Error(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOptionalEnvironmentOnlyVariable(t *testing.T) {
|
|
||||||
var args struct {
|
|
||||||
Foo string `arg:"env:FOO"`
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := parseWithEnv(Config{}, "", []string{}, &args)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEnvironmentVariableInSubcommandIgnored(t *testing.T) {
|
func TestEnvironmentVariableInSubcommandIgnored(t *testing.T) {
|
||||||
var args struct {
|
var args struct {
|
||||||
Sub *struct {
|
Sub *struct {
|
||||||
|
@ -982,51 +828,10 @@ func TestEnvironmentVariableInSubcommandIgnored(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
err = p.Parse([]string{"sub"})
|
err = p.Parse([]string{"sub"})
|
||||||
require.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
require.NotNil(t, args.Sub)
|
|
||||||
assert.Equal(t, "", args.Sub.Foo)
|
assert.Equal(t, "", args.Sub.Foo)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParserMustParseEmptyArgs(t *testing.T) {
|
|
||||||
// this mirrors TestEmptyArgs
|
|
||||||
p, err := NewParser(Config{}, &struct{}{})
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.NotNil(t, p)
|
|
||||||
p.MustParse(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParserMustParse(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args versioned
|
|
||||||
cmdLine []string
|
|
||||||
code int
|
|
||||||
output string
|
|
||||||
}{
|
|
||||||
{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: "invalid", args: struct{}{}, cmdLine: []string{"invalid"}, code: 2, output: ""},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
tt := tt
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
var exitCode int
|
|
||||||
var stdout bytes.Buffer
|
|
||||||
exit := func(code int) { exitCode = code }
|
|
||||||
|
|
||||||
p, err := NewParser(Config{Exit: exit, Out: &stdout}, &tt.args)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.NotNil(t, p)
|
|
||||||
|
|
||||||
p.MustParse(tt.cmdLine)
|
|
||||||
assert.NotNil(t, exitCode)
|
|
||||||
assert.Equal(t, tt.code, exitCode)
|
|
||||||
assert.Contains(t, stdout.String(), tt.output)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type textUnmarshaler struct {
|
type textUnmarshaler struct {
|
||||||
val int
|
val int
|
||||||
}
|
}
|
||||||
|
@ -1480,55 +1285,11 @@ func TestReuseParser(t *testing.T) {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNoVersion(t *testing.T) {
|
func TestVersion(t *testing.T) {
|
||||||
var args struct{}
|
var args struct{}
|
||||||
|
err := parse("--version", &args)
|
||||||
p, err := NewParser(Config{}, &args)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = p.Parse([]string{"--version"})
|
|
||||||
assert.Error(t, err)
|
|
||||||
assert.NotEqual(t, ErrVersion, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuiltinVersion(t *testing.T) {
|
|
||||||
var args struct{}
|
|
||||||
|
|
||||||
p, err := NewParser(Config{}, &args)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
p.version = "example 3.2.1"
|
|
||||||
|
|
||||||
err = p.Parse([]string{"--version"})
|
|
||||||
assert.Equal(t, ErrVersion, err)
|
assert.Equal(t, ErrVersion, err)
|
||||||
}
|
|
||||||
|
|
||||||
func TestArgsVersion(t *testing.T) {
|
|
||||||
var args struct {
|
|
||||||
Version bool `arg:"--version"`
|
|
||||||
}
|
|
||||||
|
|
||||||
p, err := NewParser(Config{}, &args)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = p.Parse([]string{"--version"})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, args.Version, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestArgsAndBuiltinVersion(t *testing.T) {
|
|
||||||
var args struct {
|
|
||||||
Version bool `arg:"--version"`
|
|
||||||
}
|
|
||||||
|
|
||||||
p, err := NewParser(Config{}, &args)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
p.version = "example 3.2.1"
|
|
||||||
|
|
||||||
err = p.Parse([]string{"--version"})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, args.Version, true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMultipleTerminates(t *testing.T) {
|
func TestMultipleTerminates(t *testing.T) {
|
||||||
|
@ -1559,21 +1320,13 @@ func TestDefaultOptionValues(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, 123, args.A)
|
assert.Equal(t, 123, args.A)
|
||||||
if assert.NotNil(t, args.B) {
|
|
||||||
assert.Equal(t, 123, *args.B)
|
assert.Equal(t, 123, *args.B)
|
||||||
}
|
|
||||||
assert.Equal(t, "xyz", args.C)
|
assert.Equal(t, "xyz", args.C)
|
||||||
if assert.NotNil(t, args.D) {
|
|
||||||
assert.Equal(t, "abc", *args.D)
|
assert.Equal(t, "abc", *args.D)
|
||||||
}
|
|
||||||
assert.Equal(t, 4.56, args.E)
|
assert.Equal(t, 4.56, args.E)
|
||||||
if assert.NotNil(t, args.F) {
|
|
||||||
assert.Equal(t, 1.23, *args.F)
|
assert.Equal(t, 1.23, *args.F)
|
||||||
}
|
|
||||||
assert.True(t, args.G)
|
assert.True(t, args.G)
|
||||||
if assert.NotNil(t, args.H) {
|
assert.True(t, args.G)
|
||||||
assert.True(t, *args.H)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDefaultUnparseable(t *testing.T) {
|
func TestDefaultUnparseable(t *testing.T) {
|
||||||
|
@ -1582,7 +1335,7 @@ func TestDefaultUnparseable(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
err := parse("", &args)
|
err := parse("", &args)
|
||||||
assert.EqualError(t, err, `.A: error processing default value: strconv.ParseInt: parsing "x": invalid syntax`)
|
assert.EqualError(t, err, `error processing default value for --a: strconv.ParseInt: parsing "x": invalid syntax`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDefaultPositionalValues(t *testing.T) {
|
func TestDefaultPositionalValues(t *testing.T) {
|
||||||
|
@ -1601,21 +1354,13 @@ func TestDefaultPositionalValues(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, 456, args.A)
|
assert.Equal(t, 456, args.A)
|
||||||
if assert.NotNil(t, args.B) {
|
|
||||||
assert.Equal(t, 789, *args.B)
|
assert.Equal(t, 789, *args.B)
|
||||||
}
|
|
||||||
assert.Equal(t, "abc", args.C)
|
assert.Equal(t, "abc", args.C)
|
||||||
if assert.NotNil(t, args.D) {
|
|
||||||
assert.Equal(t, "abc", *args.D)
|
assert.Equal(t, "abc", *args.D)
|
||||||
}
|
|
||||||
assert.Equal(t, 1.23, args.E)
|
assert.Equal(t, 1.23, args.E)
|
||||||
if assert.NotNil(t, args.F) {
|
|
||||||
assert.Equal(t, 1.23, *args.F)
|
assert.Equal(t, 1.23, *args.F)
|
||||||
}
|
|
||||||
assert.True(t, args.G)
|
assert.True(t, args.G)
|
||||||
if assert.NotNil(t, args.H) {
|
assert.True(t, args.G)
|
||||||
assert.True(t, *args.H)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDefaultValuesNotAllowedWithRequired(t *testing.T) {
|
func TestDefaultValuesNotAllowedWithRequired(t *testing.T) {
|
||||||
|
@ -1629,7 +1374,7 @@ func TestDefaultValuesNotAllowedWithRequired(t *testing.T) {
|
||||||
|
|
||||||
func TestDefaultValuesNotAllowedWithSlice(t *testing.T) {
|
func TestDefaultValuesNotAllowedWithSlice(t *testing.T) {
|
||||||
var args struct {
|
var args struct {
|
||||||
A []int `default:"invalid"` // default values not allowed with slices
|
A []int `default:"123"` // required not allowed with default!
|
||||||
}
|
}
|
||||||
|
|
||||||
err := parse("", &args)
|
err := parse("", &args)
|
||||||
|
@ -1646,201 +1391,68 @@ func TestUnexportedFieldsSkipped(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMustParseInvalidParser(t *testing.T) {
|
func TestMustParseInvalidParser(t *testing.T) {
|
||||||
|
originalExit := osExit
|
||||||
|
originalStdout := stdout
|
||||||
|
defer func() {
|
||||||
|
osExit = originalExit
|
||||||
|
stdout = originalStdout
|
||||||
|
}()
|
||||||
|
|
||||||
var exitCode int
|
var exitCode int
|
||||||
var stdout bytes.Buffer
|
osExit = func(code int) { exitCode = code }
|
||||||
exit := func(code int) { exitCode = code }
|
stdout = &bytes.Buffer{}
|
||||||
|
|
||||||
var args struct {
|
var args struct {
|
||||||
CannotParse struct{}
|
CannotParse struct{}
|
||||||
}
|
}
|
||||||
parser := mustParse(Config{Out: &stdout, Exit: exit}, &args)
|
parser := MustParse(&args)
|
||||||
assert.Nil(t, parser)
|
assert.Nil(t, parser)
|
||||||
assert.Equal(t, 2, exitCode)
|
assert.Equal(t, -1, exitCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMustParsePrintsHelp(t *testing.T) {
|
func TestMustParsePrintsHelp(t *testing.T) {
|
||||||
|
originalExit := osExit
|
||||||
|
originalStdout := stdout
|
||||||
originalArgs := os.Args
|
originalArgs := os.Args
|
||||||
defer func() {
|
defer func() {
|
||||||
|
osExit = originalExit
|
||||||
|
stdout = originalStdout
|
||||||
os.Args = originalArgs
|
os.Args = originalArgs
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
var exitCode *int
|
||||||
|
osExit = func(code int) { exitCode = &code }
|
||||||
os.Args = []string{"someprogram", "--help"}
|
os.Args = []string{"someprogram", "--help"}
|
||||||
|
stdout = &bytes.Buffer{}
|
||||||
var exitCode int
|
|
||||||
var stdout bytes.Buffer
|
|
||||||
exit := func(code int) { exitCode = code }
|
|
||||||
|
|
||||||
var args struct{}
|
var args struct{}
|
||||||
parser := mustParse(Config{Out: &stdout, Exit: exit}, &args)
|
parser := MustParse(&args)
|
||||||
assert.NotNil(t, parser)
|
assert.NotNil(t, parser)
|
||||||
assert.Equal(t, 0, exitCode)
|
require.NotNil(t, exitCode)
|
||||||
|
assert.Equal(t, 0, *exitCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMustParsePrintsVersion(t *testing.T) {
|
func TestMustParsePrintsVersion(t *testing.T) {
|
||||||
|
originalExit := osExit
|
||||||
|
originalStdout := stdout
|
||||||
originalArgs := os.Args
|
originalArgs := os.Args
|
||||||
defer func() {
|
defer func() {
|
||||||
|
osExit = originalExit
|
||||||
|
stdout = originalStdout
|
||||||
os.Args = originalArgs
|
os.Args = originalArgs
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var exitCode int
|
var exitCode *int
|
||||||
var stdout bytes.Buffer
|
osExit = func(code int) { exitCode = &code }
|
||||||
exit := func(code int) { exitCode = code }
|
|
||||||
|
|
||||||
os.Args = []string{"someprogram", "--version"}
|
os.Args = []string{"someprogram", "--version"}
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
stdout = &b
|
||||||
|
|
||||||
var args versioned
|
var args versioned
|
||||||
parser := mustParse(Config{Out: &stdout, Exit: exit}, &args)
|
parser := MustParse(&args)
|
||||||
require.NotNil(t, parser)
|
require.NotNil(t, parser)
|
||||||
assert.Equal(t, 0, exitCode)
|
require.NotNil(t, exitCode)
|
||||||
assert.Equal(t, "example 3.2.1\n", stdout.String())
|
assert.Equal(t, 0, *exitCode)
|
||||||
}
|
assert.Equal(t, "example 3.2.1\n", b.String())
|
||||||
|
|
||||||
type mapWithUnmarshalText struct {
|
|
||||||
val map[string]string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *mapWithUnmarshalText) UnmarshalText(data []byte) error {
|
|
||||||
return json.Unmarshal(data, &v.val)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTextUnmarshalerEmpty(t *testing.T) {
|
|
||||||
// based on https://github.com/alexflint/go-arg/issues/184
|
|
||||||
var args struct {
|
|
||||||
Config mapWithUnmarshalText `arg:"--config"`
|
|
||||||
}
|
|
||||||
|
|
||||||
err := parse("", &args)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Empty(t, args.Config)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTextUnmarshalerEmptyPointer(t *testing.T) {
|
|
||||||
// a slight variant on https://github.com/alexflint/go-arg/issues/184
|
|
||||||
var args struct {
|
|
||||||
Config *mapWithUnmarshalText `arg:"--config"`
|
|
||||||
}
|
|
||||||
|
|
||||||
err := parse("", &args)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Nil(t, args.Config)
|
|
||||||
}
|
|
||||||
|
|
||||||
// similar to the above but also implements MarshalText
|
|
||||||
type mapWithMarshalText struct {
|
|
||||||
val map[string]string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *mapWithMarshalText) MarshalText(data []byte) error {
|
|
||||||
return json.Unmarshal(data, &v.val)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *mapWithMarshalText) UnmarshalText(data []byte) error {
|
|
||||||
return json.Unmarshal(data, &v.val)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTextMarshalerUnmarshalerEmpty(t *testing.T) {
|
|
||||||
// based on https://github.com/alexflint/go-arg/issues/184
|
|
||||||
var args struct {
|
|
||||||
Config mapWithMarshalText `arg:"--config"`
|
|
||||||
}
|
|
||||||
|
|
||||||
err := parse("", &args)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Empty(t, args.Config)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTextMarshalerUnmarshalerEmptyPointer(t *testing.T) {
|
|
||||||
// a slight variant on https://github.com/alexflint/go-arg/issues/184
|
|
||||||
var args struct {
|
|
||||||
Config *mapWithMarshalText `arg:"--config"`
|
|
||||||
}
|
|
||||||
|
|
||||||
err := parse("", &args)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Nil(t, args.Config)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSubcommandGlobalFlag_Before(t *testing.T) {
|
|
||||||
var args struct {
|
|
||||||
Global bool `arg:"-g"`
|
|
||||||
Sub *struct {
|
|
||||||
} `arg:"subcommand"`
|
|
||||||
}
|
|
||||||
|
|
||||||
p, err := NewParser(Config{StrictSubcommands: false}, &args)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = p.Parse([]string{"-g", "sub"})
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.True(t, args.Global)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSubcommandGlobalFlag_InCommand(t *testing.T) {
|
|
||||||
var args struct {
|
|
||||||
Global bool `arg:"-g"`
|
|
||||||
Sub *struct {
|
|
||||||
} `arg:"subcommand"`
|
|
||||||
}
|
|
||||||
|
|
||||||
p, err := NewParser(Config{StrictSubcommands: false}, &args)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = p.Parse([]string{"sub", "-g"})
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.True(t, args.Global)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSubcommandGlobalFlag_Before_Strict(t *testing.T) {
|
|
||||||
var args struct {
|
|
||||||
Global bool `arg:"-g"`
|
|
||||||
Sub *struct {
|
|
||||||
} `arg:"subcommand"`
|
|
||||||
}
|
|
||||||
|
|
||||||
p, err := NewParser(Config{StrictSubcommands: true}, &args)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = p.Parse([]string{"-g", "sub"})
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.True(t, args.Global)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSubcommandGlobalFlag_InCommand_Strict(t *testing.T) {
|
|
||||||
var args struct {
|
|
||||||
Global bool `arg:"-g"`
|
|
||||||
Sub *struct {
|
|
||||||
} `arg:"subcommand"`
|
|
||||||
}
|
|
||||||
|
|
||||||
p, err := NewParser(Config{StrictSubcommands: true}, &args)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = p.Parse([]string{"sub", "-g"})
|
|
||||||
assert.Error(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSubcommandGlobalFlag_InCommand_Strict_Inner(t *testing.T) {
|
|
||||||
var args struct {
|
|
||||||
Global bool `arg:"-g"`
|
|
||||||
Sub *struct {
|
|
||||||
Guard bool `arg:"-g"`
|
|
||||||
} `arg:"subcommand"`
|
|
||||||
}
|
|
||||||
|
|
||||||
p, err := NewParser(Config{StrictSubcommands: true}, &args)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = p.Parse([]string{"sub", "-g"})
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.False(t, args.Global)
|
|
||||||
require.NotNil(t, args.Sub)
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
11
reflect.go
11
reflect.go
|
@ -74,10 +74,10 @@ func cardinalityOf(t reflect.Type) (cardinality, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// isBoolean returns true if the type is a boolean or a pointer to a boolean
|
// isBoolean returns true if the type can be parsed from a single string
|
||||||
func isBoolean(t reflect.Type) bool {
|
func isBoolean(t reflect.Type) bool {
|
||||||
switch {
|
switch {
|
||||||
case isTextUnmarshaler(t):
|
case t.Implements(textUnmarshalerType):
|
||||||
return false
|
return false
|
||||||
case t.Kind() == reflect.Bool:
|
case t.Kind() == reflect.Bool:
|
||||||
return true
|
return true
|
||||||
|
@ -88,11 +88,6 @@ func isBoolean(t reflect.Type) bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// isTextUnmarshaler returns true if the type or its pointer implements encoding.TextUnmarshaler
|
|
||||||
func isTextUnmarshaler(t reflect.Type) bool {
|
|
||||||
return t.Implements(textUnmarshalerType) || reflect.PtrTo(t).Implements(textUnmarshalerType)
|
|
||||||
}
|
|
||||||
|
|
||||||
// isExported returns true if the struct field name is exported
|
// isExported returns true if the struct field name is exported
|
||||||
func isExported(field string) bool {
|
func isExported(field string) bool {
|
||||||
r, _ := utf8.DecodeRuneInString(field) // returns RuneError for empty string or invalid UTF8
|
r, _ := utf8.DecodeRuneInString(field) // returns RuneError for empty string or invalid UTF8
|
||||||
|
@ -102,7 +97,7 @@ func isExported(field string) bool {
|
||||||
// isZero returns true if v contains the zero value for its type
|
// isZero returns true if v contains the zero value for its type
|
||||||
func isZero(v reflect.Value) bool {
|
func isZero(v reflect.Value) bool {
|
||||||
t := v.Type()
|
t := v.Type()
|
||||||
if t.Kind() == reflect.Ptr || t.Kind() == reflect.Slice || t.Kind() == reflect.Map || t.Kind() == reflect.Chan || t.Kind() == reflect.Interface {
|
if t.Kind() == reflect.Slice || t.Kind() == reflect.Map {
|
||||||
return v.IsNil()
|
return v.IsNil()
|
||||||
}
|
}
|
||||||
if !t.Comparable() {
|
if !t.Comparable() {
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
package arg
|
package arg
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
// Subcommand returns the user struct for the subcommand selected by
|
// Subcommand returns the user struct for the subcommand selected by
|
||||||
// the command line arguments most recently processed by the parser.
|
// the command line arguments most recently processed by the parser.
|
||||||
// The return value is always a pointer to a struct. If no subcommand
|
// The return value is always a pointer to a struct. If no subcommand
|
||||||
|
@ -9,35 +7,31 @@ import "fmt"
|
||||||
// no command line arguments have been processed by this parser then it
|
// no command line arguments have been processed by this parser then it
|
||||||
// returns nil.
|
// returns nil.
|
||||||
func (p *Parser) Subcommand() interface{} {
|
func (p *Parser) Subcommand() interface{} {
|
||||||
if len(p.subcommand) == 0 {
|
if p.lastCmd == nil || p.lastCmd.parent == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
cmd, err := p.lookupCommand(p.subcommand...)
|
return p.val(p.lastCmd.dest).Interface()
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return p.val(cmd.dest).Interface()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SubcommandNames returns the sequence of subcommands specified by the
|
// SubcommandNames returns the sequence of subcommands specified by the
|
||||||
// user. If no subcommands were given then it returns an empty slice.
|
// user. If no subcommands were given then it returns an empty slice.
|
||||||
func (p *Parser) SubcommandNames() []string {
|
func (p *Parser) SubcommandNames() []string {
|
||||||
return p.subcommand
|
if p.lastCmd == nil {
|
||||||
}
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// lookupCommand finds a subcommand based on a sequence of subcommand names. The
|
// make a list of ancestor commands
|
||||||
// first string should be a top-level subcommand, the next should be a child
|
var ancestors []string
|
||||||
// subcommand of that subcommand, and so on. If no strings are given then the
|
cur := p.lastCmd
|
||||||
// root command is returned. If no such subcommand exists then an error is
|
for cur.parent != nil { // we want to exclude the root
|
||||||
// returned.
|
ancestors = append(ancestors, cur.name)
|
||||||
func (p *Parser) lookupCommand(path ...string) (*command, error) {
|
cur = cur.parent
|
||||||
cmd := p.cmd
|
|
||||||
for _, name := range path {
|
|
||||||
found := findSubcommand(cmd.subcommands, name)
|
|
||||||
if found == nil {
|
|
||||||
return nil, fmt.Errorf("%q is not a subcommand of %s", name, cmd.name)
|
|
||||||
}
|
}
|
||||||
cmd = found
|
|
||||||
|
// reverse the list
|
||||||
|
out := make([]string, len(ancestors))
|
||||||
|
for i := 0; i < len(ancestors); i++ {
|
||||||
|
out[i] = ancestors[len(ancestors)-i-1]
|
||||||
}
|
}
|
||||||
return cmd, nil
|
return out
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,19 +83,6 @@ func TestNamedSubcommand(t *testing.T) {
|
||||||
assert.Equal(t, []string{"ls"}, p.SubcommandNames())
|
assert.Equal(t, []string{"ls"}, p.SubcommandNames())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSubcommandAliases(t *testing.T) {
|
|
||||||
type listCmd struct {
|
|
||||||
}
|
|
||||||
var args struct {
|
|
||||||
List *listCmd `arg:"subcommand:list|ls"`
|
|
||||||
}
|
|
||||||
p, err := pparse("ls", &args)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.NotNil(t, args.List)
|
|
||||||
assert.Equal(t, args.List, p.Subcommand())
|
|
||||||
assert.Equal(t, []string{"ls"}, p.SubcommandNames())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEmptySubcommand(t *testing.T) {
|
func TestEmptySubcommand(t *testing.T) {
|
||||||
type listCmd struct {
|
type listCmd struct {
|
||||||
}
|
}
|
||||||
|
@ -126,23 +113,6 @@ func TestTwoSubcommands(t *testing.T) {
|
||||||
assert.Equal(t, []string{"list"}, p.SubcommandNames())
|
assert.Equal(t, []string{"list"}, p.SubcommandNames())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTwoSubcommandsWithAliases(t *testing.T) {
|
|
||||||
type getCmd struct {
|
|
||||||
}
|
|
||||||
type listCmd struct {
|
|
||||||
}
|
|
||||||
var args struct {
|
|
||||||
Get *getCmd `arg:"subcommand:get|g"`
|
|
||||||
List *listCmd `arg:"subcommand:list|ls"`
|
|
||||||
}
|
|
||||||
p, err := pparse("ls", &args)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Nil(t, args.Get)
|
|
||||||
assert.NotNil(t, args.List)
|
|
||||||
assert.Equal(t, args.List, p.Subcommand())
|
|
||||||
assert.Equal(t, []string{"ls"}, p.SubcommandNames())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSubcommandsWithOptions(t *testing.T) {
|
func TestSubcommandsWithOptions(t *testing.T) {
|
||||||
type getCmd struct {
|
type getCmd struct {
|
||||||
Name string
|
Name string
|
||||||
|
@ -305,60 +275,6 @@ func TestNestedSubcommands(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNestedSubcommandsWithAliases(t *testing.T) {
|
|
||||||
type child struct{}
|
|
||||||
type parent struct {
|
|
||||||
Child *child `arg:"subcommand:child|ch"`
|
|
||||||
}
|
|
||||||
type grandparent struct {
|
|
||||||
Parent *parent `arg:"subcommand:parent|pa"`
|
|
||||||
}
|
|
||||||
type root struct {
|
|
||||||
Grandparent *grandparent `arg:"subcommand:grandparent|gp"`
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
var args root
|
|
||||||
p, err := pparse("gp parent child", &args)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, args.Grandparent)
|
|
||||||
require.NotNil(t, args.Grandparent.Parent)
|
|
||||||
require.NotNil(t, args.Grandparent.Parent.Child)
|
|
||||||
assert.Equal(t, args.Grandparent.Parent.Child, p.Subcommand())
|
|
||||||
assert.Equal(t, []string{"gp", "parent", "child"}, p.SubcommandNames())
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
var args root
|
|
||||||
p, err := pparse("grandparent pa", &args)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, args.Grandparent)
|
|
||||||
require.NotNil(t, args.Grandparent.Parent)
|
|
||||||
require.Nil(t, args.Grandparent.Parent.Child)
|
|
||||||
assert.Equal(t, args.Grandparent.Parent, p.Subcommand())
|
|
||||||
assert.Equal(t, []string{"grandparent", "pa"}, p.SubcommandNames())
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
var args root
|
|
||||||
p, err := pparse("grandparent", &args)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, args.Grandparent)
|
|
||||||
require.Nil(t, args.Grandparent.Parent)
|
|
||||||
assert.Equal(t, args.Grandparent, p.Subcommand())
|
|
||||||
assert.Equal(t, []string{"grandparent"}, p.SubcommandNames())
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
var args root
|
|
||||||
p, err := pparse("", &args)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Nil(t, args.Grandparent)
|
|
||||||
assert.Nil(t, p.Subcommand())
|
|
||||||
assert.Empty(t, p.SubcommandNames())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSubcommandsWithPositionals(t *testing.T) {
|
func TestSubcommandsWithPositionals(t *testing.T) {
|
||||||
type listCmd struct {
|
type listCmd struct {
|
||||||
Pattern string `arg:"positional"`
|
Pattern string `arg:"positional"`
|
||||||
|
@ -495,14 +411,3 @@ func TestValForNilStruct(t *testing.T) {
|
||||||
v := p.val(path{fields: []reflect.StructField{subField, subField}})
|
v := p.val(path{fields: []reflect.StructField{subField, subField}})
|
||||||
assert.False(t, v.IsValid())
|
assert.False(t, v.IsValid())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSubcommandInvalidInternal(t *testing.T) {
|
|
||||||
// this situation should never arise in practice but still good to test for it
|
|
||||||
var cmd struct{}
|
|
||||||
p, err := NewParser(Config{}, &cmd)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
p.subcommand = []string{"should", "never", "happen"}
|
|
||||||
sub := p.Subcommand()
|
|
||||||
assert.Nil(t, sub)
|
|
||||||
}
|
|
||||||
|
|
222
usage.go
222
usage.go
|
@ -3,37 +3,54 @@ package arg
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// the width of the left column
|
// the width of the left column
|
||||||
const colWidth = 25
|
const colWidth = 25
|
||||||
|
|
||||||
// Fail prints usage information to p.Config.Out and exits with status code 2.
|
// to allow monkey patching in tests
|
||||||
|
var (
|
||||||
|
stdout io.Writer = os.Stdout
|
||||||
|
stderr io.Writer = os.Stderr
|
||||||
|
osExit = os.Exit
|
||||||
|
)
|
||||||
|
|
||||||
|
// Fail prints usage information to stderr and exits with non-zero status
|
||||||
func (p *Parser) Fail(msg string) {
|
func (p *Parser) Fail(msg string) {
|
||||||
p.FailSubcommand(msg)
|
p.failWithSubcommand(msg, p.cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FailSubcommand prints usage information for a specified subcommand to p.Config.Out,
|
// FailSubcommand prints usage information for a specified subcommand to stderr,
|
||||||
// then exits with status code 2. To write usage information for a top-level
|
// then exits with non-zero status. To write usage information for a top-level
|
||||||
// subcommand, provide just the name of that subcommand. To write usage
|
// 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
|
||||||
// on down the tree.
|
// on down the tree.
|
||||||
func (p *Parser) FailSubcommand(msg string, subcommand ...string) error {
|
func (p *Parser) FailSubcommand(msg string, subcommand ...string) error {
|
||||||
err := p.WriteUsageForSubcommand(p.config.Out, subcommand...)
|
cmd, err := p.lookupCommand(subcommand...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
p.failWithSubcommand(msg, cmd)
|
||||||
fmt.Fprintln(p.config.Out, "error:", msg)
|
|
||||||
p.config.Exit(2)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// failWithSubcommand prints usage information for the given subcommand to stderr and exits with non-zero status
|
||||||
|
func (p *Parser) failWithSubcommand(msg string, cmd *command) {
|
||||||
|
p.writeUsageForSubcommand(stderr, cmd)
|
||||||
|
fmt.Fprintln(stderr, "error:", msg)
|
||||||
|
osExit(-1)
|
||||||
|
}
|
||||||
|
|
||||||
// WriteUsage writes usage information to the given writer
|
// WriteUsage writes usage information to the given writer
|
||||||
func (p *Parser) WriteUsage(w io.Writer) {
|
func (p *Parser) WriteUsage(w io.Writer) {
|
||||||
p.WriteUsageForSubcommand(w, p.subcommand...)
|
cmd := p.cmd
|
||||||
|
if p.lastCmd != nil {
|
||||||
|
cmd = p.lastCmd
|
||||||
|
}
|
||||||
|
p.writeUsageForSubcommand(w, cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteUsageForSubcommand writes the usage information for a specified
|
// WriteUsageForSubcommand writes the usage information for a specified
|
||||||
|
@ -46,7 +63,12 @@ func (p *Parser) WriteUsageForSubcommand(w io.Writer, subcommand ...string) erro
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
p.writeUsageForSubcommand(w, cmd)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeUsageForSubcommand writes usage information for the given subcommand
|
||||||
|
func (p *Parser) writeUsageForSubcommand(w io.Writer, cmd *command) {
|
||||||
var positionals, longOptions, shortOptions []*spec
|
var positionals, longOptions, shortOptions []*spec
|
||||||
for _, spec := range cmd.specs {
|
for _, spec := range cmd.specs {
|
||||||
switch {
|
switch {
|
||||||
|
@ -59,10 +81,22 @@ func (p *Parser) WriteUsageForSubcommand(w io.Writer, subcommand ...string) erro
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if p.version != "" {
|
||||||
|
fmt.Fprintln(w, p.version)
|
||||||
|
}
|
||||||
|
|
||||||
|
// make a list of ancestor commands so that we print with full context
|
||||||
|
var ancestors []string
|
||||||
|
ancestor := cmd
|
||||||
|
for ancestor != nil {
|
||||||
|
ancestors = append(ancestors, ancestor.name)
|
||||||
|
ancestor = ancestor.parent
|
||||||
|
}
|
||||||
|
|
||||||
// print the beginning of the usage string
|
// print the beginning of the usage string
|
||||||
fmt.Fprintf(w, "Usage: %s", p.cmd.name)
|
fmt.Fprint(w, "Usage:")
|
||||||
for _, s := range subcommand {
|
for i := len(ancestors) - 1; i >= 0; i-- {
|
||||||
fmt.Fprint(w, " "+s)
|
fmt.Fprint(w, " "+ancestors[i])
|
||||||
}
|
}
|
||||||
|
|
||||||
// write the option component of the usage message
|
// write the option component of the usage message
|
||||||
|
@ -123,66 +157,47 @@ func (p *Parser) WriteUsageForSubcommand(w io.Writer, subcommand ...string) erro
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprint(w, "\n")
|
fmt.Fprint(w, "\n")
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// print prints a line like this:
|
func printTwoCols(w io.Writer, left, help string, defaultVal string, envVal string) {
|
||||||
//
|
lhs := " " + left
|
||||||
// --option FOO A description of the option [default: 123]
|
|
||||||
//
|
|
||||||
// If the text on the left is longer than a certain threshold, the description is moved to the next line:
|
|
||||||
//
|
|
||||||
// --verylongoptionoption VERY_LONG_VARIABLE
|
|
||||||
// A description of the option [default: 123]
|
|
||||||
//
|
|
||||||
// If multiple "extras" are provided then they are put inside a single set of square brackets:
|
|
||||||
//
|
|
||||||
// --option FOO A description of the option [default: 123, env: FOO]
|
|
||||||
func print(w io.Writer, item, description string, bracketed ...string) {
|
|
||||||
lhs := " " + item
|
|
||||||
fmt.Fprint(w, lhs)
|
fmt.Fprint(w, lhs)
|
||||||
if description != "" {
|
if help != "" {
|
||||||
if len(lhs)+2 < colWidth {
|
if len(lhs)+2 < colWidth {
|
||||||
fmt.Fprint(w, strings.Repeat(" ", colWidth-len(lhs)))
|
fmt.Fprint(w, strings.Repeat(" ", colWidth-len(lhs)))
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprint(w, "\n"+strings.Repeat(" ", colWidth))
|
fmt.Fprint(w, "\n"+strings.Repeat(" ", colWidth))
|
||||||
}
|
}
|
||||||
fmt.Fprint(w, description)
|
fmt.Fprint(w, help)
|
||||||
}
|
}
|
||||||
|
|
||||||
var brack string
|
bracketsContent := []string{}
|
||||||
for _, s := range bracketed {
|
|
||||||
if s != "" {
|
if defaultVal != "" {
|
||||||
if brack != "" {
|
bracketsContent = append(bracketsContent,
|
||||||
brack += ", "
|
fmt.Sprintf("default: %s", defaultVal),
|
||||||
}
|
)
|
||||||
brack += s
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if brack != "" {
|
if envVal != "" {
|
||||||
fmt.Fprintf(w, " [%s]", brack)
|
bracketsContent = append(bracketsContent,
|
||||||
|
fmt.Sprintf("env: %s", envVal),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(bracketsContent) > 0 {
|
||||||
|
fmt.Fprintf(w, " [%s]", strings.Join(bracketsContent, ", "))
|
||||||
}
|
}
|
||||||
fmt.Fprint(w, "\n")
|
fmt.Fprint(w, "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
func withDefault(s string) string {
|
|
||||||
if s == "" {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return "default: " + s
|
|
||||||
}
|
|
||||||
|
|
||||||
func withEnv(env string) string {
|
|
||||||
if env == "" {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return "env: " + env
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteHelp writes the usage string followed by the full help string for each option
|
// WriteHelp writes the usage string followed by the full help string for each option
|
||||||
func (p *Parser) WriteHelp(w io.Writer) {
|
func (p *Parser) WriteHelp(w io.Writer) {
|
||||||
p.WriteHelpForSubcommand(w, p.subcommand...)
|
cmd := p.cmd
|
||||||
|
if p.lastCmd != nil {
|
||||||
|
cmd = p.lastCmd
|
||||||
|
}
|
||||||
|
p.writeHelpForSubcommand(w, cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteHelpForSubcommand writes the usage string followed by the full help
|
// WriteHelpForSubcommand writes the usage string followed by the full help
|
||||||
|
@ -195,55 +210,34 @@ func (p *Parser) WriteHelpForSubcommand(w io.Writer, subcommand ...string) error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
p.writeHelpForSubcommand(w, cmd)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
var positionals, longOptions, shortOptions, envOnlyOptions []*spec
|
// writeHelp writes the usage string for the given subcommand
|
||||||
var hasVersionOption bool
|
func (p *Parser) writeHelpForSubcommand(w io.Writer, cmd *command) {
|
||||||
|
var positionals, longOptions, shortOptions []*spec
|
||||||
for _, spec := range cmd.specs {
|
for _, spec := range cmd.specs {
|
||||||
switch {
|
switch {
|
||||||
case spec.positional:
|
case spec.positional:
|
||||||
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 == "":
|
|
||||||
envOnlyOptions = append(envOnlyOptions, spec)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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)
|
||||||
}
|
}
|
||||||
|
p.writeUsageForSubcommand(w, cmd)
|
||||||
if !hasVersionOption && p.version != "" {
|
|
||||||
fmt.Fprintln(w, p.version)
|
|
||||||
}
|
|
||||||
|
|
||||||
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, withDefault(spec.defaultString), withEnv(spec.env))
|
printTwoCols(w, spec.placeholder, spec.help, "", "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -258,6 +252,14 @@ func (p *Parser) WriteHelpForSubcommand(w io.Writer, subcommand ...string) error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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")
|
||||||
|
@ -273,7 +275,7 @@ func (p *Parser) WriteHelpForSubcommand(w io.Writer, subcommand ...string) error
|
||||||
short: "h",
|
short: "h",
|
||||||
help: "display this help and exit",
|
help: "display this help and exit",
|
||||||
})
|
})
|
||||||
if !hasVersionOption && p.version != "" {
|
if p.version != "" {
|
||||||
p.printOption(w, &spec{
|
p.printOption(w, &spec{
|
||||||
cardinality: zero,
|
cardinality: zero,
|
||||||
long: "version",
|
long: "version",
|
||||||
|
@ -281,27 +283,13 @@ func (p *Parser) WriteHelpForSubcommand(w io.Writer, subcommand ...string) error
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// write the list of environment only variables
|
|
||||||
if len(envOnlyOptions) > 0 {
|
|
||||||
fmt.Fprint(w, "\nEnvironment variables:\n")
|
|
||||||
for _, spec := range envOnlyOptions {
|
|
||||||
p.printEnvOnlyVar(w, spec)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// write the list of subcommands
|
// write the list of subcommands
|
||||||
if len(cmd.subcommands) > 0 {
|
if len(cmd.subcommands) > 0 {
|
||||||
fmt.Fprint(w, "\nCommands:\n")
|
fmt.Fprint(w, "\nCommands:\n")
|
||||||
for _, subcmd := range cmd.subcommands {
|
for _, subcmd := range cmd.subcommands {
|
||||||
names := append([]string{subcmd.name}, subcmd.aliases...)
|
printTwoCols(w, subcmd.name, subcmd.help, "", "")
|
||||||
print(w, strings.Join(names, ", "), subcmd.help)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.epilogue != "" {
|
|
||||||
fmt.Fprintln(w, "\n"+p.epilogue)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Parser) printOption(w io.Writer, spec *spec) {
|
func (p *Parser) printOption(w io.Writer, spec *spec) {
|
||||||
|
@ -313,30 +301,34 @@ func (p *Parser) printOption(w io.Writer, spec *spec) {
|
||||||
ways = append(ways, synopsis(spec, "-"+spec.short))
|
ways = append(ways, synopsis(spec, "-"+spec.short))
|
||||||
}
|
}
|
||||||
if len(ways) > 0 {
|
if len(ways) > 0 {
|
||||||
print(w, strings.Join(ways, ", "), spec.help, withDefault(spec.defaultString), withEnv(spec.env))
|
printTwoCols(w, strings.Join(ways, ", "), spec.help, spec.defaultVal, spec.env)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Parser) printEnvOnlyVar(w io.Writer, spec *spec) {
|
// lookupCommand finds a subcommand based on a sequence of subcommand names. The
|
||||||
ways := make([]string, 0, 2)
|
// first string should be a top-level subcommand, the next should be a child
|
||||||
if spec.required {
|
// subcommand of that subcommand, and so on. If no strings are given then the
|
||||||
ways = append(ways, "Required.")
|
// root command is returned. If no such subcommand exists then an error is
|
||||||
} else {
|
// returned.
|
||||||
ways = append(ways, "Optional.")
|
func (p *Parser) lookupCommand(path ...string) (*command, error) {
|
||||||
|
cmd := p.cmd
|
||||||
|
for _, name := range path {
|
||||||
|
var found *command
|
||||||
|
for _, child := range cmd.subcommands {
|
||||||
|
if child.name == name {
|
||||||
|
found = child
|
||||||
}
|
}
|
||||||
|
|
||||||
if spec.help != "" {
|
|
||||||
ways = append(ways, spec.help)
|
|
||||||
}
|
}
|
||||||
|
if found == nil {
|
||||||
print(w, spec.env, strings.Join(ways, " "), withDefault(spec.defaultString))
|
return nil, fmt.Errorf("%q is not a subcommand of %s", name, cmd.name)
|
||||||
|
}
|
||||||
|
cmd = found
|
||||||
|
}
|
||||||
|
return cmd, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func synopsis(spec *spec, form string) string {
|
func synopsis(spec *spec, form string) string {
|
||||||
// if the user omits the placeholder tag then we pick one automatically,
|
if spec.cardinality == zero {
|
||||||
// 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
|
||||||
|
|
548
usage_test.go
548
usage_test.go
|
@ -50,16 +50,12 @@ Options:
|
||||||
--optimize OPTIMIZE, -O OPTIMIZE
|
--optimize OPTIMIZE, -O OPTIMIZE
|
||||||
optimization level
|
optimization level
|
||||||
--ids IDS Ids
|
--ids IDS Ids
|
||||||
--values VALUES Values
|
--values VALUES Values [default: [3.14 42 256]]
|
||||||
--workers WORKERS, -w WORKERS
|
--workers WORKERS, -w WORKERS
|
||||||
number of workers to start [default: 10, env: WORKERS]
|
number of workers to start [default: 10, env: WORKERS]
|
||||||
--testenv TESTENV, -a TESTENV [env: TEST_ENV]
|
--testenv TESTENV, -a TESTENV [env: TEST_ENV]
|
||||||
--file FILE, -f FILE File with mandatory extension [default: scratch.txt]
|
--file FILE, -f FILE File with mandatory extension [default: scratch.txt]
|
||||||
--help, -h display this help and exit
|
--help, -h display this help and exit
|
||||||
|
|
||||||
Environment variables:
|
|
||||||
API_KEY Required. Only via env-var for security reasons
|
|
||||||
TRACE Optional. Record low-level trace
|
|
||||||
`
|
`
|
||||||
|
|
||||||
var args struct {
|
var args struct {
|
||||||
|
@ -74,12 +70,11 @@ Environment variables:
|
||||||
Values []float64 `help:"Values"`
|
Values []float64 `help:"Values"`
|
||||||
Workers int `arg:"-w,env:WORKERS" help:"number of workers to start" default:"10"`
|
Workers int `arg:"-w,env:WORKERS" help:"number of workers to start" default:"10"`
|
||||||
TestEnv string `arg:"-a,env:TEST_ENV"`
|
TestEnv string `arg:"-a,env:TEST_ENV"`
|
||||||
ApiKey string `arg:"required,-,--,env:API_KEY" help:"Only via env-var for security reasons"`
|
|
||||||
Trace bool `arg:"-,--,env" help:"Record low-level trace"`
|
|
||||||
File *NameDotName `arg:"-f" help:"File with mandatory extension"`
|
File *NameDotName `arg:"-f" help:"File with mandatory extension"`
|
||||||
}
|
}
|
||||||
args.Name = "Foo Bar"
|
args.Name = "Foo Bar"
|
||||||
args.Value = 42
|
args.Value = 42
|
||||||
|
args.Values = []float64{3.14, 42, 256}
|
||||||
args.File = &NameDotName{"scratch", "txt"}
|
args.File = &NameDotName{"scratch", "txt"}
|
||||||
p, err := NewParser(Config{Program: "example"}, &args)
|
p, err := NewParser(Config{Program: "example"}, &args)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -237,7 +232,7 @@ func (versioned) Version() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUsageWithVersion(t *testing.T) {
|
func TestUsageWithVersion(t *testing.T) {
|
||||||
expectedUsage := "Usage: example"
|
expectedUsage := "example 3.2.1\nUsage: example"
|
||||||
|
|
||||||
expectedHelp := `
|
expectedHelp := `
|
||||||
example 3.2.1
|
example 3.2.1
|
||||||
|
@ -260,233 +255,6 @@ 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
|
||||||
|
@ -517,37 +285,6 @@ Options:
|
||||||
assert.Equal(t, expectedUsage, strings.TrimSpace(usage.String()))
|
assert.Equal(t, expectedUsage, strings.TrimSpace(usage.String()))
|
||||||
}
|
}
|
||||||
|
|
||||||
type epilogued struct{}
|
|
||||||
|
|
||||||
// Epilogued returns the epilogue for this program
|
|
||||||
func (epilogued) Epilogue() string {
|
|
||||||
return "For more information visit github.com/alexflint/go-arg"
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUsageWithEpilogue(t *testing.T) {
|
|
||||||
expectedUsage := "Usage: example"
|
|
||||||
|
|
||||||
expectedHelp := `
|
|
||||||
Usage: example
|
|
||||||
|
|
||||||
Options:
|
|
||||||
--help, -h display this help and exit
|
|
||||||
|
|
||||||
For more information visit github.com/alexflint/go-arg
|
|
||||||
`
|
|
||||||
os.Args[0] = "example"
|
|
||||||
p, err := NewParser(Config{}, &epilogued{})
|
|
||||||
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 TestUsageForRequiredPositionals(t *testing.T) {
|
func TestUsageForRequiredPositionals(t *testing.T) {
|
||||||
expectedUsage := "Usage: example REQUIRED1 REQUIRED2\n"
|
expectedUsage := "Usage: example REQUIRED1 REQUIRED2\n"
|
||||||
var args struct {
|
var args struct {
|
||||||
|
@ -642,50 +379,6 @@ 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"
|
||||||
|
|
||||||
|
@ -721,8 +414,6 @@ Global options:
|
||||||
|
|
||||||
_ = p.Parse([]string{"child", "nested", "value"})
|
_ = p.Parse([]string{"child", "nested", "value"})
|
||||||
|
|
||||||
assert.Equal(t, []string{"child", "nested"}, p.SubcommandNames())
|
|
||||||
|
|
||||||
var help bytes.Buffer
|
var help bytes.Buffer
|
||||||
p.WriteHelp(&help)
|
p.WriteHelp(&help)
|
||||||
assert.Equal(t, expectedHelp[1:], help.String())
|
assert.Equal(t, expectedHelp[1:], help.String())
|
||||||
|
@ -744,7 +435,7 @@ func TestNonexistentSubcommand(t *testing.T) {
|
||||||
var args struct {
|
var args struct {
|
||||||
sub *struct{} `arg:"subcommand"`
|
sub *struct{} `arg:"subcommand"`
|
||||||
}
|
}
|
||||||
p, err := NewParser(Config{Exit: func(int) {}}, &args)
|
p, err := NewParser(Config{}, &args)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
|
@ -784,36 +475,7 @@ Options:
|
||||||
ShortOnly2 string `arg:"-b,--,required" help:"some help2"`
|
ShortOnly2 string `arg:"-b,--,required" help:"some help2"`
|
||||||
}
|
}
|
||||||
p, err := NewParser(Config{Program: "example"}, &args)
|
p, err := NewParser(Config{Program: "example"}, &args)
|
||||||
require.NoError(t, err)
|
assert.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 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
|
var help bytes.Buffer
|
||||||
p.WriteHelp(&help)
|
p.WriteHelp(&help)
|
||||||
|
@ -860,16 +522,10 @@ Usage: example [-s SHORT]
|
||||||
Options:
|
Options:
|
||||||
-s SHORT [env: SHORT]
|
-s SHORT [env: SHORT]
|
||||||
--help, -h display this help and exit
|
--help, -h display this help and exit
|
||||||
|
|
||||||
Environment variables:
|
|
||||||
ENVONLY Optional.
|
|
||||||
ENVONLY2 Optional.
|
|
||||||
CUSTOM Optional.
|
|
||||||
`
|
`
|
||||||
var args struct {
|
var args struct {
|
||||||
Short string `arg:"--,-s,env"`
|
Short string `arg:"--,-s,env"`
|
||||||
EnvOnly string `arg:"--,env"`
|
EnvOnly string `arg:"--,env"`
|
||||||
EnvOnly2 string `arg:"--,-,env"`
|
|
||||||
EnvOnlyOverriden string `arg:"--,env:CUSTOM"`
|
EnvOnlyOverriden string `arg:"--,env:CUSTOM"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -885,39 +541,19 @@ Environment variables:
|
||||||
assert.Equal(t, expectedUsage, strings.TrimSpace(usage.String()))
|
assert.Equal(t, expectedUsage, strings.TrimSpace(usage.String()))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEnvOnlyArgs(t *testing.T) {
|
|
||||||
expectedUsage := "Usage: example [--arg ARG]"
|
|
||||||
|
|
||||||
expectedHelp := `
|
|
||||||
Usage: example [--arg ARG]
|
|
||||||
|
|
||||||
Options:
|
|
||||||
--arg ARG, -a ARG [env: MY_ARG]
|
|
||||||
--help, -h display this help and exit
|
|
||||||
|
|
||||||
Environment variables:
|
|
||||||
AUTH_KEY Required.
|
|
||||||
`
|
|
||||||
var args struct {
|
|
||||||
ArgParam string `arg:"-a,--arg,env:MY_ARG"`
|
|
||||||
AuthKey string `arg:"required,--,env:AUTH_KEY"`
|
|
||||||
}
|
|
||||||
p, err := NewParser(Config{Program: "example"}, &args)
|
|
||||||
assert.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 TestFail(t *testing.T) {
|
func TestFail(t *testing.T) {
|
||||||
var stdout bytes.Buffer
|
originalStderr := stderr
|
||||||
|
originalExit := osExit
|
||||||
|
defer func() {
|
||||||
|
stderr = originalStderr
|
||||||
|
osExit = originalExit
|
||||||
|
}()
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
stderr = &b
|
||||||
|
|
||||||
var exitCode int
|
var exitCode int
|
||||||
exit := func(code int) { exitCode = code }
|
osExit = func(code int) { exitCode = code }
|
||||||
|
|
||||||
expectedStdout := `
|
expectedStdout := `
|
||||||
Usage: example [--foo FOO]
|
Usage: example [--foo FOO]
|
||||||
|
@ -927,18 +563,27 @@ error: something went wrong
|
||||||
var args struct {
|
var args struct {
|
||||||
Foo int
|
Foo int
|
||||||
}
|
}
|
||||||
p, err := NewParser(Config{Program: "example", Exit: exit, Out: &stdout}, &args)
|
p, err := NewParser(Config{Program: "example"}, &args)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
p.Fail("something went wrong")
|
p.Fail("something went wrong")
|
||||||
|
|
||||||
assert.Equal(t, expectedStdout[1:], stdout.String())
|
assert.Equal(t, expectedStdout[1:], b.String())
|
||||||
assert.Equal(t, 2, exitCode)
|
assert.Equal(t, -1, exitCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFailSubcommand(t *testing.T) {
|
func TestFailSubcommand(t *testing.T) {
|
||||||
var stdout bytes.Buffer
|
originalStderr := stderr
|
||||||
|
originalExit := osExit
|
||||||
|
defer func() {
|
||||||
|
stderr = originalStderr
|
||||||
|
osExit = originalExit
|
||||||
|
}()
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
stderr = &b
|
||||||
|
|
||||||
var exitCode int
|
var exitCode int
|
||||||
exit := func(code int) { exitCode = code }
|
osExit = func(code int) { exitCode = code }
|
||||||
|
|
||||||
expectedStdout := `
|
expectedStdout := `
|
||||||
Usage: example sub
|
Usage: example sub
|
||||||
|
@ -948,139 +593,12 @@ error: something went wrong
|
||||||
var args struct {
|
var args struct {
|
||||||
Sub *struct{} `arg:"subcommand"`
|
Sub *struct{} `arg:"subcommand"`
|
||||||
}
|
}
|
||||||
p, err := NewParser(Config{Program: "example", Exit: exit, Out: &stdout}, &args)
|
p, err := NewParser(Config{Program: "example"}, &args)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
err = p.FailSubcommand("something went wrong", "sub")
|
err = p.FailSubcommand("something went wrong", "sub")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, expectedStdout[1:], stdout.String())
|
assert.Equal(t, expectedStdout[1:], b.String())
|
||||||
assert.Equal(t, 2, exitCode)
|
assert.Equal(t, -1, exitCode)
|
||||||
}
|
|
||||||
|
|
||||||
type lengthOf struct {
|
|
||||||
Length int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *lengthOf) UnmarshalText(b []byte) error {
|
|
||||||
p.Length = len(b)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHelpShowsDefaultValueFromOriginalTag(t *testing.T) {
|
|
||||||
// check that the usage text prints the original string from the default tag, not
|
|
||||||
// the serialization of the parsed value
|
|
||||||
|
|
||||||
expectedHelp := `
|
|
||||||
Usage: example [--test TEST]
|
|
||||||
|
|
||||||
Options:
|
|
||||||
--test TEST [default: some_default_value]
|
|
||||||
--help, -h display this help and exit
|
|
||||||
`
|
|
||||||
|
|
||||||
var args struct {
|
|
||||||
Test *lengthOf `default:"some_default_value"`
|
|
||||||
}
|
|
||||||
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 TestHelpShowsSubcommandAliases(t *testing.T) {
|
|
||||||
expectedHelp := `
|
|
||||||
Usage: example <command> [<args>]
|
|
||||||
|
|
||||||
Options:
|
|
||||||
--help, -h display this help and exit
|
|
||||||
|
|
||||||
Commands:
|
|
||||||
remove, rm, r remove something from somewhere
|
|
||||||
simple do something simple
|
|
||||||
halt, stop stop now
|
|
||||||
`
|
|
||||||
|
|
||||||
var args struct {
|
|
||||||
Remove *struct{} `arg:"subcommand:remove|rm|r" help:"remove something from somewhere"`
|
|
||||||
Simple *struct{} `arg:"subcommand" help:"do something simple"`
|
|
||||||
Stop *struct{} `arg:"subcommand:halt|stop" help:"stop now"`
|
|
||||||
}
|
|
||||||
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 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())
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue