Skip to content

Commit

Permalink
BREAKING CHANGE(cliutils): ensure lazy help rendering (#15)
Browse files Browse the repository at this point in the history
For ordinary commands, help rendering seems already lazy because we
explicitly need to invoke the Help method.

Conversely, for NewCommandWithSubCommands the help is passed as a
string, which means markdown rendering happens unconditionally.

This has negative implications including generation of garbled output
including terminal escape sequences when we put a process in background
using the shell.

To fix, make help rendering lazy also for NewCommandWithSubCommands.
  • Loading branch information
bassosimone authored Dec 8, 2024
1 parent 1dffef2 commit ddd54c6
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 8 deletions.
2 changes: 1 addition & 1 deletion cliutils/cliutils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ func TestCommandWithSubCommands(t *testing.T) {
for _, tc := range cases {
t.Run(strings.Join(tc.argv, " "), func(t *testing.T) {
cmd := cliutils.NewCommandWithSubCommands(
"rbmk", "", map[string]cliutils.Command{
"rbmk", cliutils.LazyHelpRendererFunc(func() string { return "" }), map[string]cliutils.Command{
"env": fakecmd{},
},
)
Expand Down
31 changes: 24 additions & 7 deletions cliutils/clutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,25 +71,42 @@ type CommandWithSubCommands struct {
// commands is the map of subcommands.
commands map[string]Command

// help is the help string.
help string

// name is the full name of this command.
name string

// renderer is the help renderer.
renderer LazyHelpRenderer
}

// LazyHelpRenderer renders the help possibly adding colours and formatting.
type LazyHelpRenderer interface {
Help() string
}

// LazyHelpRendererFunc is a function that implements [LazyHelpRenderer].
type LazyHelpRendererFunc func() string

// Ensure that [LazyHelpRendererFunc] implements [LazyHelpRenderer].
var _ LazyHelpRenderer = LazyHelpRendererFunc(nil)

// Help implements HelpRenderer.
func (fx LazyHelpRendererFunc) Help() string {
return fx()
}

// NewCommandWithSubCommands constructs a [CommandWithSubCommands].
//
// The name argument contains the full name of this command (e.g., `rbmk run`).
//
// The help argument contains the help string.
// The renderer argument renders the help on demand.
//
// The commands argument contains the implemented subcommands.
func NewCommandWithSubCommands(name string, help string, commands map[string]Command) CommandWithSubCommands {
func NewCommandWithSubCommands(name string,
renderer LazyHelpRenderer, commands map[string]Command) CommandWithSubCommands {
return CommandWithSubCommands{
commands: commands,
help: help,
name: name,
renderer: renderer,
}
}

Expand All @@ -99,7 +116,7 @@ var _ Command = CommandWithSubCommands{}
func (c CommandWithSubCommands) Help(env Environment, argv ...string) error {
// 1. case where we're invoked with no arguments
if len(argv) < 2 {
fmt.Fprintf(env.Stderr(), "%s\n", c.help)
fmt.Fprintf(env.Stderr(), "%s\n", c.renderer.Help())
return nil
}

Expand Down

0 comments on commit ddd54c6

Please sign in to comment.