Skip to content

Commit

Permalink
feat: Allow full subcommand flag display
Browse files Browse the repository at this point in the history
By default, only `required` flags are displayed for subcommands

For applications with a lot of simple subcommands this obscures basic usage flags

== Mitigation

- Add a new `HelpOption` called `IncludeOptionalFlagsInSummary`
- Move `Node.Summary()` functionality into `Node.SummaryWithOptions(bool)`
    - Retain `Node.Summary()` as a default usage (forwards default
      value to `Node.SummaryWithOptions(bool)`)
- Modify `help.go@printCommandSummary()` to forward the
   `HelpOption` to `Node.SummaryWithOptions()` instead of
   calling `Node.Summary()`
- Add tests for default and optional behaviour

== Caveats

The main issue with this implementation is due to the `Node` receiver
functions doing display logic internally.  This makes sense
for most of the use cases (application error logic), but
overly restricts the help configurability

As a result:

- Must pass awkward `bool` value down from the help context
- `FlagSummary()` ends up having to explicitly exclude the `help` flag.

== Potential improvements to be made

- Remove explicit exclusion of `help` from `FlagSummary()`
    - Potentially add an equivalent `Node` receiver which just returns an
      array of flag summaries to be displayed by `help@printCommandSummary`
  • Loading branch information
keilin-anz committed May 18, 2022
1 parent 195d56c commit e37690f
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 6 deletions.
7 changes: 6 additions & 1 deletion help.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ type HelpOptions struct {
// Don't show the help associated with subcommands
NoExpandSubcommands bool

// Whether to show optional flags in command summaries
// If true, command summaries will include flags not marked as required
// If false (the default), command summaries will only show required flags
IncludeOptionalFlagsInSummary bool

// Clamp the help wrap width to a value smaller than the terminal width.
// If this is set to a non-positive number, the terminal width is used; otherwise,
// the min of this value or the terminal width is used.
Expand Down Expand Up @@ -356,7 +361,7 @@ func collectCommandGroups(nodes []*Node) []helpCommandGroup {
}

func printCommandSummary(w *helpWriter, cmd *Command) {
w.Print(cmd.Summary())
w.Print(cmd.SummaryWithOptions(w.HelpOptions.IncludeOptionalFlagsInSummary))
if cmd.Help != "" {
w.Indent().Wrap(cmd.Help)
}
Expand Down
46 changes: 46 additions & 0 deletions help_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,52 @@ Commands:
})
}

func TestSubCommandFlags(t *testing.T) {
var cli struct {
Sub struct {
MoreSub struct {
Flag string `help:"A flag."`
Required string `required:"" help:"A required flag."`
} `cmd help:"more sub help"`
Another struct {
} `cmd help:"another help"`
} `cmd help:"sub help"`
}
w := bytes.NewBuffer(nil)

app := mustNew(t, &cli,
kong.Name("test-app"),
kong.Description("A test app."),
kong.Writers(w, w),
kong.Exit(func(int) {}),
)

// Default: don't show flags on nested items unless they are `required`
t.Run("default", func(t *testing.T) {
expected := "Usage: test-app <command>\n\nA test app.\n\nFlags:\n -h, --help Show context-sensitive help.\n\nCommands:\n sub more-sub --required=STRING\n more sub help\n\n sub another\n another help\n\nRun \"test-app <command> --help\" for more information on a command.\n"
_, _ = app.Parse([]string{"--help"})
require.Equal(t, expected, w.String())
})

w.Truncate(0)
app = mustNew(t, &cli,
kong.Name("test-app"),
kong.Description("A test app."),
kong.Writers(w, w),
kong.HelpOptions{
IncludeOptionalFlagsInSummary: true,
},
kong.Exit(func(int) {}),
)

// Force flag display: don't show flags on nested items unless they are `required`
t.Run("forced", func(t *testing.T) {
expected := "Usage: test-app <command>\n\nA test app.\n\nFlags:\n -h, --help Show context-sensitive help.\n\nCommands:\n sub more-sub --flag=STRING --required=STRING\n more sub help\n\n sub another\n another help\n\nRun \"test-app <command> --help\" for more information on a command.\n"
_, _ = app.Parse([]string{"--help"})
require.Equal(t, expected, w.String())
})
}

func TestHelpCompactNoExpand(t *testing.T) {
var cli struct {
One struct {
Expand Down
26 changes: 21 additions & 5 deletions model.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,19 @@ func (n *Node) Depth() int {
return depth
}

// Summary help string for the node (not including application name).
func (n *Node) Summary() string {
// Summary help string for the node (not including application name)
//
// Used both by help writing as well as other parts of the application
//
// Most cases just use Summary() (default settings)
//
// The help printer uses this optional version to pass options along.
//
// if includeOptionalFlags is true, all flags for a command will
// be included. see HelpOptions.IncludeOptionalFlagsInSummary
func (n *Node) SummaryWithOptions(includeOptionalFlags bool) string {
summary := n.Path()
if flags := n.FlagSummary(true); flags != "" {
if flags := n.FlagSummary(true, includeOptionalFlags); flags != "" {
summary += " " + flags
}
args := []string{}
Expand All @@ -163,14 +172,21 @@ func (n *Node) Summary() string {
return summary
}

// Summary help string for the node (not including application name).
// Default behaviour used throughout the application
func (n *Node) Summary() string {
return n.SummaryWithOptions(false)
}

// FlagSummary for the node.
func (n *Node) FlagSummary(hide bool) string {
func (n *Node) FlagSummary(hide bool, showOptional bool) string {
required := []string{}
count := 0
for _, group := range n.AllFlags(hide) {
for _, flag := range group {
count++
if flag.Required {
// Show required flags, or optional flags which aren't help
if flag.Required || (showOptional && flag.Name != "help") {
required = append(required, flag.Summary())
}
}
Expand Down

0 comments on commit e37690f

Please sign in to comment.