diff --git a/vlib/cli/command.v b/vlib/cli/command.v index 6eecdd5cd0..29f4704afe 100644 --- a/vlib/cli/command.v +++ b/vlib/cli/command.v @@ -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 - description string - version string - pre_execute fn(cmd Command) - execute fn(cmd Command) - post_execute fn(cmd Command) - - disable_help bool + name string + usage string + description string + version string + 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() - commands []Command - flags []Flag - args []string + disable_flags bool + sort_flags bool = true + sort_commands bool = true + 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 { + 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()) } } @@ -94,19 +148,18 @@ fn (mut cmd Command) parse_flags() { break } mut found := false - for i in 0..cmd.flags.len { + 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,13 +169,11 @@ 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 { + for i in 0 .. cmd.args.len { arg := cmd.args[i] - for j in 0..cmd.commands.len { + for j in 0 .. cmd.commands.len { mut command := cmd.commands[j] if command.name == arg { for flag in global_flags { @@ -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) }) } diff --git a/vlib/cli/command_test.v b/vlib/cli/command_test.v index b0b7884f44..d1ff53ab97 100644 --- a/vlib/cli/command_test.v +++ b/vlib/cli/command_test.v @@ -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) + } + assert flag == 'value' + } } cmd.add_command(subcmd) - cmd.parse(['command', '--flag', 'value', 'subcommand']) + 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' -} - - // 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,11 +161,11 @@ 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 } - for i in 0..array0.len { + for i in 0 .. array0.len { if array0[i] != array1[i] { return false } diff --git a/vlib/cli/flag.v b/vlib/cli/flag.v index 031ab64801..3fc0fd0d36 100644 --- a/vlib/cli/flag.v +++ b/vlib/cli/flag.v @@ -9,16 +9,15 @@ pub enum FlagType { pub struct Flag { pub mut: - flag FlagType - name string - abbrev string + flag FlagType + name string + abbrev string description string - global bool - required bool - value string - + global bool + required bool + value string mut: - found bool + found bool } pub fn (flags []Flag) get_all_found() []Flag { @@ -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) }) } diff --git a/vlib/cli/flag_test.v b/vlib/cli/flag_test.v index b670ee0891..a9cc918263 100644 --- a/vlib/cli/flag_test.v +++ b/vlib/cli/flag_test.v @@ -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 +} diff --git a/vlib/cli/help.v b/vlib/cli/help.v index 3b404ca332..80050689e5 100644 --- a/vlib/cli/help.v +++ b/vlib/cli/help.v @@ -4,72 +4,111 @@ import term import strings const ( - base_indent_len = 2 + base_indent_len = 2 min_description_indent_len = 20 - spacing = 2 + spacing = 2 ) 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: '' + description: 'Prints help information' + execute: print_help_for_command } } -fn help_func(help_cmd Command) { - cmd := help_cmd.parent - full_name := cmd.full_name() - - 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' +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: $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 - 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 + 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 + } + 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) + } } - 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. @@ -93,22 +130,27 @@ fn help_func(help_cmd Command) { 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 { + 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('\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 } diff --git a/vlib/cli/version.v b/vlib/cli/version.v index 0ac37db3ae..38b3526f35 100644 --- a/vlib/cli/version.v +++ b/vlib/cli/version.v @@ -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) }