summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/FUNDING.yml4
-rw-r--r--README.md240
-rw-r--r--parse.go11
-rw-r--r--usage.go27
-rw-r--r--usage_test.go6
5 files changed, 216 insertions, 72 deletions
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..215c818
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,4 @@
+# These are supported funding model platforms
+
+github: [alexflint]
+custom: ['https://alexflint.io/donate.html']
diff --git a/README.md b/README.md
index 4d23034..761af56 100644
--- a/README.md
+++ b/README.md
@@ -105,7 +105,7 @@ $ NUM_WORKERS=4 ./example
Workers: 4
```
-You can provide multiple values using the CSV (RFC 4180) format:
+You can provide multiple values in environment variables using commas:
```go
var args struct {
@@ -120,7 +120,7 @@ $ WORKERS='1,99' ./example
Workers: [1 99]
```
-You can also have an environment variable that doesn't match the arg name:
+Command line arguments take precedence over environment variables:
```go
var args struct {
@@ -175,20 +175,7 @@ var args struct {
arg.MustParse(&args)
```
-### Default values (before v1.2)
-
-```go
-var args struct {
- Foo string
- Bar bool
-}
-arg.Foo = "abc"
-arg.MustParse(&args)
-```
-
-### Combining command line options, environment variables, and default values
-
-You can combine command line arguments, environment variables, and default values. Command line arguments take precedence over environment variables, which take precedence over default values. This means that we check whether a certain option was provided on the command line, then if not, we check for an environment variable (only if an `env` tag was provided), then if none is found, we check for a `default` tag containing a default value.
+Command line arguments take precedence over environment variables, which take precedence over default values. This means that we check whether a certain option was provided on the command line, then if not, we check for an environment variable (only if an `env` tag was provided), then if none is found, we check for a `default` tag containing a default value.
```go
var args struct {
@@ -198,10 +185,6 @@ arg.MustParse(&args)
```
#### Ignoring environment variables and/or default values
-
-The values in an existing structure can be kept in-tact by ignoring environment
-variables and/or default values.
-
```go
var args struct {
Test string `arg:"-t,env:TEST" default:"something"`
@@ -261,26 +244,7 @@ fmt.Println(args.UserIDs)
map[john:123 mary:456]
```
-### Custom validation
-```go
-var args struct {
- Foo string
- Bar string
-}
-p := arg.MustParse(&args)
-if args.Foo == "" && args.Bar == "" {
- p.Fail("you must provide either --foo or --bar")
-}
-```
-
-```shell
-./example
-Usage: samples [--foo FOO] [--bar BAR]
-error: you must provide either --foo or --bar
-```
-
### Version strings
-
```go
type args struct {
...
@@ -304,6 +268,24 @@ 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
```go
@@ -452,8 +434,6 @@ main.NameDotName{Head:"file", Tail:"txt"}
### Custom placeholders
-*Introduced in version 1.3.0*
-
Use the `placeholder` tag to control which placeholder text is used in the usage text.
```go
@@ -541,8 +521,6 @@ For more information visit github.com/alexflint/go-arg
### Subcommands
-*Introduced in version 1.1.0*
-
Subcommands are commonly used in tools that wish to group multiple functions into a single program. An example is the `git` tool:
```shell
$ git checkout [arguments specific to checking out code]
@@ -603,9 +581,181 @@ 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
-https://godoc.org/github.com/alexflint/go-arg
+https://pkg.go.dev/github.com/alexflint/go-arg
### Rationale
@@ -619,4 +769,4 @@ The idea behind `go-arg` is that Go already has an excellent way to describe dat
### Backward compatibility notes
-Earlier versions of this library required the help text to be part of the `arg` tag. This is still supported but is now deprecated. Instead, you should use a separate `help` tag, described above, which removes most of the limits on the text you can write. In particular, you will need to use the new `help` tag if your help text includes any commas.
+Earlier versions of this library required the help text to be part of the `arg` tag. This is still supported but is now deprecated. Instead, you should use a separate `help` tag, described above, which makes it possible to include commas inside help text.
diff --git a/parse.go b/parse.go
index 2bed8bf..172c7cd 100644
--- a/parse.go
+++ b/parse.go
@@ -493,8 +493,15 @@ func cmdFromStruct(name string, dest path, t reflect.Type) (*command, error) {
return &cmd, nil
}
-// Parse processes the given command line option, storing the results in the field
-// of the structs from which NewParser was constructed
+// Parse processes the given command line option, storing the results in the fields
+// 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 {
err := p.process(args)
if err != nil {
diff --git a/usage.go b/usage.go
index 8a7c139..d091bcb 100644
--- a/usage.go
+++ b/usage.go
@@ -48,39 +48,17 @@ func (p *Parser) WriteUsageForSubcommand(w io.Writer, subcommand ...string) erro
}
var positionals, longOptions, shortOptions []*spec
- var hasVersionOption bool
for _, spec := range cmd.specs {
switch {
case spec.positional:
positionals = append(positionals, spec)
case spec.long != "":
longOptions = append(longOptions, spec)
- if spec.long == "version" {
- hasVersionOption = true
- }
case spec.short != "":
shortOptions = append(shortOptions, spec)
}
}
- // make a list of ancestor commands so that we print with full context
- // also determine if any ancestor has a version option spec
- var ancestors []string
- ancestor := cmd
- for ancestor != nil {
- for _, spec := range ancestor.specs {
- if spec.long == "version" {
- hasVersionOption = true
- }
- }
- ancestors = append(ancestors, ancestor.name)
- ancestor = ancestor.parent
- }
-
- if !hasVersionOption && p.version != "" {
- fmt.Fprintln(w, p.version)
- }
-
// print the beginning of the usage string
fmt.Fprintf(w, "Usage: %s", p.cmd.name)
for _, s := range subcommand {
@@ -254,6 +232,11 @@ func (p *Parser) WriteHelpForSubcommand(w io.Writer, subcommand ...string) error
if p.description != "" {
fmt.Fprintln(w, p.description)
}
+
+ if !hasVersionOption && p.version != "" {
+ fmt.Fprintln(w, p.version)
+ }
+
p.WriteUsageForSubcommand(w, subcommand...)
// write the list of positionals
diff --git a/usage_test.go b/usage_test.go
index fa9ace3..e276e1a 100644
--- a/usage_test.go
+++ b/usage_test.go
@@ -237,7 +237,7 @@ func (versioned) Version() string {
}
func TestUsageWithVersion(t *testing.T) {
- expectedUsage := "example 3.2.1\nUsage: example"
+ expectedUsage := "Usage: example"
expectedHelp := `
example 3.2.1
@@ -322,7 +322,7 @@ type subcommand struct {
}
func TestUsageWithVersionAndSubcommand(t *testing.T) {
- expectedUsage := "example 3.2.1\nUsage: example <command> [<args>]"
+ expectedUsage := "Usage: example <command> [<args>]"
expectedHelp := `
example 3.2.1
@@ -353,7 +353,7 @@ Commands:
p.WriteUsage(&usage)
assert.Equal(t, expectedUsage, strings.TrimSpace(usage.String()))
- expectedUsage = "example 3.2.1\nUsage: example cmd [--number NUMBER]"
+ expectedUsage = "Usage: example cmd [--number NUMBER]"
expectedHelp = `
example 3.2.1