Initial project import
This commit is contained in:
commit
23f2d29e32
11957 files changed
+1754577
No files matched your search
@@ -0,0 +1,65 @@
|
||||
## Description
|
||||
|
||||
`cli` is a command line option parser, that supports
|
||||
declarative subcommands, each having a separate set of options.
|
||||
|
||||
See also the `flag` module, for a simpler command line option parser,
|
||||
that supports only options.
|
||||
|
||||
## Example
|
||||
|
||||
```v
|
||||
module main
|
||||
|
||||
import os
|
||||
import cli
|
||||
|
||||
fn main() {
|
||||
mut app := cli.Command{
|
||||
name: 'example-app'
|
||||
description: 'example-app'
|
||||
execute: fn (cmd cli.Command) ! {
|
||||
println('hello app')
|
||||
return
|
||||
}
|
||||
commands: [
|
||||
cli.Command{
|
||||
name: 'sub'
|
||||
alias: 's'
|
||||
execute: fn (cmd cli.Command) ! {
|
||||
println('hello subcommand')
|
||||
return
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
app.setup()
|
||||
app.parse(os.args)
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,35 @@
|
||||
// vtest build: tinyc
|
||||
module main
|
||||
|
||||
import v.util.diff
|
||||
import term
|
||||
import os
|
||||
|
||||
const vexe = @VEXE
|
||||
const vroot = os.dir(vexe)
|
||||
|
||||
fn test_cli_programs() {
|
||||
testdata := os.join_path(vroot, 'vlib/cli/testdata')
|
||||
mut has_err := false
|
||||
for test in os.walk_ext(testdata, '.vv') {
|
||||
print(test + ' ')
|
||||
out_path := test.all_before_last('.vv') + '.out'
|
||||
if !os.exists(out_path) {
|
||||
println(term.red('FAIL'))
|
||||
eprintln('failed to find output file for `${test}`')
|
||||
has_err = true
|
||||
continue
|
||||
}
|
||||
expected_out := os.read_file(out_path)!.replace('\r\n', '\n')
|
||||
test_out := os.execute('${vexe} run ${test}').output.replace('\r\n', '\n')
|
||||
diff_ := diff.compare_text(expected_out, test_out)!
|
||||
if diff_ != '' {
|
||||
println(term.red('FAIL'))
|
||||
eprintln(diff_)
|
||||
has_err = true
|
||||
} else {
|
||||
println(term.green('OK'))
|
||||
}
|
||||
}
|
||||
assert !has_err
|
||||
}
|
||||
@@ -0,0 +1,450 @@
|
||||
module cli
|
||||
|
||||
import term
|
||||
|
||||
type FnCommandCallback = fn (cmd Command) !
|
||||
|
||||
// str returns the `string` representation of the callback.
|
||||
pub fn (f FnCommandCallback) str() string {
|
||||
return 'FnCommandCallback=>' + ptr_str(f)
|
||||
}
|
||||
|
||||
// Command is a structured representation of a single command or chain of commands.
|
||||
pub struct Command {
|
||||
pub mut:
|
||||
name string
|
||||
alias string
|
||||
usage string
|
||||
description string
|
||||
man_description string
|
||||
version string
|
||||
// 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
|
||||
version Defaults = true
|
||||
mut:
|
||||
parsed struct {
|
||||
mut:
|
||||
help CommandFlag
|
||||
version CommandFlag
|
||||
man CommandFlag
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CommandFlag {
|
||||
pub mut:
|
||||
command bool = true
|
||||
flag bool = true
|
||||
}
|
||||
|
||||
type Defaults = CommandFlag | bool
|
||||
|
||||
// str returns the `string` representation of the `Command`.
|
||||
pub fn (cmd &Command) str() string {
|
||||
mut res := []string{}
|
||||
res << 'Command{'
|
||||
res << ' name: "${cmd.name}"'
|
||||
res << ' alias: "${cmd.alias}"'
|
||||
res << ' usage: "${cmd.usage}"'
|
||||
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}'
|
||||
res << ' cb execute: ${cmd.execute}'
|
||||
res << ' cb pre_execute: ${cmd.pre_execute}'
|
||||
res << ' cb post_execute: ${cmd.post_execute}'
|
||||
if unsafe { cmd.parent == 0 } {
|
||||
res << ' parent: &Command(0)'
|
||||
} else {
|
||||
res << ' parent: &Command{${cmd.parent.name} ...}'
|
||||
}
|
||||
res << ' commands: ${cmd.commands}'
|
||||
res << ' flags: ${cmd.flags}'
|
||||
res << ' required_args: ${cmd.required_args}'
|
||||
res << ' args: ${cmd.args}'
|
||||
res << ' posix_mode: ${cmd.posix_mode}'
|
||||
match cmd.defaults.help {
|
||||
bool {
|
||||
res << ' defaults.help: ${cmd.defaults.help}'
|
||||
}
|
||||
CommandFlag {
|
||||
res << ' defaults.help.command: ${cmd.defaults.help.command}'
|
||||
res << ' defaults.help.flag: ${cmd.defaults.help.flag}'
|
||||
}
|
||||
}
|
||||
|
||||
match cmd.defaults.man {
|
||||
bool {
|
||||
res << ' defaults.man: ${cmd.defaults.man}'
|
||||
}
|
||||
CommandFlag {
|
||||
res << ' defaults.man.command: ${cmd.defaults.man.command}'
|
||||
res << ' defaults.man.flag: ${cmd.defaults.man.flag}'
|
||||
}
|
||||
}
|
||||
|
||||
match cmd.defaults.version {
|
||||
bool {
|
||||
res << ' defaults.version: ${cmd.defaults.version}'
|
||||
}
|
||||
CommandFlag {
|
||||
res << ' defaults.version.command: ${cmd.defaults.version.command}'
|
||||
res << ' defaults.version.flag: ${cmd.defaults.version.flag}'
|
||||
}
|
||||
}
|
||||
|
||||
res << '}'
|
||||
return res.join('\n')
|
||||
}
|
||||
|
||||
// is_root returns `true` if this `Command` has no parents.
|
||||
pub fn (cmd &Command) is_root() bool {
|
||||
return isnil(cmd.parent)
|
||||
}
|
||||
|
||||
// root returns the root `Command` of the command chain.
|
||||
pub fn (cmd &Command) root() Command {
|
||||
if cmd.is_root() {
|
||||
return *cmd
|
||||
}
|
||||
return cmd.parent.root()
|
||||
}
|
||||
|
||||
// full_name returns the full `string` representation of all commands int the chain.
|
||||
pub fn (cmd &Command) full_name() string {
|
||||
if cmd.is_root() {
|
||||
return cmd.name
|
||||
}
|
||||
return cmd.parent.full_name() + ' ${cmd.name}'
|
||||
}
|
||||
|
||||
// add_commands adds the `commands` array of `Command`s as sub-commands.
|
||||
pub fn (mut cmd Command) add_commands(commands []Command) {
|
||||
for command in commands {
|
||||
cmd.add_command(command)
|
||||
}
|
||||
}
|
||||
|
||||
// add_command adds `command` as a sub-command of this `Command`.
|
||||
pub fn (mut cmd Command) add_command(command Command) {
|
||||
mut subcmd := command
|
||||
for existing in cmd.commands {
|
||||
if existing.name == subcmd.name {
|
||||
eprintln_exit('Command with the name `${subcmd.name}` already exists')
|
||||
}
|
||||
if existing.alias != '' && existing.alias == subcmd.name {
|
||||
eprintln_exit('Command with the name `${subcmd.name}` already exists as an alias')
|
||||
}
|
||||
if subcmd.alias != '' && existing.matches(subcmd.alias) {
|
||||
eprintln_exit('Command alias `${subcmd.alias}` already exists')
|
||||
}
|
||||
}
|
||||
subcmd.parent = unsafe { cmd }
|
||||
cmd.commands << subcmd
|
||||
}
|
||||
|
||||
// setup ensures that all sub-commands of this `Command` is linked as a chain.
|
||||
pub fn (mut cmd Command) setup() {
|
||||
for mut subcmd in cmd.commands {
|
||||
subcmd.parent = unsafe { cmd }
|
||||
subcmd.posix_mode = cmd.posix_mode
|
||||
subcmd.setup()
|
||||
}
|
||||
}
|
||||
|
||||
// add_flags adds the array `flags` to this `Command`.
|
||||
pub fn (mut cmd Command) add_flags(flags []Flag) {
|
||||
for flag in flags {
|
||||
cmd.add_flag(flag)
|
||||
}
|
||||
}
|
||||
|
||||
// add_flag adds `flag` to this `Command`.
|
||||
pub fn (mut cmd Command) add_flag(flag Flag) {
|
||||
if cmd.flags.contains(flag.name) {
|
||||
eprintln_exit('Flag with the name `${flag.name}` already exists')
|
||||
}
|
||||
cmd.flags << flag
|
||||
}
|
||||
|
||||
// TODO: remove deprecated `disable_<>` switches after deprecation period.
|
||||
fn (mut cmd Command) parse_defaults() {
|
||||
// Help
|
||||
if cmd.defaults.help is bool {
|
||||
cmd.defaults.parsed.help.flag = cmd.defaults.help
|
||||
cmd.defaults.parsed.help.command = cmd.defaults.help
|
||||
} else if cmd.defaults.help is CommandFlag {
|
||||
cmd.defaults.parsed.help.flag = cmd.defaults.help.flag
|
||||
cmd.defaults.parsed.help.command = cmd.defaults.help.command
|
||||
}
|
||||
// Version
|
||||
if cmd.defaults.version is bool {
|
||||
cmd.defaults.parsed.version.flag = cmd.defaults.version
|
||||
cmd.defaults.parsed.version.command = cmd.defaults.version
|
||||
} else if cmd.defaults.version is CommandFlag {
|
||||
cmd.defaults.parsed.version.flag = cmd.defaults.version.flag
|
||||
cmd.defaults.parsed.version.command = cmd.defaults.version.command
|
||||
}
|
||||
// Man
|
||||
if cmd.defaults.man is bool {
|
||||
cmd.defaults.parsed.man.flag = cmd.defaults.man
|
||||
cmd.defaults.parsed.man.command = cmd.defaults.man
|
||||
} else if cmd.defaults.man is CommandFlag {
|
||||
cmd.defaults.parsed.man.flag = cmd.defaults.man.flag
|
||||
cmd.defaults.parsed.man.command = cmd.defaults.man.command
|
||||
}
|
||||
// Add Flags
|
||||
if !cmd.disable_flags {
|
||||
cmd.add_default_flags()
|
||||
}
|
||||
// Add Commands
|
||||
cmd.add_default_commands()
|
||||
}
|
||||
|
||||
// parse parses the flags and commands from the given `args` into the `Command`.
|
||||
pub fn (mut cmd Command) parse(args []string) {
|
||||
cmd.parse_defaults()
|
||||
if cmd.sort_flags {
|
||||
cmd.flags.sort(a.name < b.name)
|
||||
}
|
||||
if cmd.sort_commands {
|
||||
cmd.commands.sort(a.name < b.name)
|
||||
}
|
||||
cmd.args = args[1..]
|
||||
if !cmd.disable_flags {
|
||||
cmd.parse_flags()
|
||||
}
|
||||
cmd.parse_commands()
|
||||
}
|
||||
|
||||
// add_default_flags adds the commonly used `-h`/`--help`, `--man`, and
|
||||
// `-v`/`--version` flags to the `Command`.
|
||||
fn (mut cmd Command) add_default_flags() {
|
||||
if cmd.defaults.parsed.help.flag && !cmd.flags.contains('help') {
|
||||
use_help_abbrev := !cmd.flags.contains('h') && cmd.posix_mode
|
||||
cmd.add_flag(help_flag(use_help_abbrev))
|
||||
}
|
||||
if cmd.defaults.parsed.version.flag && cmd.version != '' && !cmd.flags.contains('version') {
|
||||
use_version_abbrev := !cmd.flags.contains('v') && cmd.posix_mode
|
||||
cmd.add_flag(version_flag(use_version_abbrev))
|
||||
}
|
||||
if cmd.defaults.parsed.man.flag && !cmd.flags.contains('man') {
|
||||
cmd.add_flag(man_flag())
|
||||
}
|
||||
}
|
||||
|
||||
// add_default_commands adds the command functions of the commonly
|
||||
// used `help`, `man`, and `version` subcommands to the `Command`.
|
||||
fn (mut cmd Command) add_default_commands() {
|
||||
if cmd.defaults.parsed.help.command && !cmd.commands.contains('help') && cmd.is_root() {
|
||||
cmd.add_command(help_cmd())
|
||||
}
|
||||
if cmd.defaults.parsed.version.command && cmd.version != '' && !cmd.commands.contains('version') {
|
||||
cmd.add_command(version_cmd())
|
||||
}
|
||||
if cmd.defaults.parsed.man.command && !cmd.commands.contains('man') && cmd.is_root() {
|
||||
cmd.add_command(man_cmd())
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut cmd Command) parse_flags() {
|
||||
for cmd.args.len > 0 {
|
||||
if !cmd.args[0].starts_with('-') {
|
||||
return
|
||||
}
|
||||
mut found := false
|
||||
for mut flag in cmd.flags {
|
||||
if flag.matches(cmd.args[0], cmd.posix_mode) {
|
||||
found = true
|
||||
flag.found = true
|
||||
// Eat flag and its values, continue with reduced args.
|
||||
cmd.args = flag.parse(cmd.args, cmd.posix_mode) or {
|
||||
eprintln_exit('Failed to parse flag `${cmd.args[0]}`: ${err}')
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
eprintln_exit('Command `${cmd.name}` has no flag `${cmd.args[0]}`')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut cmd Command) parse_commands() {
|
||||
global_flags := cmd.flags.filter(it.global)
|
||||
cmd.check_help_flag()
|
||||
cmd.check_version_flag()
|
||||
cmd.check_man_flag()
|
||||
for i in 0 .. cmd.args.len {
|
||||
arg := cmd.args[i]
|
||||
for j in 0 .. cmd.commands.len {
|
||||
mut command := cmd.commands[j]
|
||||
if command.matches(arg) {
|
||||
for flag in global_flags {
|
||||
command.add_flag(flag)
|
||||
}
|
||||
command.parse(cmd.args[i..])
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
if cmd.is_root() && isnil(cmd.execute) {
|
||||
if cmd.defaults.parsed.help.command {
|
||||
cmd.execute_help()
|
||||
return
|
||||
}
|
||||
}
|
||||
// if no further command was found, execute current command
|
||||
if cmd.required_args > 0 {
|
||||
if cmd.required_args > cmd.args.len {
|
||||
descriptor := if cmd.required_args == 1 { 'argument' } else { 'arguments' }
|
||||
eprintln_exit('Command `${cmd.name}` needs at least ${cmd.required_args} ${descriptor}')
|
||||
}
|
||||
}
|
||||
cmd.check_required_flags()
|
||||
|
||||
cmd.handle_cb(cmd.pre_execute, 'preexecution')
|
||||
cmd.handle_cb(cmd.execute, 'execution')
|
||||
cmd.handle_cb(cmd.post_execute, 'postexecution')
|
||||
}
|
||||
|
||||
fn (cmd &Command) display_name() string {
|
||||
if cmd.alias == '' {
|
||||
return cmd.name
|
||||
}
|
||||
return '${cmd.name} (${cmd.alias})'
|
||||
}
|
||||
|
||||
fn (cmd &Command) matches(token string) bool {
|
||||
return cmd.name == token || (cmd.alias != '' && cmd.alias == token)
|
||||
}
|
||||
|
||||
fn (mut cmd Command) handle_cb(cb FnCommandCallback, label string) {
|
||||
if !isnil(cb) {
|
||||
cb(*cmd) or {
|
||||
label_message := term.ecolorize(term.bright_red, 'cli ${label} error:')
|
||||
eprintln_exit('${label_message} ${err}')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn (cmd &Command) check_help_flag() {
|
||||
if cmd.defaults.parsed.help.flag && cmd.flags.contains('help') {
|
||||
help_enabled :=
|
||||
cmd.flags.get_bool('help') or { return } // ignore error and handle command normally
|
||||
if help_enabled {
|
||||
cmd.execute_help()
|
||||
exit(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn (cmd &Command) check_man_flag() {
|
||||
if cmd.defaults.parsed.man.flag && cmd.flags.contains('man') {
|
||||
man_enabled :=
|
||||
cmd.flags.get_bool('man') or { return } // ignore error and handle command normally
|
||||
if man_enabled {
|
||||
cmd.execute_man()
|
||||
exit(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn (cmd &Command) check_version_flag() {
|
||||
if cmd.defaults.parsed.version.flag && cmd.version != '' && cmd.flags.contains('version') {
|
||||
version_enabled :=
|
||||
cmd.flags.get_bool('version') or { return } // ignore error and handle command normally
|
||||
if version_enabled {
|
||||
print_version_for_command(cmd) or { panic(err) }
|
||||
exit(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn (cmd &Command) check_required_flags() {
|
||||
for flag in cmd.flags {
|
||||
if flag.required && flag.value.len == 0 {
|
||||
full_name := cmd.full_name()
|
||||
eprintln_exit('Flag `${flag.name}` is required by `${full_name}`')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// execute_help executes the callback registered for the `-h`/`--help` flag option.
|
||||
pub fn (cmd &Command) execute_help() {
|
||||
if cmd.commands.contains('help') {
|
||||
sub := cmd.commands.get('help') or { return } // ignore error and handle command normally
|
||||
if !isnil(sub.execute) {
|
||||
sub.execute(sub) or { panic(err) }
|
||||
return
|
||||
}
|
||||
}
|
||||
print(cmd.help_message())
|
||||
}
|
||||
|
||||
// execute_man executes the callback registered for the `-man` flag option.
|
||||
pub fn (cmd &Command) execute_man() {
|
||||
if cmd.commands.contains('man') {
|
||||
sub := cmd.commands.get('man') or { return }
|
||||
sub.execute(sub) or { panic(err) }
|
||||
} else {
|
||||
print(cmd.manpage())
|
||||
}
|
||||
}
|
||||
|
||||
fn (cmds []Command) get(name string) !Command {
|
||||
for cmd in cmds {
|
||||
if cmd.name == name {
|
||||
return cmd
|
||||
}
|
||||
}
|
||||
return error('Command `${name}` not found in ${cmds}')
|
||||
}
|
||||
|
||||
fn (cmds []Command) contains(name string) bool {
|
||||
for cmd in cmds {
|
||||
if cmd.name == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@[noreturn]
|
||||
fn eprintln_exit(message string) {
|
||||
eprintln(message)
|
||||
exit(1)
|
||||
}
|
||||
@@ -0,0 +1,309 @@
|
||||
@[has_globals]
|
||||
module main
|
||||
|
||||
import cli
|
||||
|
||||
__global (
|
||||
if_subcommands_parse_args_called bool
|
||||
flag_should_be_set_called bool
|
||||
assert_flags_called bool
|
||||
flag_is_set_in_subcommand_called bool
|
||||
)
|
||||
|
||||
fn test_if_command_parses_empty_args() {
|
||||
mut cmd := cli.Command{
|
||||
name: 'command'
|
||||
execute: empty_func
|
||||
}
|
||||
cmd.parse(['command'])
|
||||
assert cmd.name == 'command' && cmd.args == []
|
||||
}
|
||||
|
||||
fn test_if_command_parses_args() {
|
||||
mut cmd := cli.Command{
|
||||
name: 'command'
|
||||
execute: empty_func
|
||||
}
|
||||
cmd.parse(['command', 'arg0', 'arg1'])
|
||||
assert cmd.name == 'command' && cmd.args == ['arg0', 'arg1']
|
||||
}
|
||||
|
||||
fn test_if_subcommands_parse_args() {
|
||||
mut cmd := cli.Command{
|
||||
name: 'command'
|
||||
}
|
||||
if_subcommands_parse_args_called = false
|
||||
subcmd := cli.Command{
|
||||
name: 'subcommand'
|
||||
execute: if_subcommands_parse_args_func
|
||||
}
|
||||
cmd.add_command(subcmd)
|
||||
cmd.parse(['command', 'subcommand', 'arg0', 'arg1'])
|
||||
assert if_subcommands_parse_args_called
|
||||
}
|
||||
|
||||
fn test_if_subcommand_alias_parses_args() {
|
||||
mut cmd := cli.Command{
|
||||
name: 'command'
|
||||
}
|
||||
subcmd := cli.Command{
|
||||
name: 'subcommand'
|
||||
alias: 'sc'
|
||||
execute: if_subcommands_parse_args_func
|
||||
}
|
||||
cmd.add_command(subcmd)
|
||||
cmd.parse(['command', 'sc', 'arg0', 'arg1'])
|
||||
}
|
||||
|
||||
fn if_subcommands_parse_args_func(cmd cli.Command) ! {
|
||||
if_subcommands_parse_args_called = true
|
||||
assert cmd.name == 'subcommand' && cmd.args == ['arg0', 'arg1']
|
||||
}
|
||||
|
||||
fn test_default_subcommands() {
|
||||
mut cmd := cli.Command{
|
||||
name: 'command'
|
||||
}
|
||||
cmd.parse(['command'])
|
||||
assert cmd.commands.any(it.name == 'help')
|
||||
assert cmd.commands.any(it.name == 'man')
|
||||
|
||||
cmd = cli.Command{
|
||||
name: 'command'
|
||||
version: '1.0.0'
|
||||
}
|
||||
cmd.parse(['command'])
|
||||
assert cmd.commands.any(it.name == 'version')
|
||||
}
|
||||
|
||||
fn flag_should_be_set(cmd cli.Command) ! {
|
||||
flag_should_be_set_called = true
|
||||
flag := cmd.flags.get_string('flag')!
|
||||
assert flag == 'value'
|
||||
}
|
||||
|
||||
fn test_if_flag_gets_set() {
|
||||
flag_should_be_set_called = false
|
||||
mut cmd := cli.Command{
|
||||
name: 'command'
|
||||
execute: flag_should_be_set
|
||||
}
|
||||
cmd.add_flag(cli.Flag{
|
||||
flag: .string
|
||||
name: 'flag'
|
||||
})
|
||||
cmd.parse(['command', '-flag', 'value'])
|
||||
assert flag_should_be_set_called
|
||||
}
|
||||
|
||||
fn test_if_flag_gets_set_with_abbrev() {
|
||||
flag_should_be_set_called = false
|
||||
mut cmd := cli.Command{
|
||||
name: 'command'
|
||||
execute: flag_should_be_set
|
||||
}
|
||||
cmd.add_flag(cli.Flag{
|
||||
flag: .string
|
||||
name: 'flag'
|
||||
abbrev: 'f'
|
||||
})
|
||||
cmd.parse(['command', '-f', 'value'])
|
||||
assert flag_should_be_set_called
|
||||
}
|
||||
|
||||
fn test_if_flag_gets_set_with_long_arg() {
|
||||
flag_should_be_set_called = false
|
||||
mut cmd := cli.Command{
|
||||
name: 'command'
|
||||
execute: flag_should_be_set
|
||||
posix_mode: true
|
||||
}
|
||||
cmd.add_flag(cli.Flag{
|
||||
flag: .string
|
||||
name: 'flag'
|
||||
abbrev: 'f'
|
||||
})
|
||||
cmd.parse(['command', '--flag', 'value'])
|
||||
assert flag_should_be_set_called
|
||||
}
|
||||
|
||||
fn assert_flags(cmd cli.Command) ! {
|
||||
assert_flags_called = true
|
||||
flag := cmd.flags.get_string('flag')!
|
||||
assert flag == 'value'
|
||||
value := cmd.flags.get_int('value')!
|
||||
assert value == 42
|
||||
flag2 := cmd.flags.get_string('flag-2')!
|
||||
assert flag2 == 'value-2'
|
||||
}
|
||||
|
||||
fn test_if_multiple_flags_get_set() {
|
||||
assert_flags_called = false
|
||||
mut cmd := cli.Command{
|
||||
name: 'command'
|
||||
execute: assert_flags
|
||||
}
|
||||
cmd.add_flag(cli.Flag{
|
||||
flag: .string
|
||||
name: 'flag'
|
||||
})
|
||||
cmd.add_flag(cli.Flag{
|
||||
flag: .string
|
||||
name: 'flag-2'
|
||||
})
|
||||
cmd.add_flag(cli.Flag{
|
||||
flag: .int
|
||||
name: 'value'
|
||||
})
|
||||
cmd.parse(['command', '-flag=value', '-value', '42', '-flag-2', 'value-2'])
|
||||
assert assert_flags_called
|
||||
}
|
||||
|
||||
fn test_if_required_flags_get_set() {
|
||||
assert_flags_called = false
|
||||
mut cmd := cli.Command{
|
||||
name: 'command'
|
||||
execute: assert_flags
|
||||
}
|
||||
cmd.add_flag(cli.Flag{
|
||||
flag: .string
|
||||
name: 'flag'
|
||||
})
|
||||
cmd.add_flag(cli.Flag{
|
||||
flag: .string
|
||||
name: 'flag-2'
|
||||
})
|
||||
cmd.add_flag(cli.Flag{
|
||||
flag: .int
|
||||
name: 'value'
|
||||
required: true
|
||||
})
|
||||
cmd.parse(['command', '-flag', 'value', '-value', '42', '-flag-2', 'value-2'])
|
||||
assert assert_flags_called
|
||||
}
|
||||
|
||||
fn flag_is_set_in_subcommand(cmd cli.Command) ! {
|
||||
flag_is_set_in_subcommand_called = true
|
||||
flag := cmd.flags.get_string('flag') or { panic(err) }
|
||||
assert flag == 'value'
|
||||
}
|
||||
|
||||
fn test_if_flag_gets_set_in_subcommand() {
|
||||
mut cmd := cli.Command{
|
||||
name: 'command'
|
||||
execute: empty_func
|
||||
}
|
||||
flag_is_set_in_subcommand_called = false
|
||||
mut subcmd := cli.Command{
|
||||
name: 'subcommand'
|
||||
execute: flag_is_set_in_subcommand
|
||||
}
|
||||
subcmd.add_flag(cli.Flag{
|
||||
flag: .string
|
||||
name: 'flag'
|
||||
})
|
||||
cmd.add_command(subcmd)
|
||||
cmd.parse(['command', 'subcommand', '-flag', 'value'])
|
||||
assert flag_is_set_in_subcommand_called
|
||||
}
|
||||
|
||||
fn test_if_global_flag_gets_set_in_subcommand() {
|
||||
mut cmd := cli.Command{
|
||||
name: 'command'
|
||||
execute: empty_func
|
||||
}
|
||||
cmd.add_flag(cli.Flag{
|
||||
flag: .string
|
||||
name: 'flag'
|
||||
global: true
|
||||
})
|
||||
flag_is_set_in_subcommand_called = false
|
||||
subcmd := cli.Command{
|
||||
name: 'subcommand'
|
||||
execute: flag_is_set_in_subcommand
|
||||
}
|
||||
cmd.add_command(subcmd)
|
||||
cmd.parse(['command', '-flag', 'value', 'subcommand'])
|
||||
assert flag_is_set_in_subcommand_called
|
||||
}
|
||||
|
||||
fn test_command_setup() {
|
||||
mut cmd := cli.Command{
|
||||
name: 'root'
|
||||
commands: [
|
||||
cli.Command{
|
||||
name: 'child'
|
||||
commands: [
|
||||
cli.Command{
|
||||
name: 'child-child'
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
}
|
||||
assert isnil(cmd.commands[0].parent)
|
||||
assert isnil(cmd.commands[0].commands[0].parent)
|
||||
cmd.setup()
|
||||
assert cmd.commands[0].parent.name == 'root'
|
||||
assert cmd.commands[0].commands[0].parent.name == 'child'
|
||||
}
|
||||
|
||||
fn test_help_message_lists_command_aliases() {
|
||||
cmd := cli.Command{
|
||||
name: 'command'
|
||||
description: 'description'
|
||||
commands: [
|
||||
cli.Command{
|
||||
name: 'sub'
|
||||
alias: 's'
|
||||
description: 'subcommand'
|
||||
},
|
||||
]
|
||||
}
|
||||
assert cmd.help_message() == r'Usage: command [commands]
|
||||
|
||||
description
|
||||
|
||||
Commands:
|
||||
sub (s) subcommand
|
||||
'
|
||||
}
|
||||
|
||||
fn test_manpage_lists_command_aliases() {
|
||||
mut cmd := cli.Command{
|
||||
name: 'command'
|
||||
description: 'description'
|
||||
commands: [
|
||||
cli.Command{
|
||||
name: 'sub'
|
||||
alias: 's'
|
||||
description: 'subcommand'
|
||||
},
|
||||
]
|
||||
}
|
||||
cmd.setup()
|
||||
assert cmd.manpage().after_char(`\n`) == r'.Dt COMMAND 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
.Nm command
|
||||
.Nd description
|
||||
.Sh SYNOPSIS
|
||||
.Nm command
|
||||
.Nm command
|
||||
.Ar subcommand
|
||||
.Sh DESCRIPTION
|
||||
description
|
||||
.Pp
|
||||
The subcommands are as follows:
|
||||
.Bl -tag -width indent
|
||||
.It Cm sub Pq Cm s
|
||||
subcommand
|
||||
.El
|
||||
.Sh SEE ALSO
|
||||
.Xr command-sub 1
|
||||
'
|
||||
}
|
||||
|
||||
// helper functions
|
||||
fn empty_func(cmd cli.Command) ! {
|
||||
}
|
||||
@@ -0,0 +1,294 @@
|
||||
module cli
|
||||
|
||||
pub enum FlagType {
|
||||
bool
|
||||
int
|
||||
float
|
||||
string
|
||||
// If flag can set multiple time, use array type
|
||||
int_array
|
||||
float_array
|
||||
string_array
|
||||
}
|
||||
|
||||
// Flag holds information for a command line flag.
|
||||
// (flags are also commonly referred to as "options" or "switches")
|
||||
// These are typically denoted in the shell by a short form `-f` and/or a long form `--flag`
|
||||
pub struct Flag {
|
||||
pub mut:
|
||||
flag FlagType
|
||||
// Name of flag
|
||||
name string
|
||||
// Like short option
|
||||
abbrev string
|
||||
// Description of flag
|
||||
description string
|
||||
// If the flag is added to this command and to all subcommands
|
||||
global bool
|
||||
// If flag is required
|
||||
required bool
|
||||
// Default value if no value provide by command line
|
||||
default_value []string = []
|
||||
mut:
|
||||
// Set true if flag found.
|
||||
found bool
|
||||
// Value of flag
|
||||
value []string = []
|
||||
}
|
||||
|
||||
// get_all_found returns an array of all `Flag`s found in the command parameters.
|
||||
pub fn (flags []Flag) get_all_found() []Flag {
|
||||
return flags.filter(it.found)
|
||||
}
|
||||
|
||||
// get_bool returns `true` if the flag is set.
|
||||
// get_bool returns an error if the `FlagType` is not boolean.
|
||||
pub fn (flag &Flag) get_bool() !bool {
|
||||
if flag.flag != .bool {
|
||||
return error('${flag.name}: Invalid flag type `${flag.flag}`, expected `bool`')
|
||||
}
|
||||
|
||||
val := flag.get_value_or_default_value()
|
||||
|
||||
return val.len > 0 && val[0] == 'true'
|
||||
}
|
||||
|
||||
// get_bool returns `true` if the flag specified in `name` is set.
|
||||
// get_bool returns an error if the `FlagType` is not boolean.
|
||||
pub fn (flags []Flag) get_bool(name string) !bool {
|
||||
flag := flags.get(name)!
|
||||
return flag.get_bool()
|
||||
}
|
||||
|
||||
// get_int returns the `int` value argument of the flag.
|
||||
// get_int returns an error if the `FlagType` is not integer.
|
||||
pub fn (flag &Flag) get_int() !int {
|
||||
if flag.flag != .int {
|
||||
return error('${flag.name}: Invalid flag type `${flag.flag}`, expected `int`')
|
||||
}
|
||||
|
||||
val := flag.get_value_or_default_value()
|
||||
|
||||
if val.len == 0 {
|
||||
return 0
|
||||
} else {
|
||||
return val[0].int()
|
||||
}
|
||||
}
|
||||
|
||||
// get_ints returns the array of `int` value argument of the flag specified in `name`.
|
||||
// get_ints returns an error if the `FlagType` is not integer.
|
||||
pub fn (flag &Flag) get_ints() ![]int {
|
||||
if flag.flag != .int_array {
|
||||
return error('${flag.name}: Invalid flag type `${flag.flag}`, expected `int_array`')
|
||||
}
|
||||
|
||||
val := flag.get_value_or_default_value()
|
||||
|
||||
if val.len == 0 {
|
||||
return []int{}
|
||||
} else {
|
||||
mut values := []int{}
|
||||
|
||||
for f in val {
|
||||
values << f.int()
|
||||
}
|
||||
|
||||
return values
|
||||
}
|
||||
}
|
||||
|
||||
// get_int returns the `int` value argument of the flag specified in `name`.
|
||||
// get_int returns an error if the `FlagType` is not integer.
|
||||
pub fn (flags []Flag) get_int(name string) !int {
|
||||
flag := flags.get(name)!
|
||||
return flag.get_int()
|
||||
}
|
||||
|
||||
// get_ints returns the array of `int` value argument of the flag specified in `name`.
|
||||
// get_ints returns an error if the `FlagType` is not integer.
|
||||
pub fn (flags []Flag) get_ints(name string) ![]int {
|
||||
flag := flags.get(name)!
|
||||
return flag.get_ints()
|
||||
}
|
||||
|
||||
// get_float returns the `f64` value argument of the flag.
|
||||
// get_float returns an error if the `FlagType` is not floating point.
|
||||
pub fn (flag &Flag) get_float() !f64 {
|
||||
if flag.flag != .float {
|
||||
return error('${flag.name}: Invalid flag type `${flag.flag}`, expected `float`')
|
||||
}
|
||||
|
||||
val := flag.get_value_or_default_value()
|
||||
|
||||
if val.len == 0 {
|
||||
return 0.0
|
||||
} else {
|
||||
return val[0].f64()
|
||||
}
|
||||
}
|
||||
|
||||
// get_floats returns the `f64` value argument of the flag.
|
||||
// get_floats returns an error if the `FlagType` is not floating point.
|
||||
pub fn (flag &Flag) get_floats() ![]f64 {
|
||||
if flag.flag != .float_array {
|
||||
return error('${flag.name}: Invalid flag type `${flag.flag}`, expected `float_array`')
|
||||
}
|
||||
|
||||
val := flag.get_value_or_default_value()
|
||||
|
||||
if val.len == 0 {
|
||||
return []f64{}
|
||||
} else {
|
||||
mut values := []f64{}
|
||||
|
||||
for f in val {
|
||||
values << f.f64()
|
||||
}
|
||||
|
||||
return values
|
||||
}
|
||||
}
|
||||
|
||||
// get_float returns the `f64` value argument of the flag specified in `name`.
|
||||
// get_float returns an error if the `FlagType` is not floating point.
|
||||
pub fn (flags []Flag) get_float(name string) !f64 {
|
||||
flag := flags.get(name)!
|
||||
return flag.get_float()
|
||||
}
|
||||
|
||||
// get_floats returns the array of `f64` value argument of the flag specified in `name`.
|
||||
// get_floats returns an error if the `FlagType` is not floating point.
|
||||
pub fn (flags []Flag) get_floats(name string) ![]f64 {
|
||||
flag := flags.get(name)!
|
||||
return flag.get_floats()
|
||||
}
|
||||
|
||||
// get_string returns the `string` value argument of the flag.
|
||||
// get_string returns an error if the `FlagType` is not string.
|
||||
pub fn (flag &Flag) get_string() !string {
|
||||
if flag.flag != .string {
|
||||
return error('${flag.name}: Invalid flag type `${flag.flag}`, expected `string`')
|
||||
}
|
||||
|
||||
val := flag.get_value_or_default_value()
|
||||
|
||||
if val.len == 0 {
|
||||
return ''
|
||||
} else {
|
||||
return val[0]
|
||||
}
|
||||
}
|
||||
|
||||
// get_strings returns the array of `string` value argument of the flag.
|
||||
// get_strings returns an error if the `FlagType` is not string.
|
||||
pub fn (flag &Flag) get_strings() ![]string {
|
||||
if flag.flag != .string_array {
|
||||
return error('${flag.name}: Invalid flag type `${flag.flag}`, expected `string_array`')
|
||||
}
|
||||
|
||||
val := flag.get_value_or_default_value()
|
||||
|
||||
if val.len == 0 {
|
||||
return []string{}
|
||||
} else {
|
||||
return val
|
||||
}
|
||||
}
|
||||
|
||||
// get_string returns the `string` value argument of the flag specified in `name`.
|
||||
// get_string returns an error if the `FlagType` is not string.
|
||||
pub fn (flags []Flag) get_string(name string) !string {
|
||||
flag := flags.get(name)!
|
||||
return flag.get_string()
|
||||
}
|
||||
|
||||
// get_strings returns the `string` value argument of the flag specified in `name`.
|
||||
// get_strings returns an error if the `FlagType` is not string.
|
||||
pub fn (flags []Flag) get_strings(name string) ![]string {
|
||||
flag := flags.get(name)!
|
||||
return flag.get_strings()
|
||||
}
|
||||
|
||||
// parse parses flag values from arguments and return an array of arguments with all consumed elements removed.
|
||||
pub fn (mut flag Flag) parse(args []string, posix_mode bool) ![]string {
|
||||
return match true {
|
||||
!flag.matches(args[0], posix_mode) {
|
||||
args
|
||||
}
|
||||
flag.flag == .bool {
|
||||
flag.parse_bool(args)!
|
||||
}
|
||||
flag.value.len > 0 && flag.flag !in [.int_array, .float_array, .string_array] {
|
||||
error('The argument `${flag.name}` accept only one value!')
|
||||
}
|
||||
else {
|
||||
flag.parse_raw(args)!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// matches returns `true` if first arg in `args` matches this flag.
|
||||
fn (mut flag Flag) matches(arg string, posix_mode bool) bool {
|
||||
prefix := if posix_mode { '--' } else { '-' }
|
||||
return (flag.name != '' && arg == '${prefix}${flag.name}')
|
||||
|| (flag.name != '' && arg.starts_with('${prefix}${flag.name}='))
|
||||
|| (flag.abbrev != '' && arg == '-${flag.abbrev}')
|
||||
|| (flag.abbrev != '' && arg.starts_with('-${flag.abbrev}='))
|
||||
}
|
||||
|
||||
fn (mut flag Flag) parse_raw(args []string) ![]string {
|
||||
if args[0].len > flag.name.len && args[0].contains('=') {
|
||||
flag.value << args[0].split('=')[1]
|
||||
return args[1..]
|
||||
} else if args.len >= 2 {
|
||||
flag.value << args[1]
|
||||
return args[2..]
|
||||
}
|
||||
return error('Missing argument for `${flag.name}`')
|
||||
}
|
||||
|
||||
fn (mut flag Flag) parse_bool(args []string) ![]string {
|
||||
if args[0].len > flag.name.len && args[0].contains('=') {
|
||||
flag.value = [args[0].split('=')[1]]
|
||||
return args[1..]
|
||||
} else if args.len >= 2 {
|
||||
if args[1] in ['true', 'false'] {
|
||||
flag.value = [args[1]]
|
||||
return args[2..]
|
||||
}
|
||||
}
|
||||
// In fact bool cannot be multiple
|
||||
flag.value = ['true']
|
||||
return args[1..]
|
||||
}
|
||||
|
||||
// get returns the `Flag` matching `name` or an error
|
||||
// if it can't be found.
|
||||
fn (flags []Flag) get(name string) !Flag {
|
||||
for flag in flags {
|
||||
if flag.name == name {
|
||||
return flag
|
||||
}
|
||||
}
|
||||
return error('Flag `${name}` not found in ${flags}')
|
||||
}
|
||||
|
||||
fn (flags []Flag) contains(name string) bool {
|
||||
for flag in flags {
|
||||
if flag.name == name || flag.abbrev == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if value is set by command line option. If not, return default value.
|
||||
fn (flag &Flag) get_value_or_default_value() []string {
|
||||
if flag.value.len == 0 && flag.default_value.len > 0 {
|
||||
// If default value is set and no value provide, use default value.
|
||||
return flag.default_value
|
||||
} else {
|
||||
return flag.value
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
import cli
|
||||
|
||||
fn test_if_string_flag_parses() {
|
||||
mut flag := cli.Flag{
|
||||
flag: .string
|
||||
name: 'flag'
|
||||
}
|
||||
flag.parse(['-flag', 'value1'], false) or { panic(err) }
|
||||
mut value := flag.get_string() or { panic(err) }
|
||||
assert value == 'value1'
|
||||
|
||||
flag = cli.Flag{
|
||||
flag: .string
|
||||
name: 'flag'
|
||||
}
|
||||
flag.parse(['-flag=value2'], false) or { panic(err) }
|
||||
value = flag.get_string() or { panic(err) }
|
||||
assert value == 'value2'
|
||||
|
||||
flag = cli.Flag{
|
||||
flag: .string_array
|
||||
name: 'flag'
|
||||
}
|
||||
flag.parse(['-flag=value1'], false) or { panic(err) }
|
||||
flag.parse(['-flag=value2'], false) or { panic(err) }
|
||||
mut values := flag.get_strings() or { panic(err) }
|
||||
assert values == ['value1', 'value2']
|
||||
}
|
||||
|
||||
fn test_if_bool_flag_parses() {
|
||||
mut flag := cli.Flag{
|
||||
flag: .bool
|
||||
name: 'flag'
|
||||
}
|
||||
mut value := false
|
||||
flag.parse(['-flag'], false) or { panic(err) }
|
||||
value = flag.get_bool() or { panic(err) }
|
||||
assert value == true
|
||||
flag.parse(['-flag', 'false'], false) or { panic(err) }
|
||||
value = flag.get_bool() or { panic(err) }
|
||||
assert value == false
|
||||
flag.parse(['-flag', 'true'], false) or { panic(err) }
|
||||
value = flag.get_bool() or { panic(err) }
|
||||
assert value == true
|
||||
flag.parse(['-flag=false'], false) or { panic(err) }
|
||||
value = flag.get_bool() or { panic(err) }
|
||||
assert value == false
|
||||
flag.parse(['-flag=true'], false) or { panic(err) }
|
||||
value = flag.get_bool() or { panic(err) }
|
||||
assert value == true
|
||||
}
|
||||
|
||||
fn test_if_int_flag_parses() {
|
||||
mut flag := cli.Flag{
|
||||
flag: .int
|
||||
name: 'flag'
|
||||
}
|
||||
|
||||
mut value := 0
|
||||
flag.parse(['-flag', '42'], false) or { panic(err) }
|
||||
value = flag.get_int() or { panic(err) }
|
||||
assert value == 42
|
||||
|
||||
flag = cli.Flag{
|
||||
flag: .int
|
||||
name: 'flag'
|
||||
}
|
||||
|
||||
flag.parse(['-flag=45'], false) or { panic(err) }
|
||||
value = flag.get_int() or { panic(err) }
|
||||
assert value == 45
|
||||
|
||||
flag = cli.Flag{
|
||||
flag: .int_array
|
||||
name: 'flag'
|
||||
}
|
||||
|
||||
flag.parse(['-flag=42'], false) or { panic(err) }
|
||||
flag.parse(['-flag=45'], false) or { panic(err) }
|
||||
mut values := flag.get_ints() or { panic(err) }
|
||||
assert values == [42, 45]
|
||||
}
|
||||
|
||||
fn test_if_float_flag_parses() {
|
||||
mut flag := cli.Flag{
|
||||
flag: .float
|
||||
name: 'flag'
|
||||
}
|
||||
mut value := f64(0)
|
||||
flag.parse(['-flag', '3.14158'], false) or { panic(err) }
|
||||
value = flag.get_float() or { panic(err) }
|
||||
assert value == 3.14158
|
||||
|
||||
flag = cli.Flag{
|
||||
flag: .float
|
||||
name: 'flag'
|
||||
}
|
||||
|
||||
flag.parse(['-flag=3.14159'], false) or { panic(err) }
|
||||
value = flag.get_float() or { panic(err) }
|
||||
assert value == 3.14159
|
||||
|
||||
flag = cli.Flag{
|
||||
flag: .float_array
|
||||
name: 'flag'
|
||||
}
|
||||
|
||||
flag.parse(['-flag=3.1'], false) or { panic(err) }
|
||||
flag.parse(['-flag=1.3'], false) or { panic(err) }
|
||||
mut values := flag.get_floats() or { panic(err) }
|
||||
assert values == [3.1, 1.3]
|
||||
}
|
||||
|
||||
fn test_if_flag_parses_with_abbrev() {
|
||||
mut flag := cli.Flag{
|
||||
flag: .bool
|
||||
name: 'flag'
|
||||
abbrev: 'f'
|
||||
}
|
||||
mut value := false
|
||||
flag.parse(['--flag'], true) or { panic(err) }
|
||||
value = flag.get_bool() or { panic(err) }
|
||||
assert value == true
|
||||
|
||||
value = false
|
||||
flag = cli.Flag{
|
||||
flag: .bool
|
||||
name: 'flag'
|
||||
abbrev: 'f'
|
||||
}
|
||||
flag.parse(['-f'], true) or { panic(err) }
|
||||
value = flag.get_bool() or { panic(err) }
|
||||
assert value == true
|
||||
}
|
||||
|
||||
fn test_if_multiple_value_on_single_value() {
|
||||
mut flag := cli.Flag{
|
||||
flag: .float
|
||||
name: 'flag'
|
||||
}
|
||||
|
||||
flag.parse(['-flag', '3.14158'], false) or { panic(err) }
|
||||
|
||||
if _ := flag.parse(['-flag', '3.222'], false) {
|
||||
panic("No multiple value flag don't raise an error!")
|
||||
} else {
|
||||
assert true
|
||||
}
|
||||
}
|
||||
|
||||
fn test_default_value() {
|
||||
mut flag := cli.Flag{
|
||||
flag: .float
|
||||
name: 'flag'
|
||||
default_value: ['1.234']
|
||||
}
|
||||
|
||||
flag.parse(['-flag', '3.14158'], false) or { panic(err) }
|
||||
mut value := flag.get_float() or { panic(err) }
|
||||
assert value == 3.14158
|
||||
|
||||
flag = cli.Flag{
|
||||
flag: .float
|
||||
name: 'flag'
|
||||
default_value: ['1.234']
|
||||
}
|
||||
|
||||
flag.parse([''], false) or { panic(err) }
|
||||
value = flag.get_float() or { panic(err) }
|
||||
assert value == 1.234
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
module cli
|
||||
|
||||
import term
|
||||
import strings
|
||||
|
||||
const base_indent_len = 2
|
||||
const min_description_indent_len = 20
|
||||
const spacing = 2
|
||||
|
||||
fn help_flag(with_abbrev bool) Flag {
|
||||
sabbrev := if with_abbrev { 'h' } else { '' }
|
||||
return Flag{
|
||||
flag: .bool
|
||||
name: 'help'
|
||||
abbrev: sabbrev
|
||||
description: 'Print help information'
|
||||
}
|
||||
}
|
||||
|
||||
fn help_cmd() Command {
|
||||
return Command{
|
||||
name: 'help'
|
||||
usage: '<command>'
|
||||
description: 'Print help information'
|
||||
execute: print_help_for_command
|
||||
}
|
||||
}
|
||||
|
||||
// print_help_for_command outputs the help message of `help_cmd`.
|
||||
pub fn print_help_for_command(cmd Command) ! {
|
||||
if cmd.args.len > 0 {
|
||||
for sub_cmd in cmd.commands {
|
||||
if sub_cmd.matches(cmd.args[0]) {
|
||||
cmd_ := unsafe { &sub_cmd }
|
||||
print(cmd_.help_message())
|
||||
return
|
||||
}
|
||||
}
|
||||
print('Invalid command: ${cmd.args.join(' ')}')
|
||||
} else if cmd.parent != unsafe { nil } {
|
||||
print(cmd.parent.help_message())
|
||||
}
|
||||
}
|
||||
|
||||
// help_message returns a generated help message as a `string` for the `Command`.
|
||||
//
|
||||
// The output is composed of (each section is omitted when empty):
|
||||
//
|
||||
// 1. `Usage:` line
|
||||
// 2. The description, if any
|
||||
// 3. `Flags:` — flags defined on this command
|
||||
// 4. `Inherited flags:` — flags propagated from any ancestor via `Flag.global`
|
||||
// 5. One section per sub-command group; the group name is rendered as the
|
||||
// user wrote it followed by `:`. Sub-commands without a group go under
|
||||
// the default `Commands:` section.
|
||||
// 6. `Examples:` — `Command.examples` entries, one per line
|
||||
// 7. `Learn more:` — the multi-line `Command.learn_more` field
|
||||
pub fn (cmd &Command) help_message() string {
|
||||
mut help := ''
|
||||
help += 'Usage: ${cmd.full_name()}'
|
||||
if cmd.flags.len > 0 {
|
||||
help += ' [flags]'
|
||||
}
|
||||
if cmd.commands.len > 0 {
|
||||
help += ' [commands]'
|
||||
}
|
||||
if cmd.usage.len > 0 {
|
||||
help += ' ${cmd.usage}'
|
||||
} else {
|
||||
for i in 0 .. cmd.required_args {
|
||||
help += ' <arg${i}>'
|
||||
}
|
||||
}
|
||||
help += '\n'
|
||||
if cmd.description != '' {
|
||||
help += '\n${cmd.description}\n'
|
||||
}
|
||||
abbrev_len, name_len := cmd.help_columns()
|
||||
local_flags, inherited_flags := cmd.partition_flags()
|
||||
if local_flags.len > 0 {
|
||||
help += render_flag_section('Flags', local_flags, abbrev_len, name_len, cmd.posix_mode)
|
||||
}
|
||||
if inherited_flags.len > 0 {
|
||||
help += render_flag_section('Inherited flags', inherited_flags, abbrev_len, name_len,
|
||||
cmd.posix_mode)
|
||||
}
|
||||
if cmd.commands.len > 0 {
|
||||
groups, order := group_commands(cmd.commands)
|
||||
for idx, key in order {
|
||||
title := if key == '' { 'Commands' } else { key }
|
||||
help += render_command_section(title, groups[idx], name_len)
|
||||
}
|
||||
}
|
||||
if cmd.examples.len > 0 {
|
||||
help += '\nExamples:\n'
|
||||
base_indent := ' '.repeat(base_indent_len)
|
||||
for line in cmd.examples {
|
||||
help += '${base_indent}${line}\n'
|
||||
}
|
||||
}
|
||||
if cmd.learn_more != '' {
|
||||
help += '\nLearn more:\n'
|
||||
base_indent := ' '.repeat(base_indent_len)
|
||||
for line in cmd.learn_more.split_into_lines() {
|
||||
help += '${base_indent}${line}\n'
|
||||
}
|
||||
}
|
||||
return help
|
||||
}
|
||||
|
||||
// help_columns computes the abbreviation column width and the name column
|
||||
// width used by `Flags:`, `Inherited Flags:` and command sections so all
|
||||
// rows align vertically.
|
||||
fn (cmd &Command) help_columns() (int, int) {
|
||||
mut abbrev_len := 0
|
||||
mut name_len := min_description_indent_len
|
||||
if cmd.posix_mode {
|
||||
for flag in cmd.flags {
|
||||
if flag.abbrev != '' {
|
||||
abbrev_len = max(abbrev_len, flag.abbrev.len + spacing + 1) // + 1 for '-' in front
|
||||
}
|
||||
name_len =
|
||||
max(name_len, abbrev_len + flag.name.len + spacing + 2) // + 2 for '--' in front
|
||||
}
|
||||
} else {
|
||||
for flag in cmd.flags {
|
||||
if flag.abbrev != '' {
|
||||
abbrev_len = max(abbrev_len, flag.abbrev.len + spacing + 1) // + 1 for '-' in front
|
||||
}
|
||||
name_len =
|
||||
max(name_len, abbrev_len + flag.name.len + spacing + 1) // + 1 for '-' in front
|
||||
}
|
||||
}
|
||||
for command in cmd.commands {
|
||||
name_len = max(name_len, command.display_name().len + spacing)
|
||||
}
|
||||
return abbrev_len, name_len
|
||||
}
|
||||
|
||||
// partition_flags splits this command's `flags` into two arrays preserving
|
||||
// declaration order: locally-defined flags first, then flags inherited from
|
||||
// any ancestor via `Flag.global`.
|
||||
fn (cmd &Command) partition_flags() ([]Flag, []Flag) {
|
||||
inherited_names := cmd.inherited_global_flag_names()
|
||||
mut local := []Flag{}
|
||||
mut inherited := []Flag{}
|
||||
for flag in cmd.flags {
|
||||
if flag.name in inherited_names {
|
||||
inherited << flag
|
||||
} else {
|
||||
local << flag
|
||||
}
|
||||
}
|
||||
return local, inherited
|
||||
}
|
||||
|
||||
// inherited_global_flag_names returns the names of flags that any ancestor
|
||||
// declared with `global = true`. A flag is "inherited" only when an ancestor
|
||||
// declared it global, never when this command itself defines it.
|
||||
fn (cmd &Command) inherited_global_flag_names() []string {
|
||||
mut names := []string{}
|
||||
mut walker := cmd.parent
|
||||
for unsafe { walker != nil } {
|
||||
for flag in walker.flags {
|
||||
if flag.global && flag.name !in names {
|
||||
names << flag.name
|
||||
}
|
||||
}
|
||||
walker = walker.parent
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
// group_commands buckets `commands` by their `group` field, preserving the
|
||||
// declaration order of both groups and members.
|
||||
fn group_commands(commands []Command) ([][]Command, []string) {
|
||||
mut order := []string{}
|
||||
mut groups := [][]Command{}
|
||||
for sub in commands {
|
||||
key := sub.group
|
||||
idx := order.index(key)
|
||||
if idx == -1 {
|
||||
order << key
|
||||
groups << [sub]
|
||||
} else {
|
||||
groups[idx] << sub
|
||||
}
|
||||
}
|
||||
return groups, order
|
||||
}
|
||||
|
||||
fn render_flag_section(title string, flags []Flag, abbrev_len int, name_len int, posix_mode bool) string {
|
||||
mut out := '\n${title}:\n'
|
||||
prefix := if posix_mode { '--' } else { '-' }
|
||||
base_indent := ' '.repeat(base_indent_len)
|
||||
for flag in flags {
|
||||
mut flag_name := ''
|
||||
if flag.abbrev != '' {
|
||||
abbrev_indent := ' '.repeat(abbrev_len - flag.abbrev.len - 1) // - 1 for '-' in front
|
||||
flag_name = '-${flag.abbrev}${abbrev_indent}${prefix}${flag.name}'
|
||||
} else {
|
||||
abbrev_indent := ' '.repeat(abbrev_len)
|
||||
flag_name = '${abbrev_indent}${prefix}${flag.name}'
|
||||
}
|
||||
mut required := ''
|
||||
if flag.required {
|
||||
required = ' (required)'
|
||||
}
|
||||
description_indent := ' '.repeat(name_len - flag_name.len)
|
||||
out += '${base_indent}${flag_name}${description_indent}' +
|
||||
pretty_description(flag.description + required, base_indent_len + name_len) + '\n'
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
fn render_command_section(title string, commands []Command, name_len int) string {
|
||||
mut out := '\n${title}:\n'
|
||||
base_indent := ' '.repeat(base_indent_len)
|
||||
for command in commands {
|
||||
command_name := command.display_name()
|
||||
description_indent := ' '.repeat(name_len - command_name.len)
|
||||
out += '${base_indent}${command_name}${description_indent}' +
|
||||
pretty_description(command.description, base_indent_len + name_len) + '\n'
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// pretty_description resizes description text depending on terminal width.
|
||||
// Essentially, smart wrap-around
|
||||
fn pretty_description(s string, indent_len int) string {
|
||||
width, _ := term.get_terminal_size()
|
||||
// Don't prettify if the terminal is that small, it won't be pretty anyway.
|
||||
if indent_len > width {
|
||||
return s
|
||||
}
|
||||
indent := ' '.repeat(indent_len)
|
||||
chars_per_line := width - indent_len
|
||||
// Give us enough room, better a little bigger than smaller
|
||||
mut acc := strings.new_builder(((s.len / chars_per_line) + 1) * (width + 1))
|
||||
for k, line in s.split('\n') {
|
||||
if k != 0 {
|
||||
acc.write_string('\n${indent}')
|
||||
}
|
||||
mut i := chars_per_line - 2
|
||||
mut j := 0
|
||||
for ; i < line.len; i += chars_per_line - 2 {
|
||||
for j > 0 && line[j] != ` ` {
|
||||
j--
|
||||
}
|
||||
// indent was already done the first iteration
|
||||
if j != 0 {
|
||||
acc.write_string(indent)
|
||||
}
|
||||
acc.writeln(line[j..i].trim_space())
|
||||
j = i
|
||||
}
|
||||
// We need this even though it should never happen
|
||||
if j != 0 {
|
||||
acc.write_string(indent)
|
||||
}
|
||||
acc.write_string(line[j..].trim_space())
|
||||
}
|
||||
return acc.str()
|
||||
}
|
||||
|
||||
fn max(a int, b int) int {
|
||||
res := if a > b { a } else { b }
|
||||
return res
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
module cli
|
||||
|
||||
fn test_help_message() {
|
||||
mut cmd := Command{
|
||||
name: 'command'
|
||||
description: 'description'
|
||||
commands: [
|
||||
Command{
|
||||
name: 'sub'
|
||||
description: 'subcommand'
|
||||
},
|
||||
Command{
|
||||
name: 'sub2'
|
||||
description: 'another subcommand'
|
||||
},
|
||||
]
|
||||
flags: [
|
||||
Flag{
|
||||
flag: .string
|
||||
name: 'str'
|
||||
description: 'str flag'
|
||||
},
|
||||
Flag{
|
||||
flag: .bool
|
||||
name: 'bool'
|
||||
description: 'bool flag'
|
||||
abbrev: 'b'
|
||||
},
|
||||
Flag{
|
||||
flag: .string
|
||||
name: 'required'
|
||||
abbrev: 'r'
|
||||
required: true
|
||||
},
|
||||
]
|
||||
}
|
||||
assert cmd.help_message() == r'Usage: command [flags] [commands]
|
||||
|
||||
description
|
||||
|
||||
Flags:
|
||||
-str str flag
|
||||
-b -bool bool flag
|
||||
-r -required (required)
|
||||
|
||||
Commands:
|
||||
sub subcommand
|
||||
sub2 another subcommand
|
||||
'
|
||||
|
||||
cmd.posix_mode = true
|
||||
assert cmd.help_message() == r'Usage: command [flags] [commands]
|
||||
|
||||
description
|
||||
|
||||
Flags:
|
||||
--str str flag
|
||||
-b --bool bool flag
|
||||
-r --required (required)
|
||||
|
||||
Commands:
|
||||
sub subcommand
|
||||
sub2 another subcommand
|
||||
'
|
||||
}
|
||||
|
||||
fn test_help_message_groups_commands_by_group_field() {
|
||||
cmd := Command{
|
||||
name: 'app'
|
||||
description: 'app'
|
||||
commands: [
|
||||
Command{
|
||||
name: 'auth'
|
||||
description: 'Authenticate'
|
||||
group: 'Core commands'
|
||||
},
|
||||
Command{
|
||||
name: 'repo'
|
||||
description: 'Work with repositories'
|
||||
group: 'Core commands'
|
||||
},
|
||||
Command{
|
||||
name: 'alias'
|
||||
description: 'Define shortcuts'
|
||||
group: 'General commands'
|
||||
},
|
||||
Command{
|
||||
name: 'misc'
|
||||
description: 'Other things'
|
||||
},
|
||||
]
|
||||
}
|
||||
assert cmd.help_message() == r'Usage: app [commands]
|
||||
|
||||
app
|
||||
|
||||
Core commands:
|
||||
auth Authenticate
|
||||
repo Work with repositories
|
||||
|
||||
General commands:
|
||||
alias Define shortcuts
|
||||
|
||||
Commands:
|
||||
misc Other things
|
||||
'
|
||||
}
|
||||
|
||||
fn test_help_message_renders_group_title_verbatim() {
|
||||
// Group string is rendered as-is; capitalisation is the caller's
|
||||
// choice. Non-ASCII first characters must work without mojibake.
|
||||
cmd := Command{
|
||||
name: 'app'
|
||||
commands: [
|
||||
Command{
|
||||
name: 'edit'
|
||||
description: 'Edit something'
|
||||
group: 'éditeurs'
|
||||
},
|
||||
]
|
||||
}
|
||||
assert cmd.help_message() == r'Usage: app [commands]
|
||||
|
||||
éditeurs:
|
||||
edit Edit something
|
||||
'
|
||||
}
|
||||
|
||||
fn test_help_message_separates_inherited_flags_from_locals() {
|
||||
mut root := Command{
|
||||
name: 'app'
|
||||
flags: [
|
||||
Flag{
|
||||
flag: .string
|
||||
name: 'config'
|
||||
abbrev: 'c'
|
||||
description: 'Path to config'
|
||||
global: true
|
||||
},
|
||||
]
|
||||
commands: [
|
||||
Command{
|
||||
name: 'run'
|
||||
description: 'Run something'
|
||||
flags: [
|
||||
Flag{
|
||||
flag: .bool
|
||||
name: 'verbose'
|
||||
abbrev: 'v'
|
||||
description: 'Verbose output'
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
}
|
||||
root.setup()
|
||||
mut sub := root.commands[0]
|
||||
sub.parent = unsafe { &root }
|
||||
// Mirror what `parse_commands` does when a child is dispatched.
|
||||
sub.flags << root.flags[0]
|
||||
assert sub.help_message() == r'Usage: app run [flags]
|
||||
|
||||
Run something
|
||||
|
||||
Flags:
|
||||
-v -verbose Verbose output
|
||||
|
||||
Inherited flags:
|
||||
-c -config Path to config
|
||||
'
|
||||
}
|
||||
|
||||
fn test_help_message_renders_examples_and_learn_more() {
|
||||
cmd := Command{
|
||||
name: 'app'
|
||||
description: 'app'
|
||||
examples: ['\$ app run', '\$ app help run']
|
||||
learn_more: 'Use `app help <command>` for details about a command.\nDocumentation lives at https://example.test'
|
||||
}
|
||||
assert cmd.help_message() == r'Usage: app
|
||||
|
||||
app
|
||||
|
||||
Examples:
|
||||
$ app run
|
||||
$ app help run
|
||||
|
||||
Learn more:
|
||||
Use `app help <command>` for details about a command.
|
||||
Documentation lives at https://example.test
|
||||
'
|
||||
}
|
||||
|
||||
fn test_help_message_omits_empty_optional_sections() {
|
||||
cmd := Command{
|
||||
name: 'app'
|
||||
}
|
||||
// No description, no flags, no commands, no examples, no learn_more.
|
||||
assert cmd.help_message() == 'Usage: app\n'
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
module cli
|
||||
|
||||
import time
|
||||
|
||||
fn man_flag() Flag {
|
||||
return Flag{
|
||||
flag: .bool
|
||||
name: 'man'
|
||||
description: 'Print the auto-generated manpage'
|
||||
}
|
||||
}
|
||||
|
||||
fn man_cmd() Command {
|
||||
return Command{
|
||||
name: 'man'
|
||||
usage: '<subcommand>'
|
||||
description: 'Print the auto-generated manpage'
|
||||
execute: print_manpage_for_command
|
||||
}
|
||||
}
|
||||
|
||||
// print_manpage_for_command prints the manpage for the command or subcommand in `man_cmd` to stdout.
|
||||
pub fn print_manpage_for_command(cmd Command) ! {
|
||||
if cmd.args.len > 0 {
|
||||
for sub_cmd in cmd.commands {
|
||||
if sub_cmd.matches(cmd.args[0]) {
|
||||
target := unsafe { &sub_cmd }
|
||||
print(target.manpage())
|
||||
return
|
||||
}
|
||||
}
|
||||
print('Invalid command: ${cmd.args.join(' ')}')
|
||||
} else if cmd.parent != unsafe { nil } {
|
||||
print(cmd.parent.manpage())
|
||||
}
|
||||
}
|
||||
|
||||
// manpage returns a `string` containing the mdoc(7) manpage for this `Command`.
|
||||
pub fn (cmd &Command) manpage() string {
|
||||
mut mdoc := '.Dd ${time.now().strftime('%B %d, %Y')}\n'
|
||||
mdoc += '.Dt ${cmd.full_name().replace(' ', '-').to_upper()} 1\n'
|
||||
mdoc += '.Os\n.Sh NAME\n.Nm ${cmd.full_name().replace(' ', '-')}\n.Nd ${cmd.description}\n'
|
||||
mdoc += '.Sh SYNOPSIS\n'
|
||||
mdoc += '.Nm ${cmd.root().name}\n'
|
||||
if unsafe { cmd.parent != 0 } {
|
||||
mut parents := []Command{}
|
||||
if !cmd.parent.is_root() {
|
||||
parents.prepend(cmd.parent)
|
||||
for {
|
||||
p := parents[0]
|
||||
if p.parent.is_root() {
|
||||
break
|
||||
} else {
|
||||
parents.prepend(p.parent)
|
||||
}
|
||||
}
|
||||
for c in parents {
|
||||
mdoc += '.Ar ${c.name}\n'
|
||||
}
|
||||
}
|
||||
mdoc += '.Ar ${cmd.name}\n'
|
||||
}
|
||||
for flag in cmd.flags {
|
||||
mdoc += '.Op'
|
||||
if flag.abbrev != '' {
|
||||
mdoc += ' Fl ${flag.abbrev}'
|
||||
} else {
|
||||
if cmd.posix_mode {
|
||||
mdoc += ' Fl -${flag.name}'
|
||||
} else {
|
||||
mdoc += ' Fl ${flag.name}'
|
||||
}
|
||||
}
|
||||
match flag.flag {
|
||||
.int, .float, .int_array, .float_array { mdoc += ' Ar num' }
|
||||
.string, .string_array { mdoc += ' Ar string' }
|
||||
else {}
|
||||
}
|
||||
|
||||
mdoc += '\n'
|
||||
}
|
||||
for i in 0 .. cmd.required_args {
|
||||
mdoc += '.Ar arg${i}\n'
|
||||
}
|
||||
if cmd.commands.len > 0 {
|
||||
mdoc += '.Nm ${cmd.root().name}\n'
|
||||
if unsafe { cmd.parent != 0 } {
|
||||
mut parents := []Command{}
|
||||
if !cmd.parent.is_root() {
|
||||
parents.prepend(cmd.parent)
|
||||
for {
|
||||
p := parents[0]
|
||||
if p.parent.is_root() {
|
||||
break
|
||||
} else {
|
||||
parents.prepend(p.parent)
|
||||
}
|
||||
}
|
||||
for c in parents {
|
||||
mdoc += '.Ar ${c.name}\n'
|
||||
}
|
||||
}
|
||||
mdoc += '.Ar ${cmd.name}\n'
|
||||
}
|
||||
mdoc += '.Ar subcommand\n'
|
||||
}
|
||||
|
||||
mdoc += '.Sh DESCRIPTION\n'
|
||||
if cmd.man_description != '' {
|
||||
mdoc += '${cmd.man_description}\n'
|
||||
} else if cmd.description != '' {
|
||||
mdoc += '${cmd.description}\n'
|
||||
}
|
||||
if cmd.flags.len > 0 {
|
||||
mdoc += '.Pp\nThe options are as follows:\n'
|
||||
mdoc += '.Bl -tag -width indent\n'
|
||||
for flag in cmd.flags {
|
||||
mdoc += '.It'
|
||||
if flag.abbrev != '' {
|
||||
mdoc += ' Fl ${flag.abbrev}'
|
||||
}
|
||||
if cmd.posix_mode {
|
||||
mdoc += ' Fl -${flag.name}'
|
||||
} else {
|
||||
mdoc += ' Fl ${flag.name}'
|
||||
}
|
||||
mdoc += '\n'
|
||||
if flag.description != '' {
|
||||
mdoc += '${flag.description}\n'
|
||||
}
|
||||
}
|
||||
mdoc += '.El\n'
|
||||
}
|
||||
if cmd.commands.len > 0 {
|
||||
mdoc += '.Pp\nThe subcommands are as follows:\n'
|
||||
mdoc += '.Bl -tag -width indent\n'
|
||||
for c in cmd.commands {
|
||||
mdoc += '.It Cm ${c.name}'
|
||||
if c.alias != '' {
|
||||
mdoc += ' Pq Cm ${c.alias}'
|
||||
}
|
||||
mdoc += '\n'
|
||||
if c.description != '' {
|
||||
mdoc += '${c.description}\n'
|
||||
}
|
||||
}
|
||||
mdoc += '.El\n'
|
||||
}
|
||||
|
||||
if cmd.commands.len > 0 {
|
||||
mdoc += '.Sh SEE ALSO\n'
|
||||
mut cmds := []string{}
|
||||
if unsafe { cmd.parent != 0 } {
|
||||
cmds << cmd.parent.full_name().replace(' ', '-')
|
||||
}
|
||||
for c in cmd.commands {
|
||||
cmds << c.full_name().replace(' ', '-')
|
||||
}
|
||||
cmds.sort()
|
||||
mut i := 1
|
||||
for c in cmds {
|
||||
mdoc += '.Xr ${c} 1'
|
||||
if i == cmds.len {
|
||||
mdoc += '\n'
|
||||
} else {
|
||||
mdoc += ' ,\n'
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
return mdoc
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
module cli
|
||||
|
||||
fn test_manpage() {
|
||||
mut cmd := Command{
|
||||
name: 'command'
|
||||
description: 'description'
|
||||
commands: [
|
||||
Command{
|
||||
name: 'sub'
|
||||
description: 'subcommand'
|
||||
},
|
||||
Command{
|
||||
name: 'sub2'
|
||||
description: 'another subcommand'
|
||||
},
|
||||
]
|
||||
flags: [
|
||||
Flag{
|
||||
flag: .string
|
||||
name: 'str'
|
||||
description: 'str flag'
|
||||
},
|
||||
Flag{
|
||||
flag: .bool
|
||||
name: 'bool'
|
||||
description: 'bool flag'
|
||||
abbrev: 'b'
|
||||
},
|
||||
Flag{
|
||||
flag: .string
|
||||
name: 'required'
|
||||
abbrev: 'r'
|
||||
required: true
|
||||
},
|
||||
]
|
||||
}
|
||||
cmd.setup()
|
||||
assert cmd.manpage().after_char(`\n`) == r'.Dt COMMAND 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
.Nm command
|
||||
.Nd description
|
||||
.Sh SYNOPSIS
|
||||
.Nm command
|
||||
.Op Fl str Ar string
|
||||
.Op Fl b
|
||||
.Op Fl r Ar string
|
||||
.Nm command
|
||||
.Ar subcommand
|
||||
.Sh DESCRIPTION
|
||||
description
|
||||
.Pp
|
||||
The options are as follows:
|
||||
.Bl -tag -width indent
|
||||
.It Fl str
|
||||
str flag
|
||||
.It Fl b Fl bool
|
||||
bool flag
|
||||
.It Fl r Fl required
|
||||
.El
|
||||
.Pp
|
||||
The subcommands are as follows:
|
||||
.Bl -tag -width indent
|
||||
.It Cm sub
|
||||
subcommand
|
||||
.It Cm sub2
|
||||
another subcommand
|
||||
.El
|
||||
.Sh SEE ALSO
|
||||
.Xr command-sub 1 ,
|
||||
.Xr command-sub2 1
|
||||
'
|
||||
|
||||
cmd.posix_mode = true
|
||||
assert cmd.manpage().after_char(`\n`) == r'.Dt COMMAND 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
.Nm command
|
||||
.Nd description
|
||||
.Sh SYNOPSIS
|
||||
.Nm command
|
||||
.Op Fl -str Ar string
|
||||
.Op Fl b
|
||||
.Op Fl r Ar string
|
||||
.Nm command
|
||||
.Ar subcommand
|
||||
.Sh DESCRIPTION
|
||||
description
|
||||
.Pp
|
||||
The options are as follows:
|
||||
.Bl -tag -width indent
|
||||
.It Fl -str
|
||||
str flag
|
||||
.It Fl b Fl -bool
|
||||
bool flag
|
||||
.It Fl r Fl -required
|
||||
.El
|
||||
.Pp
|
||||
The subcommands are as follows:
|
||||
.Bl -tag -width indent
|
||||
.It Cm sub
|
||||
subcommand
|
||||
.It Cm sub2
|
||||
another subcommand
|
||||
.El
|
||||
.Sh SEE ALSO
|
||||
.Xr command-sub 1 ,
|
||||
.Xr command-sub2 1
|
||||
'
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
Usage: [flags] [commands]
|
||||
|
||||
Flags:
|
||||
-version Print version information
|
||||
|
||||
Commands:
|
||||
help Print help information
|
||||
@@ -0,0 +1,13 @@
|
||||
import cli { Command, CommandFlag }
|
||||
|
||||
fn main() {
|
||||
mut cmd := Command{
|
||||
version: '0.1.0'
|
||||
defaults: struct {
|
||||
help: CommandFlag{true, false}
|
||||
version: CommandFlag{false, true}
|
||||
man: false
|
||||
}
|
||||
}
|
||||
cmd.parse([''])
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
Command `foo` has no flag `-man`
|
||||
@@ -0,0 +1,11 @@
|
||||
import cli { Command, CommandFlag }
|
||||
|
||||
fn main() {
|
||||
mut cmd := Command{
|
||||
name: 'foo'
|
||||
defaults: struct {
|
||||
man: CommandFlag{true, false}
|
||||
}
|
||||
}
|
||||
cmd.parse(['foo', '-man'])
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
Usage: [flags] [commands]
|
||||
|
||||
Flags:
|
||||
-help Print help information
|
||||
|
||||
Commands:
|
||||
help Print help information
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import cli { Command }
|
||||
import os
|
||||
|
||||
fn main() {
|
||||
mut cmd := Command{}
|
||||
cmd.defaults = struct {
|
||||
man: false
|
||||
}
|
||||
cmd.parse(os.args)
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
Usage: foo [flags] [commands]
|
||||
|
||||
Flags:
|
||||
-help Print help information
|
||||
-man Print the auto-generated manpage
|
||||
|
||||
Commands:
|
||||
man Print the auto-generated manpage
|
||||
@@ -0,0 +1,11 @@
|
||||
import cli { Command, CommandFlag }
|
||||
|
||||
fn main() {
|
||||
mut cmd := Command{
|
||||
name: 'foo'
|
||||
defaults: struct {
|
||||
help: CommandFlag{false, true}
|
||||
}
|
||||
}
|
||||
cmd.parse(['foo', '-help'])
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
foo version 0.1.0
|
||||
@@ -0,0 +1,13 @@
|
||||
import cli { Command, CommandFlag }
|
||||
|
||||
fn main() {
|
||||
mut cmd := Command{
|
||||
name: 'foo'
|
||||
version: '0.1.0'
|
||||
posix_mode: true
|
||||
defaults: struct {
|
||||
version: CommandFlag{false, true}
|
||||
}
|
||||
}
|
||||
cmd.parse(['foo', '--version'])
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
Usage: cli greet [flags] <name>
|
||||
|
||||
Prints greeting in different languages.
|
||||
|
||||
Flags:
|
||||
-fun '{"uri":"mqtt://broker.emqx.io:1883","topic":"test_emq/1
|
||||
'{"uri":"mqtt://broker.emqx.io:1883","topic":"test_emq/1","filters":[{"producer_id":0,"trace_group":2,"string_id
|
||||
":3001},{"string_id":3002}]}'
|
||||
-help Print help information
|
||||
-man Print the auto-generated manpage
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import cli { Command, Flag }
|
||||
|
||||
mut cmd := Command{
|
||||
name: 'cli'
|
||||
description: 'An example of the cli library.'
|
||||
version: '1.0.0'
|
||||
}
|
||||
mut greet_cmd := Command{
|
||||
name: 'greet'
|
||||
description: 'Prints greeting in different languages.'
|
||||
usage: '<name>'
|
||||
required_args: 1
|
||||
}
|
||||
greet_cmd.add_flag(Flag{
|
||||
flag: .string_array
|
||||
name: 'fun'
|
||||
description: '\'{"uri":"mqtt://broker.emqx.io:1883","topic":"test_emq/1","filters":[{"producer_id":0,"trace_group":2,"string_id":3001},{"string_id":3002}]}\''
|
||||
})
|
||||
cmd.add_command(greet_cmd)
|
||||
cmd.setup()
|
||||
cmd.parse(['cli', 'greet', '-help'])
|
||||
Whitespace-only changes.
+11
@@ -0,0 +1,11 @@
|
||||
import cli { Command }
|
||||
|
||||
fn main() {
|
||||
mut cmd := Command{
|
||||
name: 'cmd'
|
||||
}
|
||||
cmd.add_command(
|
||||
name: 'foo'
|
||||
)
|
||||
cmd.parse(['', 'cmd', 'foo'])
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
module cli
|
||||
|
||||
fn version_flag(with_abbrev bool) Flag {
|
||||
sabbrev := if with_abbrev { 'v' } else { '' }
|
||||
return Flag{
|
||||
flag: .bool
|
||||
name: 'version'
|
||||
abbrev: sabbrev
|
||||
description: 'Print version information'
|
||||
}
|
||||
}
|
||||
|
||||
fn version_cmd() Command {
|
||||
return Command{
|
||||
name: 'version'
|
||||
description: 'Print version information'
|
||||
execute: print_version_for_command
|
||||
}
|
||||
}
|
||||
|
||||
fn print_version_for_command(cmd Command) ! {
|
||||
if cmd.args.len > 0 {
|
||||
for sub_cmd in cmd.commands {
|
||||
if sub_cmd.name == cmd.args[0] {
|
||||
target := unsafe { &sub_cmd }
|
||||
print(target.version())
|
||||
return
|
||||
}
|
||||
}
|
||||
println('Invalid command: ${cmd.args.join(' ')}')
|
||||
} else if cmd.parent != unsafe { nil } {
|
||||
println(cmd.parent.version())
|
||||
} else {
|
||||
println(cmd.version())
|
||||
}
|
||||
}
|
||||
|
||||
// version returns a generated version `string` for the `Command`.
|
||||
pub fn (cmd &Command) version() string {
|
||||
return '${cmd.name} version ${cmd.version}'
|
||||
}
|
||||
Reference in new issue
Block a user