Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix usage writing when using custom version flag #224

Merged
merged 4 commits into from
Jun 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 built-in versioning.

### Overriding option names

```go
Expand Down
52 changes: 37 additions & 15 deletions usage.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,18 +48,36 @@ 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)
}
}

if 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)
}

Expand Down Expand Up @@ -208,13 +226,31 @@ func (p *Parser) WriteHelpForSubcommand(w io.Writer, subcommand ...string) error
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 == "":
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 != "" {
fmt.Fprintln(w, p.description)
}
Expand All @@ -236,28 +272,14 @@ func (p *Parser) WriteHelpForSubcommand(w io.Writer, subcommand ...string) error
}
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
}
}
}

Expand Down
212 changes: 203 additions & 9 deletions usage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,28 +260,199 @@ Options:
assert.Equal(t, expectedUsage, strings.TrimSpace(usage.String()))
}

type userDefinedVersionFlag struct {
ShowVersion bool `arg:"--version" help:"this is a user-defined version flag"`
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()))
}

// Version returns the version for this program
func (userDefinedVersionFlag) Version() string {
return "example 3.2.1"
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()))
}

func TestUsageWithUserDefinedVersionFlag(t *testing.T) {
expectedUsage := "example 3.2.1\nUsage: example [--version]"
type subcommand struct {
Number int `arg:"-n,--number" help:"compute something on the given number"`
}

func TestUsageWithVersionAndSubcommand(t *testing.T) {
expectedUsage := "example 3.2.1\nUsage: example <command> [<args>]"

expectedHelp := `
example 3.2.1
Usage: example [--version]
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 = "example 3.2.1\nUsage: 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{}, &userDefinedVersionFlag{})
p, err := NewParser(Config{}, &args)
require.NoError(t, err)

var help bytes.Buffer
Expand All @@ -291,6 +462,29 @@ Options:
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{}
Expand Down
Loading