cli: extract improvements to vlib/cli, based on PR 5616 (without cmd/v2)

pull/5874/head
Delyan Angelov 2020-07-18 14:24:10 +03:00
parent 7ab6899538
commit 3a4f2dfe8b
6 changed files with 458 additions and 269 deletions

View File

@ -1,48 +1,105 @@
module cli
fn nil() voidptr { return 0 }
type FnCommandCallback fn (cmd Command)?
pub fn (f FnCommandCallback) str() string {
return 'FnCommandCallback=>' + ptr_str(f)
}
pub struct Command {
pub mut:
name string
usage string
description string
version string
pre_execute fn(cmd Command)
execute fn(cmd Command)
post_execute fn(cmd Command)
pre_execute FnCommandCallback
execute FnCommandCallback
post_execute FnCommandCallback
disable_help bool
disable_version bool
disable_flags bool
sort_flags bool = true
sort_commands bool = true
parent &Command = nil()
parent &Command = 0
commands []Command
flags []Flag
args []string
}
pub fn (cmd Command) full_name() string {
if isnil(cmd.parent) {
return cmd.name
pub fn (cmd Command) str() string {
mut res := []string{}
res << 'Command{'
res << ' name: "$cmd.name"'
res << ' usage: "$cmd.usage"'
res << ' version: "$cmd.version"'
res << ' description: "$cmd.description"'
res << ' disable_help: $cmd.disable_help'
res << ' disable_flags: $cmd.disable_flags'
res << ' disable_version: $cmd.disable_version'
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 cmd.parent == 0 {
res << ' parent: &Command(0)'
} else {
res << ' parent: &Command{$cmd.parent.name ...}'
}
return cmd.parent.full_name() + ' ${cmd.name}'
res << ' commands: $cmd.commands'
res << ' flags: $cmd.flags'
res << ' args: $cmd.args'
res << '}'
return res.join('\n')
}
pub fn (cmd Command) is_root() bool {
return isnil(cmd.parent)
}
pub fn (cmd Command) root() Command {
if isnil(cmd.parent) {
if cmd.is_root() {
return cmd
}
return cmd.parent.root()
}
pub fn (cmd Command) full_name() string {
if cmd.is_root() {
return cmd.name
}
return cmd.parent.full_name() + ' $cmd.name'
}
pub fn (mut cmd Command) add_commands(commands []Command) {
for command in commands {
cmd.add_command(command)
}
}
pub fn (mut cmd Command) add_command(command Command) {
for existing_cmd in cmd.commands {
if existing_cmd.name == command.name {
println("command with the same name \'$command.name\' already exists")
exit(1)
}
}
cmd.commands << command
}
pub fn (mut cmd Command) add_flags(flags []Flag) {
for flag in flags {
cmd.add_flag(flag)
}
}
pub fn (mut cmd Command) add_flag(flag Flag) {
for existing_flag in cmd.flags {
if existing_flag.name == flag.name {
println("flag with the same name \'$flag.name\' already exists")
exit(1)
}
}
cmd.flags << flag
}
@ -51,19 +108,16 @@ pub fn (mut cmd Command) parse(args []string) {
cmd.add_default_flags()
}
cmd.add_default_commands()
if cmd.sort_flags {
cmd.flags.sort()
}
if cmd.sort_commands {
cmd.commands.sort()
}
cmd.args = args[1..]
for i in 0 .. cmd.commands.len {
cmd.commands[i].parent = cmd
}
if !cmd.disable_flags {
cmd.parse_flags()
}
@ -72,18 +126,18 @@ pub fn (mut cmd Command) parse(args []string) {
fn (mut cmd Command) add_default_flags() {
if !cmd.disable_help && !cmd.flags.contains('help') {
cmd.add_flag(help_flag(!cmd.flags.contains('h')))
cmd.add_flag(help_flag(!cmd.flags.contains('h') && cmd.has_abbrev_flags()))
}
if !cmd.disable_version && cmd.version != '' && !cmd.flags.contains('version') {
cmd.add_flag(version_flag(!cmd.flags.contains('v')))
if cmd.version != '' && !cmd.flags.contains('version') {
cmd.add_flag(version_flag(!cmd.flags.contains('v') && cmd.has_abbrev_flags()))
}
}
fn (mut cmd Command) add_default_commands() {
if !cmd.disable_help && !cmd.commands.contains('help') {
if !cmd.disable_help && !cmd.commands.contains('help') && cmd.is_root() {
cmd.add_command(help_cmd())
}
if !cmd.disable_version && cmd.version != '' && !cmd.commands.contains('version') {
if cmd.version != '' && !cmd.commands.contains('version') {
cmd.add_command(version_cmd())
}
}
@ -96,17 +150,16 @@ fn (mut cmd Command) parse_flags() {
mut found := false
for i in 0 .. cmd.flags.len {
mut flag := &cmd.flags[i]
if flag.matches(cmd.args) {
if flag.matches(cmd.args, cmd.has_abbrev_flags()) {
found = true
flag.found = true
cmd.args = flag.parse(cmd.args) or {
println('failed to parse flag ${cmd.args[0]}: ${err}')
cmd.args = flag.parse(cmd.args, cmd.has_abbrev_flags()) or {
println('failed to parse flag ${cmd.args[0]}: $err')
exit(1)
}
break
}
}
if !found {
println('invalid flag: ${cmd.args[0]}')
exit(1)
@ -116,10 +169,8 @@ fn (mut cmd Command) parse_flags() {
fn (mut cmd Command) parse_commands() {
global_flags := cmd.flags.filter(it.global)
cmd.check_help_flag()
cmd.check_version_flag()
for i in 0 .. cmd.args.len {
arg := cmd.args[i]
for j in 0 .. cmd.commands.len {
@ -133,73 +184,97 @@ fn (mut cmd Command) parse_commands() {
}
}
}
// if no further command was found, execute current command
if int(cmd.execute) == 0 {
if !cmd.disable_help {
help_cmd := cmd.commands.get('help') or { return } // ignore error and handle command normally
help_cmd.execute(help_cmd)
cmd.execute_help()
}
} else {
cmd.check_required_flags()
if int(cmd.pre_execute) > 0 {
cmd.pre_execute(*cmd)
cmd.pre_execute(*cmd) or {
println('cli preexecution error: $err')
exit(1)
}
}
cmd.execute(*cmd) or {
println('cli execution error: $err')
exit(1)
}
cmd.execute(*cmd)
if int(cmd.post_execute) > 0 {
cmd.post_execute(*cmd)
cmd.post_execute(*cmd) or {
println('cli postexecution error: $err')
exit(1)
}
}
}
}
fn (mut cmd Command) check_help_flag() {
if cmd.disable_help {
return
fn (cmd Command) has_abbrev_flags() bool {
mut has_abbrev := false
for flag in cmd.flags {
if flag.abbrev != '' {
has_abbrev = true
}
if cmd.flags.contains('help') {
help_flag := cmd.flags.get_bool('help') or { return } // ignore error and handle command normally
}
return has_abbrev
}
fn (cmd Command) check_help_flag() {
if !cmd.disable_help && cmd.flags.contains('help') {
help_flag := cmd.flags.get_bool('help') or {
return
} // ignore error and handle command normally
if help_flag {
help_cmd := cmd.commands.get('help') or { return } // ignore error and handle command normally
help_cmd.execute(help_cmd)
cmd.execute_help()
exit(0)
}
}
}
fn (mut cmd Command) check_version_flag() {
if cmd.disable_version {
return
}
fn (cmd Command) check_version_flag() {
if cmd.version != '' && cmd.flags.contains('version') {
version_flag := cmd.flags.get_bool('version') or { return } // ignore error and handle command normally
version_flag := cmd.flags.get_bool('version') or {
return
} // ignore error and handle command normally
if version_flag {
version_cmd := cmd.commands.get('version') or { return } // ignore error and handle command normally
version_cmd := cmd.commands.get('version') or {
return
} // ignore error and handle command normally
version_cmd.execute(version_cmd)
exit(0)
}
}
}
fn (mut cmd Command) check_required_flags() {
fn (cmd Command) check_required_flags() {
for flag in cmd.flags {
if flag.required && flag.value == '' {
full_name := cmd.full_name()
println('flag \'${flag.name}\' is required by \'${full_name}\'')
println("flag \'$flag.name\' is required by \'$full_name\'")
exit(1)
}
}
}
fn (cmd Command) execute_help() {
if cmd.commands.contains('help') {
help_cmd := cmd.commands.get('help') or {
return
} // ignore error and handle command normally
help_cmd.execute(help_cmd)
} else {
print(cmd.help_message())
}
}
fn (cmds []Command) get(name string) ?Command {
for cmd in cmds {
if cmd.name == name {
return cmd
}
}
return error('command \'${name}\' not found.')
return error("command \'$name\' not found.")
}
fn (cmds []Command) contains(name string) bool {
@ -212,7 +287,7 @@ fn (cmds []Command) contains(name string) bool {
}
fn (mut cmds []Command) sort() {
cmds.sort_with_compare(fn(a &Command, b &Command) int {
cmds.sort_with_compare(fn (a, b &Command) int {
return compare_strings(&a.name, &b.name)
})
}

View File

@ -2,100 +2,95 @@ import cli
fn test_if_command_parses_empty_args() {
mut cmd := cli.Command{
name: 'command',
execute: empty_func,
name: 'command'
execute: empty_func
}
cmd.parse(['command'])
assert cmd.name == 'command'
&& compare_arrays(cmd.args, [])
assert cmd.name == 'command' && compare_arrays(cmd.args, [])
}
fn test_if_command_parses_args() {
mut cmd := cli.Command{
name: 'command',
execute: empty_func,
name: 'command'
execute: empty_func
}
cmd.parse(['command', 'arg0', 'arg1'])
assert cmd.name == 'command'
&& compare_arrays(cmd.args, ['arg0', 'arg1'])
assert cmd.name == 'command' && compare_arrays(cmd.args, ['arg0', 'arg1'])
}
fn test_if_subcommands_parse_args() {
mut cmd := cli.Command{
name: 'command',
name: 'command'
}
subcmd := cli.Command{
name: 'subcommand',
execute: empty_func,
name: 'subcommand'
execute: empty_func
}
cmd.add_command(subcmd)
cmd.parse(['command', 'subcommand', 'arg0', 'arg1'])
}
fn if_subcommands_parse_args_func(cmd cli.Command) {
assert cmd.name == 'subcommand'
&& compare_arrays(cmd.args, ['arg0', 'arg1'])
assert cmd.name == 'subcommand' && compare_arrays(cmd.args, ['arg0', 'arg1'])
}
fn test_if_command_has_default_help_subcommand() {
mut cmd := cli.Command{
name: 'command',
name: 'command'
}
cmd.parse(['command'])
assert has_command(cmd, 'help')
}
fn test_if_command_has_default_version_subcommand_if_version_is_set() {
mut cmd := cli.Command{
name: 'command',
version: '1.0.0',
name: 'command'
version: '1.0.0'
}
cmd.parse(['command'])
assert has_command(cmd, 'version')
}
fn test_if_flag_gets_set() {
mut cmd := cli.Command{
name: 'command',
execute: if_flag_gets_set_func,
name: 'command'
execute: fn (cmd cli.Command) ? {
flag := cmd.flags.get_string('flag')?
assert flag == 'value'
}
}
cmd.add_flag(cli.Flag{
flag: .string
name: 'flag'
})
cmd.parse(['command', '--flag', 'value'])
}
fn if_flag_gets_set_func(cmd cli.Command) {
flag := cmd.flags.get_string('flag') or { panic(err) }
assert flag == 'value'
cmd.parse(['command', '-flag', 'value'])
}
fn test_if_flag_gets_set_with_abbrev() {
mut cmd := cli.Command{
name: 'command',
execute: if_flag_gets_set_with_abbrev_func,
name: 'command'
execute: fn (cmd cli.Command) ? {
flag := cmd.flags.get_string('flag')?
assert flag == 'value'
}
}
cmd.add_flag(cli.Flag{
flag: .string,
name: 'flag',
abbrev: 'f',
flag: .string
name: 'flag'
abbrev: 'f'
})
cmd.parse(['command', '-f', 'value'])
}
fn if_flag_gets_set_with_abbrev_func(cmd cli.Command) {
flag := cmd.flags.get_string('flag') or { panic(err) }
assert flag == 'value'
cmd.parse(['command', '--flag', 'value'])
}
fn test_if_multiple_flags_get_set() {
mut cmd := cli.Command{
name: 'command',
execute: if_multiple_flags_get_set_func,
name: 'command'
execute: fn (cmd cli.Command) ? {
flag := cmd.flags.get_string('flag')?
value := cmd.flags.get_int('value')?
assert flag == 'value' && value == 42
}
}
cmd.add_flag(cli.Flag{
flag: .string
@ -105,65 +100,57 @@ fn test_if_multiple_flags_get_set() {
flag: .int
name: 'value'
})
cmd.parse(['command', '--flag', 'value', '--value', '42'])
}
fn if_multiple_flags_get_set_func(cmd cli.Command) {
flag := cmd.flags.get_string('flag') or { panic(err) }
value := cmd.flags.get_int('value') or { panic(err) }
assert flag == 'value'
&& value == 42
cmd.parse(['command', '-flag', 'value', '-value', '42'])
}
fn test_if_flag_gets_set_in_subcommand() {
mut cmd := cli.Command{
name: 'command',
execute: empty_func,
name: 'command'
execute: empty_func
}
mut subcmd := cli.Command{
name: 'subcommand',
execute: if_flag_gets_set_in_subcommand_func
name: 'subcommand'
execute: fn (cmd cli.Command) ? {
flag := cmd.flags.get_string('flag') or {
panic(err)
}
assert flag == 'value'
}
}
subcmd.add_flag(cli.Flag{
flag: .string
name: 'flag'
})
cmd.add_command(subcmd)
cmd.parse(['command', 'subcommand', '--flag', 'value'])
}
fn if_flag_gets_set_in_subcommand_func(cmd cli.Command) {
flag := cmd.flags.get_string('flag') or { panic(err) }
assert flag == 'value'
cmd.parse(['command', 'subcommand', '-flag', 'value'])
}
fn test_if_global_flag_gets_set_in_subcommand() {
mut cmd := cli.Command{
name: 'command',
execute: empty_func,
name: 'command'
execute: empty_func
}
cmd.add_flag(cli.Flag{
flag: .string,
name: 'flag',
global: true,
flag: .string
name: 'flag'
global: true
})
subcmd := cli.Command{
name: 'subcommand',
execute: if_global_flag_gets_set_in_subcommand_func,
name: 'subcommand'
execute: fn (cmd cli.Command) {
flag := cmd.flags.get_string('flag') or {
panic(err)
}
cmd.add_command(subcmd)
cmd.parse(['command', '--flag', 'value', 'subcommand'])
}
fn if_global_flag_gets_set_in_subcommand_func(cmd cli.Command) {
flag := cmd.flags.get_string('flag') or { panic(err) }
assert flag == 'value'
}
}
cmd.add_command(subcmd)
cmd.parse(['command', '-flag', 'value', 'subcommand'])
}
// helper functions
fn empty_func(cmd cli.Command) {}
fn empty_func(cmd cli.Command) ? {
}
fn has_command(cmd cli.Command, name string) bool {
for subcmd in cmd.commands {
@ -174,7 +161,7 @@ fn has_command(cmd cli.Command, name string) bool {
return false
}
fn compare_arrays(array0 []string, array1 []string) bool {
fn compare_arrays(array0, array1 []string) bool {
if array0.len != array1.len {
return false
}

View File

@ -16,7 +16,6 @@ pub mut:
global bool
required bool
value string
mut:
found bool
}
@ -26,73 +25,101 @@ pub fn (flags []Flag) get_all_found() []Flag {
}
pub fn (flag Flag) get_bool() ?bool {
if flag.flag != .bool { return error('invalid flag type') }
if flag.flag != .bool {
return error('invalid flag type')
}
return flag.value == 'true'
}
pub fn (flags []Flag) get_bool(name string) ?bool {
flag := flags.get(name) or { return error(err) }
flag := flags.get(name) or {
return error(err)
}
return flag.get_bool()
}
pub fn (flags []Flag) get_bool_or(name string, or_value bool) bool {
value := flags.get_bool(name) or { return or_value }
value := flags.get_bool(name) or {
return or_value
}
return value
}
pub fn (flag Flag) get_int() ?int {
if flag.flag != .int { return error('invalid flag type') }
if flag.flag != .int {
return error('invalid flag type')
}
return flag.value.int()
}
pub fn (flags []Flag) get_int(name string) ?int {
flag := flags.get(name) or { return error(err) }
flag := flags.get(name) or {
return error(err)
}
return flag.get_int()
}
pub fn (flags []Flag) get_int_or(name string, or_value int) int {
value := flags.get_int(name) or { return or_value }
value := flags.get_int(name) or {
return or_value
}
return value
}
pub fn (flag Flag) get_float() ?f64 {
if flag.flag != .float { return error('invalid flag type') }
if flag.flag != .float {
return error('invalid flag type')
}
return flag.value.f64()
}
pub fn (flags []Flag) get_float(name string) ?f64 {
flag := flags.get(name) or { return error(err) }
flag := flags.get(name) or {
return error(err)
}
return flag.get_float()
}
pub fn (flags []Flag) get_float_or(name string, or_value f64) f64 {
value := flags.get_float(name) or { return or_value }
value := flags.get_float(name) or {
return or_value
}
return value
}
pub fn (flag Flag) get_string() ?string {
if flag.flag != .string { return error('invalid flag type') }
if flag.flag != .string {
return error('invalid flag type')
}
return flag.value
}
pub fn (flags []Flag) get_string(name string) ?string {
flag := flags.get(name) or { return error(err) }
flag := flags.get(name) or {
return error(err)
}
return flag.get_string()
}
pub fn (flags []Flag) get_string_or(name string, or_value string) string {
value := flags.get_string(name) or { return or_value }
pub fn (flags []Flag) get_string_or(name, or_value string) string {
value := flags.get_string(name) or {
return or_value
}
return value
}
// parse flag value from arguments and return arguments with all consumed element removed
fn (mut flag Flag) parse(args []string) ?[]string {
if flag.matches(args) {
fn (mut flag Flag) parse(args []string, with_abbrev bool) ?[]string {
if flag.matches(args, with_abbrev) {
if flag.flag == .bool {
new_args := flag.parse_bool(args) or { return error(err) }
new_args := flag.parse_bool(args) or {
return error(err)
}
return new_args
} else {
new_args := flag.parse_raw(args) or { return error(err) }
new_args := flag.parse_raw(args) or {
return error(err)
}
return new_args
}
} else {
@ -101,12 +128,16 @@ fn (mut flag Flag) parse(args []string) ?[]string {
}
// check if first arg matches flag
fn (mut flag Flag) matches(args []string) bool {
return
(flag.name != '' && args[0] == '--${flag.name}') ||
(flag.name != '' && args[0].starts_with('--${flag.name}=')) ||
(flag.abbrev != '' && args[0] == '-${flag.abbrev}') ||
(flag.abbrev != '' && args[0].starts_with('-${flag.abbrev}='))
fn (mut flag Flag) matches(args []string, with_abbrev bool) bool {
if with_abbrev {
return (flag.name != '' && args[0] == '--$flag.name') ||
(flag.name != '' && args[0].starts_with('--$flag.name=')) ||
(flag.abbrev != '' && args[0] == '-$flag.abbrev') ||
(flag.abbrev != '' && args[0].starts_with('-$flag.abbrev='))
} else {
return (flag.name != '' && args[0] == '-$flag.name') ||
(flag.name != '' && args[0].starts_with('-$flag.name='))
}
}
fn (mut flag Flag) parse_raw(args []string) ?[]string {
@ -117,7 +148,7 @@ fn (mut flag Flag) parse_raw(args []string) ?[]string {
flag.value = args[1]
return args[2..]
}
return error('missing argument for ${flag.name}')
return error('missing argument for $flag.name')
}
fn (mut flag Flag) parse_bool(args []string) ?[]string {
@ -140,7 +171,7 @@ fn (flags []Flag) get(name string) ?Flag {
return flag
}
}
return error('flag ${name} not found.')
return error('flag $name not found.')
}
fn (flags []Flag) contains(name string) bool {
@ -153,7 +184,7 @@ fn (flags []Flag) contains(name string) bool {
}
fn (mut flags []Flag) sort() {
flags.sort_with_compare(fn(a &Flag, b &Flag) int {
flags.sort_with_compare(fn (a, b &Flag) int {
return compare_strings(&a.name, &b.name)
})
}

View File

@ -2,74 +2,126 @@ import cli
fn test_if_string_flag_parses() {
mut flag := cli.Flag{
flag: .string,
name: 'flag',
flag: .string
name: 'flag'
}
flag.parse(['-flag', 'value'], false) or {
panic(err)
}
flag.parse(['--flag', 'value']) or { panic(err) }
assert flag.value == 'value'
flag.parse(['--flag=value']) or { panic(err) }
flag.parse(['-flag=value'], false) or {
panic(err)
}
assert flag.value == 'value'
}
fn test_if_bool_flag_parses() {
mut flag := cli.Flag{
flag: .bool,
name: 'flag',
flag: .bool
name: 'flag'
}
mut value := false
flag.parse(['--flag']) or { panic(err) }
value = flag.get_bool() or { panic(err) }
flag.parse(['-flag'], false) or {
panic(err)
}
value = flag.get_bool() or {
panic(err)
}
assert value == true
flag.parse(['--flag', 'true']) or { panic(err) }
value = flag.get_bool() or { panic(err) }
flag.parse(['-flag', 'true'], false) or {
panic(err)
}
value = flag.get_bool() or {
panic(err)
}
assert value == true
flag.parse(['--flag=true']) or { panic(err) }
value = flag.get_bool() or { panic(err) }
flag.parse(['-flag=true'], false) or {
panic(err)
}
value = flag.get_bool() or {
panic(err)
}
assert value == true
flag.parse(['--flag', 'false']) or { panic(err) }
value = flag.get_bool() or { panic(err) }
flag.parse(['-flag', 'false'], false) or {
panic(err)
}
value = flag.get_bool() or {
panic(err)
}
assert value == false
flag.parse(['--flag=false']) or { panic(err) }
value = flag.get_bool() or { panic(err) }
flag.parse(['-flag=false'], false) or {
panic(err)
}
value = flag.get_bool() or {
panic(err)
}
assert value == false
}
fn test_if_int_flag_parses() {
mut flag := cli.Flag{
flag: .int,
name: 'flag',
flag: .int
name: 'flag'
}
mut value := 0
flag.parse(['--flag', '42']) or { panic(err) }
value = flag.get_int() or { panic(err) }
flag.parse(['-flag', '42'], false) or {
panic(err)
}
value = flag.get_int() or {
panic(err)
}
assert value == 42
flag.parse(['--flag=42']) or { panic(err) }
value = flag.get_int() or { panic(err) }
flag.parse(['-flag=42'], false) or {
panic(err)
}
value = flag.get_int() or {
panic(err)
}
assert value == 42
}
fn test_if_float_flag_parses() {
mut flag := cli.Flag{
flag: .float,
name: 'flag',
flag: .float
name: 'flag'
}
mut value := f64(0)
flag.parse(['--flag', '3.14159']) or { panic(err) }
value = flag.get_float() or { panic(err) }
flag.parse(['-flag', '3.14159'], false) or {
panic(err)
}
value = flag.get_float() or {
panic(err)
}
assert value == 3.14159
flag.parse(['--flag=3.14159']) or { panic(err) }
flag.parse(['-flag=3.14159'], false) or {
panic(err)
}
assert flag.value.f64() == 3.14159
value = flag.get_float() or { panic(err) }
value = flag.get_float() or {
panic(err)
}
assert value == 3.14159
}
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
flag.parse(['-flag'], true) or {
panic(err)
}
value = flag.get_bool() or {
panic(err)
}
assert value == true
}

View File

@ -10,38 +10,69 @@ const (
)
fn help_flag(with_abbrev bool) Flag {
sabbrev := if with_abbrev { 'h' } else { '' }
return Flag{
flag: .bool,
name: 'help',
abbrev: if with_abbrev { 'h' } else { '' },
description: 'Prints help information',
flag: .bool
name: 'help'
abbrev: sabbrev
description: 'Prints help information'
}
}
fn help_cmd() Command {
return Command{
name: 'help',
description: 'Prints help information',
execute: help_func,
name: 'help'
usage: '<command>'
description: 'Prints help information'
execute: print_help_for_command
}
}
fn help_func(help_cmd Command) {
cmd := help_cmd.parent
full_name := cmd.full_name()
fn print_help_for_command(help_cmd Command) ? {
if help_cmd.args.len > 0 {
mut cmd := help_cmd.parent
for arg in help_cmd.args {
mut found := false
for sub_cmd in cmd.commands {
if sub_cmd.name == arg {
cmd = &sub_cmd
found = true
break
}
}
if !found {
args := help_cmd.args.join(' ')
print('invalid command: $args')
return
}
}
print(cmd.help_message())
} else {
if help_cmd.parent != 0 {
print(help_cmd.parent.help_message())
}
}
}
fn (cmd Command) help_message() string {
mut help := ''
help += 'Usage: ${full_name}'
if cmd.flags.len > 0 { help += ' [FLAGS]'}
if cmd.commands.len > 0 { help += ' [COMMANDS]'}
help += '\n\n'
if cmd.description != '' {
help += '${cmd.description}\n\n'
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'
}
help += '\n\n'
if cmd.description != '' {
help += '$cmd.description\n\n'
}
mut abbrev_len := 0
mut name_len := min_description_indent_len
if cmd.has_abbrev_flags() {
for flag in cmd.flags {
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
@ -49,27 +80,35 @@ fn help_func(help_cmd Command) {
for command in cmd.commands {
name_len = max(name_len, command.name.len + spacing)
}
} else {
for flag in cmd.flags {
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.name.len + spacing)
}
}
if cmd.flags.len > 0 {
help += 'Flags:\n'
for flag in cmd.flags {
mut flag_name := ''
if flag.abbrev != '' {
if flag.abbrev != '' && cmd.has_abbrev_flags() {
abbrev_indent := ' '.repeat(abbrev_len - flag.abbrev.len - 1) // - 1 for '-' in front
flag_name = '-${flag.abbrev}${abbrev_indent}--${flag.name}'
} else {
flag_name = '-$flag.abbrev$abbrev_indent--$flag.name'
} else if cmd.has_abbrev_flags() {
abbrev_indent := ' '.repeat(abbrev_len)
flag_name = '${abbrev_indent}--${flag.name}'
flag_name = '$abbrev_indent--$flag.name'
} else {
flag_name = '-$flag.name'
}
mut required := ''
if flag.required {
required = ' (required)'
}
base_indent := ' '.repeat(base_indent_len)
description_indent := ' '.repeat(name_len - flag_name.len)
help += '${base_indent}${flag_name}${description_indent}' +
pretty_description(flag.description + required, base_indent_len + name_len) + '\n'
help += '$base_indent$flag_name$description_indent' + pretty_description(flag.description +
required, base_indent_len + name_len) + '\n'
}
help += '\n'
}
@ -78,14 +117,12 @@ fn help_func(help_cmd Command) {
for command in cmd.commands {
base_indent := ' '.repeat(base_indent_len)
description_indent := ' '.repeat(name_len - command.name.len)
help += '${base_indent}${command.name}${description_indent}' +
pretty_description(command.description, name_len) + '\n'
help += '$base_indent$command.name$description_indent' + pretty_description(command.description, name_len) +
'\n'
}
help += '\n'
}
print(help)
return help
}
// pretty_description resizes description text depending on terminal width.
@ -100,15 +137,20 @@ fn pretty_description(s string, indent_len int) string {
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('\n${indent}') }
if k != 0 {
acc.write('\n$indent')
}
mut i := chars_per_line - 2
mut j := 0
for ; i < line.len; i += chars_per_line - 2 {
for line.str[i] != ` ` { i-- }
for line.str[i] != ` ` {
i--
}
// indent was already done the first iteration
if j != 0 { acc.write(indent) }
if j != 0 {
acc.write(indent)
}
acc.writeln(line[j..i].trim_space())
j = i
}
@ -122,5 +164,6 @@ fn pretty_description(s string, indent_len int) string {
}
fn max(a, b int) int {
return if a > b {a} else {b}
res := if a > b { a } else { b }
return res
}

View File

@ -1,24 +1,25 @@
module cli
fn version_flag(with_abbrev bool) Flag {
sabbrev := if with_abbrev { 'v' } else { '' }
return Flag{
flag: .bool,
name: 'version',
abbrev: if with_abbrev { 'v' } else { '' },
description: 'Prints version information',
flag: .bool
name: 'version'
abbrev: sabbrev
description: 'Prints version information'
}
}
fn version_cmd() Command {
return Command{
name: 'version'
description: 'Prints version information',
execute: version_func,
description: 'Prints version information'
execute: version_func
}
}
fn version_func(version_cmd Command) {
fn version_func(version_cmd Command) ? {
cmd := version_cmd.parent
version := '${cmd.name} v${cmd.version}'
version := '$cmd.name version $cmd.version'
println(version)
}