From 405b2f4fd9a44c95f00baee2a6e766a32b0e1fa6 Mon Sep 17 00:00:00 2001 From: Jaco Coetzee Date: Tue, 9 Feb 2021 10:21:40 +0200 Subject: [PATCH] Help Option to position flags in help output When FlagsLast is set to true, flags are printed after commands. - Added HelpOptions.FlagsLast bool - Added test TestFlagsLast --- help.go | 31 ++++++++++----- help_test.go | 109 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 130 insertions(+), 10 deletions(-) diff --git a/help.go b/help.go index e107724..2594946 100644 --- a/help.go +++ b/help.go @@ -41,6 +41,9 @@ type HelpOptions struct { // Tree writes command chains in a tree structure instead of listing them separately. Tree bool + // Place the flags after the commands listing. + FlagsLast bool + // Indenter modulates the given prefix for the next layer in the tree view. // The following exported templates can be used: kong.SpaceIndenter, kong.LineIndenter, kong.TreeIndenter // The kong.SpaceIndenter will be used by default. @@ -143,20 +146,25 @@ func printNodeDetail(w *helpWriter, node *Node, hide bool) { w.Print("Arguments:") writePositionals(w.Indent(), node.Positional) } - if flags := node.AllFlags(true); len(flags) > 0 { - groupedFlags := collectFlagGroups(flags) - for _, group := range groupedFlags { - w.Print("") - if group.Metadata.Title != "" { - w.Wrap(group.Metadata.Title) - } - if group.Metadata.Description != "" { - w.Indent().Wrap(group.Metadata.Description) + printFlags := func() { + if flags := node.AllFlags(true); len(flags) > 0 { + groupedFlags := collectFlagGroups(flags) + for _, group := range groupedFlags { w.Print("") + if group.Metadata.Title != "" { + w.Wrap(group.Metadata.Title) + } + if group.Metadata.Description != "" { + w.Indent().Wrap(group.Metadata.Description) + w.Print("") + } + writeFlags(w.Indent(), group.Flags) } - writeFlags(w.Indent(), group.Flags) } } + if !w.FlagsLast { + printFlags() + } cmds := node.Leaves(hide) if len(cmds) > 0 { iw := w.Indent() @@ -184,6 +192,9 @@ func printNodeDetail(w *helpWriter, node *Node, hide bool) { } } } + if w.FlagsLast { + printFlags() + } } func writeCommandList(cmds []*Node, iw *helpWriter) { diff --git a/help_test.go b/help_test.go index ca0ab10..c7d5fe0 100644 --- a/help_test.go +++ b/help_test.go @@ -126,6 +126,115 @@ Flags: }) } +func TestFlagsLast(t *testing.T) { + // nolint: govet + var cli struct { + String string `help:"A string flag."` + Bool bool `help:"A bool flag with very long help that wraps a lot and is verbose and is really verbose."` + Slice []string `help:"A slice of strings." placeholder:"STR"` + Map map[string]int `help:"A map of strings to ints."` + Required bool `required help:"A required flag."` + + One struct { + Flag string `help:"Nested flag."` + } `cmd help:"A subcommand."` + + Two struct { + Flag string `help:"Nested flag under two."` + RequiredTwo bool `required` + + Three threeArg `arg help:"Sub-sub-arg."` + + Four struct { + } `cmd help:"Sub-sub-command."` + } `cmd help:"Another subcommand."` + } + + w := bytes.NewBuffer(nil) + exited := false + app := mustNew(t, &cli, + kong.Name("test-app"), + kong.Description("A test app."), + kong.HelpOptions{ + FlagsLast: true, + }, + kong.Writers(w, w), + kong.Exit(func(int) { + exited = true + panic(true) // Panic to fake "exit". + }), + ) + + t.Run("Full", func(t *testing.T) { + require.PanicsWithValue(t, true, func() { + _, err := app.Parse([]string{"--help"}) + require.NoError(t, err) + }) + require.True(t, exited) + expected := `Usage: test-app --required + +A test app. + +Commands: + one --required + A subcommand. + + two --required --required-two --required-three + Sub-sub-arg. + + two four --required --required-two + Sub-sub-command. + +Flags: + -h, --help Show context-sensitive help. + --string=STRING A string flag. + --bool A bool flag with very long help that wraps a lot + and is verbose and is really verbose. + --slice=STR,... A slice of strings. + --map=KEY=VALUE;... A map of strings to ints. + --required A required flag. + +Run "test-app --help" for more information on a command. +` + t.Log(w.String()) + t.Log(expected) + require.Equal(t, expected, w.String()) + }) + + t.Run("Selected", func(t *testing.T) { + exited = false + w.Truncate(0) + require.PanicsWithValue(t, true, func() { + _, err := app.Parse([]string{"two", "hello", "--help"}) + require.NoError(t, err) + }) + require.True(t, exited) + expected := `Usage: test-app two --required --required-two --required-three + +Sub-sub-arg. + +Detailed help provided through the HelpProvider interface. + +Flags: + -h, --help Show context-sensitive help. + --string=STRING A string flag. + --bool A bool flag with very long help that wraps a lot + and is verbose and is really verbose. + --slice=STR,... A slice of strings. + --map=KEY=VALUE;... A map of strings to ints. + --required A required flag. + + --flag=STRING Nested flag under two. + --required-two + + --required-three +` + t.Log(expected) + t.Log(w.String()) + require.Equal(t, expected, w.String()) + }) +} + func TestHelpTree(t *testing.T) { // nolint: govet var cli struct {