Skip to content
Open
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
122 changes: 122 additions & 0 deletions examples/cli_groups.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// This example demonstrates the `vlib/cli` features that show up automatically
// in `--help` when you populate the optional `group`, `examples` and
// `learn_more` fields of `Command`. No opt-in is required — leaving any of
// them empty simply skips the corresponding section.
//
// Compile from the V repo root:
// v -o tasky examples/cli_groups.v
//
// Try it out:
// ./tasky --help # root help with grouped commands and examples
// ./tasky issue --help # sub-command help with INHERITED FLAGS
// ./tasky issue list -v
// ./tasky version
module main

import cli
import os

fn main() {
mut app := cli.Command{
name: 'tasky'
description: 'A tiny issue tracker CLI'
version: '0.1.0'
posix_mode: true
examples: [
'\$ tasky issue list',
'\$ tasky issue create --title "Fix CI"',
'\$ tasky config get editor',
]
learn_more: 'Use `tasky <command> --help` for details about a command.\nDocumentation lives at https://example.test/tasky'
}
app.add_flag(cli.Flag{
flag: .string
name: 'config'
abbrev: 'c'
description: 'Path to a tasky config file'
global: true
})
app.add_command(issue_command())
app.add_command(config_command())
app.setup()
app.parse(os.args)
}

fn issue_command() cli.Command {
return cli.Command{
name: 'issue'
description: 'Work with issues'
group: 'Core commands'
commands: [
cli.Command{
name: 'list'
description: 'List open issues'
execute: issue_list
flags: [
cli.Flag{
flag: .bool
name: 'verbose'
abbrev: 'v'
description: 'Show issue bodies in addition to titles'
},
]
},
cli.Command{
name: 'create'
description: 'Create a new issue'
execute: issue_create
flags: [
cli.Flag{
flag: .string
name: 'title'
abbrev: 't'
description: 'Title of the issue to create'
required: true
},
]
},
]
}
}

fn config_command() cli.Command {
return cli.Command{
name: 'config'
description: 'Read or write tasky settings'
group: 'Additional commands'
commands: [
cli.Command{
name: 'get'
description: 'Print the value of a config key'
usage: '<key>'
required_args: 1
execute: config_get
},
cli.Command{
name: 'set'
description: 'Update a config key'
usage: '<key> <value>'
required_args: 2
execute: config_set
},
]
}
}

fn issue_list(cmd cli.Command) ! {
verbose := cmd.flags.get_bool('verbose') or { false }
println('Listing issues (verbose=${verbose})')
}

fn issue_create(cmd cli.Command) ! {
title := cmd.flags.get_string('title')!
println('Created issue: ${title}')
}

fn config_get(cmd cli.Command) ! {
println('config get ${cmd.args[0]}')
}

fn config_set(cmd cli.Command) ! {
println('config set ${cmd.args[0]} = ${cmd.args[1]}')
}
23 changes: 23 additions & 0 deletions vlib/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,26 @@ fn main() {

Subcommands can set `alias` to accept a shorter invocation token, for example
`example-app s`.

## Help layout

`--help` is generated automatically. Beyond `Usage:`, `Flags:` and `Commands:`
the output picks up extra sections when the relevant `Command` fields are
populated:

- **`Inherited flags:`** — flags inherited from any ancestor through
`Flag.global`. Renders separately from local flags so a sub-command's
own surface stays readable.
- **Grouped commands** — set `group` on a sub-command to lift it out of
the default `Commands:` block into a section named after the group.
The group string is rendered verbatim followed by `:`, so capitalisation
is the caller's choice. Sub-commands sharing a group keep their
declaration order.
- **`Examples:`** — set `examples []string` on a `Command`; each entry
becomes one indented line.
- **`Learn more:`** — set `learn_more string`; newlines split the block
into separate lines.

Sections that have nothing to render are simply omitted, so existing apps
see no change unless they opt in by setting these fields. See
`examples/cli_groups.v` for a runnable program that exercises all of them.
43 changes: 30 additions & 13 deletions vlib/cli/command.v
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,33 @@ pub mut:
description string
man_description string
version string
pre_execute FnCommandCallback = unsafe { nil }
execute FnCommandCallback = unsafe { nil }
post_execute FnCommandCallback = unsafe { nil }
disable_flags bool
sort_flags bool
sort_commands bool
parent &Command = unsafe { nil }
commands []Command
flags []Flag
required_args int
args []string
posix_mode bool
defaults struct {
// group is the section title under which `--help` lists this sub-command
// in its parent's command listing. Empty falls back to the default
// `Commands:` section. The string is rendered verbatim followed by `:`
// — capitalisation is the caller's responsibility. Groups appear in the
// order their first member is declared, which can interact with
// `sort_commands` (sorting may change which member of each group comes
// first, hence which group is listed first).
group string
// examples lists invocations rendered under `Examples:` by `--help`.
// Each entry is one line; a leading `$` is conventional but not required.
examples []string
// learn_more is rendered under the `Learn more:` section by `--help`.
// Newlines split the block into separate lines.
learn_more string
pre_execute FnCommandCallback = unsafe { nil }
execute FnCommandCallback = unsafe { nil }
post_execute FnCommandCallback = unsafe { nil }
disable_flags bool
sort_flags bool
sort_commands bool
parent &Command = unsafe { nil }
commands []Command
flags []Flag
required_args int
args []string
posix_mode bool
defaults struct {
pub:
help Defaults = true
man Defaults = true
Expand Down Expand Up @@ -63,6 +77,9 @@ pub fn (cmd &Command) str() string {
res << ' version: "${cmd.version}"'
res << ' description: "${cmd.description}"'
res << ' man_description: "${cmd.man_description}"'
res << ' group: "${cmd.group}"'
res << ' examples: ${cmd.examples}'
res << ' learn_more: "${cmd.learn_more}"'
res << ' disable_flags: ${cmd.disable_flags}'
res << ' sort_flags: ${cmd.sort_flags}'
res << ' sort_commands: ${cmd.sort_commands}'
Expand Down
Loading
Loading