Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 100 additions & 2 deletions src/help-generator_.toit
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,17 @@ It finds the selected command and prints its help.
*/
help-command_ path/Path arguments/List --ui/Ui:
command/Command := path.last
show-all := false

for i := 0; i < arguments.size; i++:
argument := arguments[i]
if argument == "--": break

// Simply drop all options.
if argument == "--all":
show-all = true
continue

// Simply drop all other options.
if argument.starts-with "-":
continue

Expand All @@ -32,7 +37,24 @@ help-command_ path/Path arguments/List --ui/Ui:
command = subcommand
path += command

emit-help_ path --ui=ui
if show-all:
emit-help-all_ path --ui=ui
else:
emit-help_ path --ui=ui

/**
Emits the help for all commands in the subtree rooted at the given command.

The command is identified by the $path where the command is the last element.
*/
emit-help-all_ path/Path --ui/Ui:
ui.emit --kind=Ui.RESULT
--structured=:
build-json-help-all_ path
--text=:
generator := HelpGenerator path
generator.build-all-commands
generator.to-string

/**
Emits the help for the given command.
Expand All @@ -48,6 +70,35 @@ emit-help_ path/Path --ui/Ui:
generator.build-all
generator.to-string

build-json-help-all_ path/Path -> Map:
return build-json-command-tree_ path.last --is-root=(path.size == 1)

build-json-command-tree_ command/Command --is-root/bool=false -> Map:
sub-list := []
sorted := command.subcommands_.sort: | a/Command b/Command | a.name.compare-to b.name
sorted.do: | sub/Command |
if sub.is-hidden_: continue.do
sub-list.add (build-json-command-tree_ sub)

if is-root:
has-help := false
has-completion := false
command.subcommands_.do: | sub/Command |
if sub.name == "help": has-help = true
sub.aliases_.do: if it == "help": has-help = true
if sub.name == "completion": has-completion = true
sub.aliases_.do: if it == "completion": has-completion = true
if not has-help:
sub-list.add {"name": "help", "help": "Show help for a command.", "subcommands": []}
if not has-completion:
sub-list.add {"name": "completion", "help": "Generate shell completion scripts.", "subcommands": []}

return {
"name": command.name,
"help": command.short-help,
"subcommands": sub-list,
}

build-json-help_ path/Path -> Map:
// Local block to build json objects for the given command.
// Adds the converted json object to the out-map.
Expand Down Expand Up @@ -254,6 +305,53 @@ class HelpGenerator:
sorted-commands := commands-and-help.sort: | a/List b/List | a[0].compare-to b[0]
write-table_ sorted-commands --indentation=2

/**
Builds a hierarchical summary of all commands in the subtree.

Recursively lists all subcommands with indentation showing nesting.
Hidden commands are excluded. At root level, auto-added 'help' and
'completion' entries are included.
*/
build-all-commands -> none:
rows := []
collect-commands-recursive_ command_ rows --indent=0 --is-root=is-root-command_
if rows.is-empty: return
write-table_ rows

/**
Collects all commands recursively into $rows as [indented-name, short-help] pairs.
*/
collect-commands-recursive_ command/Command rows/List --indent/int --is-root/bool=false -> none:
// Each entry is [name, help, subcommand-or-null].
entries := []
command.subcommands_.do: | subcommand/Command |
if subcommand.is-hidden_: continue.do
entries.add [subcommand.name, subcommand.short-help, subcommand]

if is-root:
has-help := false
has-completion := false
command.subcommands_.do: | sub/Command |
if sub.name == "help": has-help = true
sub.aliases_.do: if it == "help": has-help = true
if sub.name == "completion": has-completion = true
sub.aliases_.do: if it == "completion": has-completion = true
if not has-help:
entries.add ["help", "Show help for a command.", null]
if not has-completion:
entries.add ["completion", "Generate shell completion scripts.", null]

sorted := entries.sort: | a/List b/List |
(a[0] as string).compare-to (b[0] as string)

prefix := " " * indent
sorted.do: | entry/List |
name := entry[0] as string
help-str := entry[1] as string
rows.add ["$prefix$name", help-str]
if entry[2]:
collect-commands-recursive_ (entry[2] as Command) rows --indent=(indent + 2)

/**
Builds the local options section.

Expand Down
64 changes: 64 additions & 0 deletions tests/help_test.toit
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ main:
test-options
test-examples
test-short-help
test-help-all

check-output expected/string [block]:
ui := TestUi
Expand Down Expand Up @@ -870,3 +871,66 @@ test-short-help:
expected = "Test command."
actual = cmd.short-help
expect-equals expected actual

test-help-all:
sub-sub := cli.Command "sub2"
--help="Even more nested info."
--run=:: null

sub1 := cli.Command "sub1"
--help="Sub info."
--subcommands=[sub-sub]

tool-foo := cli.Command "foo"
--help="The foo tool."
--run=:: null

tool-bar := cli.Command "bar"
--help="The bar tool."
--run=:: null

tool := cli.Command "tool"
--help="Tools for xyz."
--subcommands=[tool-foo, tool-bar]

hidden-cmd := cli.Command "secret"
--help="Secret command."
--hidden
--run=:: null

execute := cli.Command "execute"
--help="Runs toto."
--run=:: null

root := cli.Command "root"
--help="Root command."
--subcommands=[tool, execute, sub1, hidden-cmd]

expected := """
completion Generate shell completion scripts.
execute Runs toto.
help Show help for a command.
sub1 Sub info.
sub2 Even more nested info.
tool Tools for xyz.
bar The bar tool.
foo The foo tool.
"""
check-output expected: | cli/cli.Cli |
root.run ["help", "--all"] --cli=cli --invoked-command="bin/app"

// Test --all on a subcommand.
expected = """
bar The bar tool.
foo The foo tool.
"""
check-output expected: | cli/cli.Cli |
root.run ["help", "--all", "tool"] --cli=cli --invoked-command="bin/app"

// Test --all on a leaf command (no subcommands).
expected = ""
ui := TestUi
cli-obj := cli.Cli "test" --ui=ui
root.run ["help", "--all", "execute"] --cli=cli-obj --invoked-command="bin/app"
all-output := ui.stdout + ui.stderr
expect-equals (expected + "\n") all-output