From 4ed4ce751fa49ae1b04f672c1092120fd71d5b21 Mon Sep 17 00:00:00 2001 From: Hugo Hromic Date: Sat, 15 Jul 2023 13:25:46 +0100 Subject: [PATCH 1/4] Better scanning of version flag in specs for help generation --- usage.go | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/usage.go b/usage.go index 6b578a5..ae98478 100644 --- a/usage.go +++ b/usage.go @@ -208,6 +208,9 @@ 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 == "": @@ -215,6 +218,21 @@ func (p *Parser) WriteHelpForSubcommand(w io.Writer, subcommand ...string) error } } + // 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) } @@ -236,28 +254,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 - } } } From bed89eb683e6016be7247041db3c998e57fc838c Mon Sep 17 00:00:00 2001 From: Hugo Hromic Date: Thu, 18 Jan 2024 23:04:55 +0000 Subject: [PATCH 2/4] Implement scanning of version flag in specs for usage generation --- usage.go | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/usage.go b/usage.go index ae98478..4a056f3 100644 --- a/usage.go +++ b/usage.go @@ -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) } From c992aa8627a00b83eb94895173760b6be684d807 Mon Sep 17 00:00:00 2001 From: Hugo Hromic Date: Sat, 15 Jul 2023 13:52:15 +0100 Subject: [PATCH 3/4] Add more test cases for version help/usage writing --- usage_test.go | 212 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 203 insertions(+), 9 deletions(-) diff --git a/usage_test.go b/usage_test.go index b2bcab1..b13ecbc 100644 --- a/usage_test.go +++ b/usage_test.go @@ -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 []" expectedHelp := ` example 3.2.1 -Usage: example [--version] +Usage: example [] + +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] []" + + expectedHelp := ` +Usage: example [--version] [] + +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] []" + + expectedHelp := ` +Usage: example [--version] [] 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 @@ -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{} From c087d7180231ea3cfc12a79ee091786ac9954e6a Mon Sep 17 00:00:00 2001 From: Hugo Hromic Date: Sat, 15 Jul 2023 15:12:58 +0100 Subject: [PATCH 4/4] Add note for version flag overriding to README --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index f105b17..c644227 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 built-in versioning. + ### Overriding option names ```go