Skip to content

Commit

Permalink
Help Option to position flags in help output
Browse files Browse the repository at this point in the history
When FlagsLast is set to true, flags are printed after commands.

- Added HelpOptions.FlagsLast bool
- Added test TestFlagsLast
  • Loading branch information
jaco-codexorbis authored and alecthomas committed Feb 9, 2021
1 parent abbc2df commit 405b2f4
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 10 deletions.
31 changes: 21 additions & 10 deletions help.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -184,6 +192,9 @@ func printNodeDetail(w *helpWriter, node *Node, hide bool) {
}
}
}
if w.FlagsLast {
printFlags()
}
}

func writeCommandList(cmds []*Node, iw *helpWriter) {
Expand Down
109 changes: 109 additions & 0 deletions help_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 <command>
A test app.
Commands:
one --required
A subcommand.
two <three> --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 <command> --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 <three> --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 {
Expand Down

0 comments on commit 405b2f4

Please sign in to comment.