From 93e6c3df6ae38b4770bab82fd07ddd2e5f6a5462 Mon Sep 17 00:00:00 2001 From: Lukas Neubert Date: Thu, 20 Aug 2020 23:14:53 +0200 Subject: [PATCH] cli: various improvements (#6180) --- examples/cli.v | 28 +++++++++-------- vlib/cli/command.v | 68 +++++++++++++++++++---------------------- vlib/cli/command_test.v | 3 -- vlib/cli/flag.v | 26 ++++++++-------- vlib/cli/help.v | 15 +++++---- vlib/cli/version.v | 4 +-- 6 files changed, 71 insertions(+), 73 deletions(-) diff --git a/examples/cli.v b/examples/cli.v index f69cf0c0b8..db45b6cc35 100644 --- a/examples/cli.v +++ b/examples/cli.v @@ -1,29 +1,30 @@ module main -import cli +import cli { Command, Flag } import os fn main() { - mut cmd := cli.Command{ + mut cmd := Command{ name: 'cli' description: 'An example of the cli library.' version: '1.0.0' } - mut greet_cmd := cli.Command{ + mut greet_cmd := Command{ name: 'greet' description: 'Prints greeting in different languages.' + required_args: 1 pre_execute: greet_pre_func execute: greet_func post_execute: greet_post_func } - greet_cmd.add_flag(cli.Flag{ + greet_cmd.add_flag(Flag{ flag: .string required: true name: 'language' abbrev: 'l' description: 'Language of the message.' }) - greet_cmd.add_flag(cli.Flag{ + greet_cmd.add_flag(Flag{ flag: .int name: 'times' value: '3' @@ -33,23 +34,24 @@ fn main() { cmd.parse(os.args) } -fn greet_func(cmd cli.Command) { +fn greet_func(cmd Command) { language := cmd.flags.get_string('language') or { - panic("Failed to get \'language\' flag: $err") + panic('Failed to get `language` flag: $err') } times := cmd.flags.get_int('times') or { - panic("Failed to get \'times\' flag: $err") + panic('Failed to get `times` flag: $err') } + name := cmd.args[0] for _ in 0 .. times { match language { 'english' { - println('Hello World') + println('Welcome $name') } 'german' { - println('Hallo Welt') + println('Willkommen $name') } 'dutch' { - println('Hallo Wereld') + println('Welkom $name') } else { println('Unsupported language') @@ -60,10 +62,10 @@ fn greet_func(cmd cli.Command) { } } -fn greet_pre_func(cmd cli.Command) { +fn greet_pre_func(cmd Command) { println('This is a function running before the main function.\n') } -fn greet_post_func(cmd cli.Command) { +fn greet_post_func(cmd Command) { println('\nThis is a function running after the main function.') } diff --git a/vlib/cli/command.v b/vlib/cli/command.v index c78481beac..73f5fd23f4 100644 --- a/vlib/cli/command.v +++ b/vlib/cli/command.v @@ -1,6 +1,6 @@ module cli -type FnCommandCallback fn (cmd Command)? +type FnCommandCallback = fn (cmd Command) ? pub fn (f FnCommandCallback) str() string { return 'FnCommandCallback=>' + ptr_str(f) @@ -23,6 +23,7 @@ pub mut: parent &Command = 0 commands []Command flags []Flag + required_args int args []string } @@ -48,6 +49,7 @@ pub fn (cmd Command) str() string { } res << ' commands: $cmd.commands' res << ' flags: $cmd.flags' + res << ' required_args: $cmd.required_args' res << ' args: $cmd.args' res << '}' return res.join('\n') @@ -78,13 +80,13 @@ pub fn (mut cmd Command) add_commands(commands []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) - } + mut subcmd := command + if cmd.commands.contains(subcmd.name) { + println('Command with the name `$subcmd.name` already exists') + exit(1) } - cmd.commands << command + subcmd.parent = cmd + cmd.commands << subcmd } pub fn (mut cmd Command) add_flags(flags []Flag) { @@ -94,11 +96,9 @@ pub fn (mut cmd Command) add_flags(flags []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) - } + if cmd.flags.contains(flag.name) { + println('Flag with the name `$flag.name` already exists') + exit(1) } cmd.flags << flag } @@ -115,9 +115,6 @@ pub fn (mut cmd Command) parse(args []string) { cmd.commands.sort(a.name < b.name) } cmd.args = args[1..] - for i in 0 .. cmd.commands.len { - cmd.commands[i].parent = cmd - } if !cmd.disable_flags { cmd.parse_flags() } @@ -126,10 +123,12 @@ 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.has_abbrev_flags())) + use_help_abbrev := !cmd.flags.contains('h') && cmd.flags.have_abbrev() + cmd.add_flag(help_flag(use_help_abbrev)) } - if cmd.version != '' && !cmd.flags.contains('version') { - cmd.add_flag(version_flag(!cmd.flags.contains('v') && cmd.has_abbrev_flags())) + if !cmd.disable_version && cmd.version != '' && !cmd.flags.contains('version') { + use_version_abbrev := !cmd.flags.contains('v') && cmd.flags.have_abbrev() + cmd.add_flag(version_flag(use_version_abbrev)) } } @@ -137,7 +136,7 @@ fn (mut cmd Command) add_default_commands() { if !cmd.disable_help && !cmd.commands.contains('help') && cmd.is_root() { cmd.add_command(help_cmd()) } - if cmd.version != '' && !cmd.commands.contains('version') { + if !cmd.disable_version && cmd.version != '' && !cmd.commands.contains('version') { cmd.add_command(version_cmd()) } } @@ -150,18 +149,18 @@ 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, cmd.has_abbrev_flags()) { + if flag.matches(cmd.args, cmd.flags.have_abbrev()) { found = true flag.found = true - cmd.args = flag.parse(cmd.args, cmd.has_abbrev_flags()) or { - println('failed to parse flag ${cmd.args[0]}: $err') + cmd.args = flag.parse(cmd.args, cmd.flags.have_abbrev()) or { + println('Failed to parse flag `${cmd.args[0]}`: $err') exit(1) } break } } if !found { - println('invalid flag: ${cmd.args[0]}') + println('Command `$cmd.name` has no flag `${cmd.args[0]}`') exit(1) } } @@ -190,6 +189,12 @@ fn (mut cmd Command) parse_commands() { cmd.execute_help() } } else { + if cmd.required_args > 0 { + if cmd.required_args > cmd.args.len { + println('Command `$cmd.name` needs at least $cmd.required_args arguments') + exit(1) + } + } cmd.check_required_flags() if int(cmd.pre_execute) > 0 { cmd.pre_execute(*cmd) or { @@ -210,16 +215,6 @@ fn (mut cmd Command) parse_commands() { } } -fn (cmd Command) has_abbrev_flags() bool { - mut has_abbrev := false - for flag in cmd.flags { - if flag.abbrev != '' { - has_abbrev = true - } - } - 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 { @@ -233,7 +228,7 @@ fn (cmd Command) check_help_flag() { } fn (cmd Command) check_version_flag() { - if cmd.version != '' && cmd.flags.contains('version') { + if !cmd.disable_version && cmd.version != '' && cmd.flags.contains('version') { version_flag := cmd.flags.get_bool('version') or { return } // ignore error and handle command normally @@ -251,7 +246,7 @@ 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) } } @@ -274,7 +269,7 @@ fn (cmds []Command) get(name string) ?Command { return cmd } } - return error("command \'$name\' not found.") + return error('Command `$name` not found') } fn (cmds []Command) contains(name string) bool { @@ -285,4 +280,3 @@ fn (cmds []Command) contains(name string) bool { } return false } - diff --git a/vlib/cli/command_test.v b/vlib/cli/command_test.v index 1bd2014752..e5924232b0 100644 --- a/vlib/cli/command_test.v +++ b/vlib/cli/command_test.v @@ -51,7 +51,6 @@ fn test_if_command_has_default_version_subcommand_if_version_is_set() { assert has_command(cmd, 'version') } - fn flag_should_be_set(cmd cli.Command) ? { flag := cmd.flags.get_string('flag')? assert flag == 'value' @@ -83,7 +82,6 @@ fn test_if_flag_gets_set_with_abbrev() { cmd.parse(['command', '--flag', 'value']) } - fn flag_should_have_value_of_42(cmd cli.Command) ? { flag := cmd.flags.get_string('flag')? assert flag == 'value' @@ -107,7 +105,6 @@ fn test_if_multiple_flags_get_set() { cmd.parse(['command', '-flag', 'value', '-value', '42']) } - fn flag_is_set_in_subcommand(cmd cli.Command) ? { flag := cmd.flags.get_string('flag') or { panic(err) diff --git a/vlib/cli/flag.v b/vlib/cli/flag.v index 2171a32dda..64af6a2693 100644 --- a/vlib/cli/flag.v +++ b/vlib/cli/flag.v @@ -26,7 +26,7 @@ pub fn (flags []Flag) get_all_found() []Flag { pub fn (flag Flag) get_bool() ?bool { if flag.flag != .bool { - return error('invalid flag type') + return error('Invalid flag type') } return flag.value == 'true' } @@ -47,7 +47,7 @@ pub fn (flags []Flag) get_bool_or(name string, or_value bool) bool { pub fn (flag Flag) get_int() ?int { if flag.flag != .int { - return error('invalid flag type') + return error('Invalid flag type') } return flag.value.int() } @@ -68,7 +68,7 @@ pub fn (flags []Flag) get_int_or(name string, or_value int) int { pub fn (flag Flag) get_float() ?f64 { if flag.flag != .float { - return error('invalid flag type') + return error('Invalid flag type') } return flag.value.f64() } @@ -89,7 +89,7 @@ pub fn (flags []Flag) get_float_or(name string, or_value f64) f64 { pub fn (flag Flag) get_string() ?string { if flag.flag != .string { - return error('invalid flag type') + return error('Invalid flag type') } return flag.value } @@ -148,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 { @@ -171,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 { @@ -183,10 +183,12 @@ fn (flags []Flag) contains(name string) bool { return false } - -fn (mut flags []Flag) sort2() { - flags.sort_with_compare(fn (a, b &Flag) int { - return compare_strings(&a.name, &b.name) - }) +fn (flags []Flag) have_abbrev() bool { + mut have_abbrev := false + for flag in flags { + if flag.abbrev != '' { + have_abbrev = true + } + } + return have_abbrev } - diff --git a/vlib/cli/help.v b/vlib/cli/help.v index 5fd8d46220..ca48fe84c5 100644 --- a/vlib/cli/help.v +++ b/vlib/cli/help.v @@ -15,7 +15,7 @@ fn help_flag(with_abbrev bool) Flag { flag: .bool name: 'help' abbrev: sabbrev - description: 'Prints help information' + description: 'Prints help information.' } } @@ -23,7 +23,7 @@ fn help_cmd() Command { return Command{ name: 'help' usage: '' - description: 'Prints help information' + description: 'Prints help information.' execute: print_help_for_command } } @@ -42,7 +42,7 @@ fn print_help_for_command(help_cmd Command) ? { } if !found { args := help_cmd.args.join(' ') - print('invalid command: $args') + print('Invalid command: $args') return } } @@ -63,6 +63,9 @@ fn (cmd Command) help_message() string { if cmd.commands.len > 0 { help += ' [commands]' } + for i in 0 .. cmd.required_args { + help += ' ' + } if cmd.usage.len > 0 { help += ' $cmd.usage' } @@ -72,7 +75,7 @@ fn (cmd Command) help_message() string { } mut abbrev_len := 0 mut name_len := min_description_indent_len - if cmd.has_abbrev_flags() { + if cmd.flags.have_abbrev() { 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 @@ -92,10 +95,10 @@ fn (cmd Command) help_message() string { help += 'Flags:\n' for flag in cmd.flags { mut flag_name := '' - if flag.abbrev != '' && cmd.has_abbrev_flags() { + if flag.abbrev != '' && cmd.flags.have_abbrev() { abbrev_indent := ' '.repeat(abbrev_len - flag.abbrev.len - 1) // - 1 for '-' in front flag_name = '-$flag.abbrev$abbrev_indent--$flag.name' - } else if cmd.has_abbrev_flags() { + } else if cmd.flags.have_abbrev() { abbrev_indent := ' '.repeat(abbrev_len) flag_name = '$abbrev_indent--$flag.name' } else { diff --git a/vlib/cli/version.v b/vlib/cli/version.v index 38b3526f35..0f3f583b88 100644 --- a/vlib/cli/version.v +++ b/vlib/cli/version.v @@ -6,14 +6,14 @@ fn version_flag(with_abbrev bool) Flag { flag: .bool name: 'version' abbrev: sabbrev - description: 'Prints version information' + description: 'Prints version information.' } } fn version_cmd() Command { return Command{ name: 'version' - description: 'Prints version information' + description: 'Prints version information.' execute: version_func } }