From 338e831a8474c442b2c5e75eefed7b602276faa8 Mon Sep 17 00:00:00 2001 From: Dylan Allbee Date: Thu, 23 Jan 2020 21:08:18 -0800 Subject: [PATCH 1/3] Print global options in help for subcommands fixes #101 --- example_test.go | 3 ++- usage.go | 26 +++++++++++++++++++++++--- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/example_test.go b/example_test.go index be3cd12..4e6372f 100644 --- a/example_test.go +++ b/example_test.go @@ -235,7 +235,8 @@ func Example_helpTextForSubcommand() { // Positional arguments: // ITEM item to fetch // - // Options: + // Global options: + // --verbose // --help, -h display this help and exit } diff --git a/usage.go b/usage.go index 7ee68da..816c4be 100644 --- a/usage.go +++ b/usage.go @@ -144,9 +144,29 @@ func (p *Parser) writeHelpForCommand(w io.Writer, cmd *command) { } // write the list of options - fmt.Fprint(w, "\nOptions:\n") - for _, spec := range options { - p.printOption(w, spec) + if len(options) > 0 || cmd.parent == nil { + fmt.Fprint(w, "\nOptions:\n") + for _, spec := range options { + p.printOption(w, spec) + } + } + + // obtain a flattened list of options from all ancestors + var globals []*spec + ancestor := cmd.parent + for ancestor != nil { + for _, spec := range ancestor.specs { + globals = append(globals, spec) + } + 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) + } } // write the list of built in options From 5df19ebe00e88d443e062c6661f52b7069f486d7 Mon Sep 17 00:00:00 2001 From: Dylan Allbee Date: Thu, 23 Jan 2020 21:09:21 -0800 Subject: [PATCH 2/3] Use command passed into p.Parse(...) write methods It is currently impossible to programatically write help and usage messages for subcommands, due to parser.WriteHelp and parser.WriteUsage not taking the state of the parser into account. Check for the existence of p.lastCmd and use it for the writers when available. Enables ability to write unit tests for subcommand help. --- usage.go | 12 ++++++++++-- usage_test.go | 39 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/usage.go b/usage.go index 816c4be..c13835e 100644 --- a/usage.go +++ b/usage.go @@ -27,7 +27,11 @@ func (p *Parser) failWithCommand(msg string, cmd *command) { // WriteUsage writes usage information to the given writer func (p *Parser) WriteUsage(w io.Writer) { - p.writeUsageForCommand(w, p.cmd) + cmd := p.cmd + if p.lastCmd != nil { + cmd = p.lastCmd + } + p.writeUsageForCommand(w, cmd) } // writeUsageForCommand writes usage information for the given subcommand @@ -116,7 +120,11 @@ func printTwoCols(w io.Writer, left, help string, defaultVal string) { // WriteHelp writes the usage string followed by the full help string for each option func (p *Parser) WriteHelp(w io.Writer) { - p.writeHelpForCommand(w, p.cmd) + cmd := p.cmd + if p.lastCmd != nil { + cmd = p.lastCmd + } + p.writeHelpForCommand(w, cmd) } // writeHelp writes the usage string for the given subcommand diff --git a/usage_test.go b/usage_test.go index d9d33f0..24846ae 100644 --- a/usage_test.go +++ b/usage_test.go @@ -266,8 +266,45 @@ Options: p, err := NewParser(Config{}, &args) require.NoError(t, err) - os.Args[0] = "example" var help bytes.Buffer p.WriteHelp(&help) assert.Equal(t, expectedHelp, help.String()) } + +func TestUsagWithNestedSubcommands(t *testing.T) { + expectedHelp := `Usage: example child nested [--enable] OUTPUT + +Positional arguments: + OUTPUT + +Options: + --enable + +Global options: + --values VALUES Values + --verbose, -v verbosity level + --help, -h display this help and exit +` + + var args struct { + Verbose bool `arg:"-v" help:"verbosity level"` + Child *struct { + Values []float64 `help:"Values"` + Nested *struct { + Enable bool + Output string `arg:"positional,required"` + } `arg:"subcommand:nested"` + } `arg:"subcommand:child"` + } + + os.Args[0] = "example" + p, err := NewParser(Config{}, &args) + require.NoError(t, err) + + err = p.Parse([]string{"child", "nested", "value"}) + + var help bytes.Buffer + p.WriteHelp(&help) + fmt.Println(help.String()) + assert.Equal(t, expectedHelp, help.String()) +} From c24567c12e7036f9f18c62ea13bb85b84e3d80ff Mon Sep 17 00:00:00 2001 From: Dylan Allbee Date: Thu, 23 Jan 2020 21:36:24 -0800 Subject: [PATCH 3/3] Fix lint warnings --- usage.go | 4 +--- usage_test.go | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/usage.go b/usage.go index c13835e..db43dd1 100644 --- a/usage.go +++ b/usage.go @@ -163,9 +163,7 @@ func (p *Parser) writeHelpForCommand(w io.Writer, cmd *command) { var globals []*spec ancestor := cmd.parent for ancestor != nil { - for _, spec := range ancestor.specs { - globals = append(globals, spec) - } + globals = append(globals, ancestor.specs...) ancestor = ancestor.parent } diff --git a/usage_test.go b/usage_test.go index 24846ae..31b439b 100644 --- a/usage_test.go +++ b/usage_test.go @@ -301,7 +301,7 @@ Global options: p, err := NewParser(Config{}, &args) require.NoError(t, err) - err = p.Parse([]string{"child", "nested", "value"}) + _ = p.Parse([]string{"child", "nested", "value"}) var help bytes.Buffer p.WriteHelp(&help)