Skip to content

Commit ef702a6

Browse files
authored
cli: render command groups, inherited flags, examples and learn-more in --help (#27124)
1 parent 4b7955a commit ef702a6

5 files changed

Lines changed: 445 additions & 47 deletions

File tree

examples/cli_groups.v

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
// This example demonstrates the `vlib/cli` features that show up automatically
2+
// in `--help` when you populate the optional `group`, `examples` and
3+
// `learn_more` fields of `Command`. No opt-in is required — leaving any of
4+
// them empty simply skips the corresponding section.
5+
//
6+
// Compile from the V repo root:
7+
// v -o tasky examples/cli_groups.v
8+
//
9+
// Try it out:
10+
// ./tasky --help # root help with grouped commands and examples
11+
// ./tasky issue --help # sub-command help with INHERITED FLAGS
12+
// ./tasky issue list -v
13+
// ./tasky version
14+
module main
15+
16+
import cli
17+
import os
18+
19+
fn main() {
20+
mut app := cli.Command{
21+
name: 'tasky'
22+
description: 'A tiny issue tracker CLI'
23+
version: '0.1.0'
24+
posix_mode: true
25+
examples: [
26+
'\$ tasky issue list',
27+
'\$ tasky issue create --title "Fix CI"',
28+
'\$ tasky config get editor',
29+
]
30+
learn_more: 'Use `tasky <command> --help` for details about a command.\nDocumentation lives at https://example.test/tasky'
31+
}
32+
app.add_flag(cli.Flag{
33+
flag: .string
34+
name: 'config'
35+
abbrev: 'c'
36+
description: 'Path to a tasky config file'
37+
global: true
38+
})
39+
app.add_command(issue_command())
40+
app.add_command(config_command())
41+
app.setup()
42+
app.parse(os.args)
43+
}
44+
45+
fn issue_command() cli.Command {
46+
return cli.Command{
47+
name: 'issue'
48+
description: 'Work with issues'
49+
group: 'Core commands'
50+
commands: [
51+
cli.Command{
52+
name: 'list'
53+
description: 'List open issues'
54+
execute: issue_list
55+
flags: [
56+
cli.Flag{
57+
flag: .bool
58+
name: 'verbose'
59+
abbrev: 'v'
60+
description: 'Show issue bodies in addition to titles'
61+
},
62+
]
63+
},
64+
cli.Command{
65+
name: 'create'
66+
description: 'Create a new issue'
67+
execute: issue_create
68+
flags: [
69+
cli.Flag{
70+
flag: .string
71+
name: 'title'
72+
abbrev: 't'
73+
description: 'Title of the issue to create'
74+
required: true
75+
},
76+
]
77+
},
78+
]
79+
}
80+
}
81+
82+
fn config_command() cli.Command {
83+
return cli.Command{
84+
name: 'config'
85+
description: 'Read or write tasky settings'
86+
group: 'Additional commands'
87+
commands: [
88+
cli.Command{
89+
name: 'get'
90+
description: 'Print the value of a config key'
91+
usage: '<key>'
92+
required_args: 1
93+
execute: config_get
94+
},
95+
cli.Command{
96+
name: 'set'
97+
description: 'Update a config key'
98+
usage: '<key> <value>'
99+
required_args: 2
100+
execute: config_set
101+
},
102+
]
103+
}
104+
}
105+
106+
fn issue_list(cmd cli.Command) ! {
107+
verbose := cmd.flags.get_bool('verbose') or { false }
108+
println('Listing issues (verbose=${verbose})')
109+
}
110+
111+
fn issue_create(cmd cli.Command) ! {
112+
title := cmd.flags.get_string('title')!
113+
println('Created issue: ${title}')
114+
}
115+
116+
fn config_get(cmd cli.Command) ! {
117+
println('config get ${cmd.args[0]}')
118+
}
119+
120+
fn config_set(cmd cli.Command) ! {
121+
println('config set ${cmd.args[0]} = ${cmd.args[1]}')
122+
}

vlib/cli/README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,26 @@ fn main() {
4040

4141
Subcommands can set `alias` to accept a shorter invocation token, for example
4242
`example-app s`.
43+
44+
## Help layout
45+
46+
`--help` is generated automatically. Beyond `Usage:`, `Flags:` and `Commands:`
47+
the output picks up extra sections when the relevant `Command` fields are
48+
populated:
49+
50+
- **`Inherited flags:`** — flags inherited from any ancestor through
51+
`Flag.global`. Renders separately from local flags so a sub-command's
52+
own surface stays readable.
53+
- **Grouped commands** — set `group` on a sub-command to lift it out of
54+
the default `Commands:` block into a section named after the group.
55+
The group string is rendered verbatim followed by `:`, so capitalisation
56+
is the caller's choice. Sub-commands sharing a group keep their
57+
declaration order.
58+
- **`Examples:`** — set `examples []string` on a `Command`; each entry
59+
becomes one indented line.
60+
- **`Learn more:`** — set `learn_more string`; newlines split the block
61+
into separate lines.
62+
63+
Sections that have nothing to render are simply omitted, so existing apps
64+
see no change unless they opt in by setting these fields. See
65+
`examples/cli_groups.v` for a runnable program that exercises all of them.

vlib/cli/command.v

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,33 @@ pub mut:
1818
description string
1919
man_description string
2020
version string
21-
pre_execute FnCommandCallback = unsafe { nil }
22-
execute FnCommandCallback = unsafe { nil }
23-
post_execute FnCommandCallback = unsafe { nil }
24-
disable_flags bool
25-
sort_flags bool
26-
sort_commands bool
27-
parent &Command = unsafe { nil }
28-
commands []Command
29-
flags []Flag
30-
required_args int
31-
args []string
32-
posix_mode bool
33-
defaults struct {
21+
// group is the section title under which `--help` lists this sub-command
22+
// in its parent's command listing. Empty falls back to the default
23+
// `Commands:` section. The string is rendered verbatim followed by `:`
24+
// — capitalisation is the caller's responsibility. Groups appear in the
25+
// order their first member is declared, which can interact with
26+
// `sort_commands` (sorting may change which member of each group comes
27+
// first, hence which group is listed first).
28+
group string
29+
// examples lists invocations rendered under `Examples:` by `--help`.
30+
// Each entry is one line; a leading `$` is conventional but not required.
31+
examples []string
32+
// learn_more is rendered under the `Learn more:` section by `--help`.
33+
// Newlines split the block into separate lines.
34+
learn_more string
35+
pre_execute FnCommandCallback = unsafe { nil }
36+
execute FnCommandCallback = unsafe { nil }
37+
post_execute FnCommandCallback = unsafe { nil }
38+
disable_flags bool
39+
sort_flags bool
40+
sort_commands bool
41+
parent &Command = unsafe { nil }
42+
commands []Command
43+
flags []Flag
44+
required_args int
45+
args []string
46+
posix_mode bool
47+
defaults struct {
3448
pub:
3549
help Defaults = true
3650
man Defaults = true
@@ -63,6 +77,9 @@ pub fn (cmd &Command) str() string {
6377
res << ' version: "${cmd.version}"'
6478
res << ' description: "${cmd.description}"'
6579
res << ' man_description: "${cmd.man_description}"'
80+
res << ' group: "${cmd.group}"'
81+
res << ' examples: ${cmd.examples}'
82+
res << ' learn_more: "${cmd.learn_more}"'
6683
res << ' disable_flags: ${cmd.disable_flags}'
6784
res << ' sort_flags: ${cmd.sort_flags}'
6885
res << ' sort_commands: ${cmd.sort_commands}'

0 commit comments

Comments
 (0)