diff --git a/README.md b/README.md index f105b17..6661fe3 100644 --- a/README.md +++ b/README.md @@ -284,6 +284,9 @@ $ ./example --version someprogram 4.3.0 ``` +> **Note** +> If a `--version` flag is defined in `args` or any subcommand, it overrides the above functionality. + ### Overriding option names ```go diff --git a/usage.go b/usage.go index a9f9844..aa73a96 100644 --- a/usage.go +++ b/usage.go @@ -62,29 +62,39 @@ func (p *Parser) WriteUsageForSubcommand(w io.Writer, subcommand ...string) erro // writeUsageForSubcommand writes usage information for the given subcommand func (p *Parser) writeUsageForSubcommand(w io.Writer, cmd *command) { 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) } } - if p.version != "" { - fmt.Fprintln(w, p.version) - } - // 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.Fprint(w, "Usage:") for i := len(ancestors) - 1; i >= 0; i-- { @@ -216,6 +226,9 @@ func (p *Parser) writeHelpForSubcommand(w io.Writer, cmd *command) { positionals = append(positionals, spec) case spec.long != "": longOptions = append(longOptions, spec) + if spec.long == "version" { + hasVersionOption = true + } case spec.short != "": shortOptions = append(shortOptions, spec) case spec.short == "" && spec.long == "": @@ -223,6 +236,21 @@ func (p *Parser) writeHelpForSubcommand(w io.Writer, cmd *command) { } } + // 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 != "" { fmt.Fprintln(w, p.description) } @@ -244,28 +272,14 @@ func (p *Parser) writeHelpForSubcommand(w io.Writer, cmd *command) { } for _, spec := range longOptions { p.printOption(w, spec) - if spec.long == "version" { - hasVersionOption = true - } } } - // obtain a flattened list of options from all ancestors - var globals []*spec - ancestor := cmd.parent - for ancestor != nil { - globals = append(globals, ancestor.specs...) - ancestor = ancestor.parent - } - // write the list of global options if len(globals) > 0 { fmt.Fprint(w, "\nGlobal options:\n") for _, spec := range globals { p.printOption(w, spec) - if spec.long == "version" { - hasVersionOption = true - } } } diff --git a/usage_test.go b/usage_test.go index 1a64ad4..d67b1ef 100644 --- a/usage_test.go +++ b/usage_test.go @@ -236,7 +236,7 @@ func (versioned) Version() string { return "example 3.2.1" } -func TestUsageWithVersion(t *testing.T) { +func TestUsageWithBuiltinVersion(t *testing.T) { expectedUsage := "example 3.2.1\nUsage: example" expectedHelp := ` @@ -260,6 +260,230 @@ Options: assert.Equal(t, expectedUsage, strings.TrimSpace(usage.String())) } +func TestUsageWithArgsVersion(t *testing.T) { + var args struct { + Version bool `arg:"-V,--version" help:"display version and build info"` + } + + expectedUsage := "Usage: example [--version]" + + expectedHelp := ` +Usage: example [--version] + +Options: + --version, -V display version and build info + --help, -h display this help and exit +` + 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 TestUsageWithBuiltinAndArgsVersion(t *testing.T) { + var args struct { + versioned + VersionFlag bool `arg:"-V,--version" help:"display version and build info"` + } + + expectedUsage := "Usage: example [--version]" + + expectedHelp := ` +Usage: example [--version] + +Options: + --version, -V display version and build info + --help, -h display this help and exit +` + 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 TestUsageWithBuiltinVersionAndSubcommands(t *testing.T) { + type cmd struct { + Test int `arg:"-t,--test" help:"test number"` + } + var args struct { + versioned + Cmd *cmd `arg:"subcommand"` + } + + expectedUsage := "example 3.2.1\nUsage: example []" + + expectedHelp := ` +example 3.2.1 +Usage: example [] + +Options: + --help, -h display this help and exit + --version display version and exit + +Commands: + cmd +` + 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 = "example 3.2.1\nUsage: example cmd [--test TEST]" + + expectedHelp = ` +example 3.2.1 +Usage: example cmd [--test TEST] + +Options: + --test TEST, -t TEST test 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 TestUsageWithArgsVersionAndSubcommands(t *testing.T) { + type cmd struct { + Test int `arg:"-t,--test" help:"test number"` + } + var args struct { + Cmd *cmd `arg:"subcommand"` + Version bool `arg:"-V,--version" help:"display version and build info"` + } + + expectedUsage := "Usage: example [--version] []" + + expectedHelp := ` +Usage: example [--version] [] + +Options: + --version, -V display version and build info + --help, -h display this help and exit + +Commands: + cmd +` + 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 [--test TEST]" + + expectedHelp = ` +Usage: example cmd [--test TEST] + +Options: + --test TEST, -t TEST test number + +Global options: + --version, -V display version and build info + --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 TestUsageWithBuiltinAndArgsVersionAndSubcommands(t *testing.T) { + type cmd struct { + Test int `arg:"-t,--test" help:"test number"` + } + var args struct { + versioned + Cmd *cmd `arg:"subcommand"` + Version bool `arg:"-V,--version" help:"display version and build info"` + } + + expectedUsage := "Usage: example [--version] []" + + expectedHelp := ` +Usage: example [--version] [] + +Options: + --version, -V display version and build info + --help, -h display this help and exit + +Commands: + cmd +` + 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 [--test TEST]" + + expectedHelp = ` +Usage: example cmd [--test TEST] + +Options: + --test TEST, -t TEST test number + +Global options: + --version, -V display version and build info + --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{} // Described returns the description for this program