This commit is contained in:
Hugo Hromic 2023-09-11 21:54:10 -06:00 committed by GitHub
commit bdad143a88
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 260 additions and 19 deletions

View File

@ -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

View File

@ -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
}
}
}

View File

@ -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 <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
`
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] <command> [<args>]"
expectedHelp := `
Usage: example [--version] <command> [<args>]
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] <command> [<args>]"
expectedHelp := `
Usage: example [--version] <command> [<args>]
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