diff options
Diffstat (limited to 'v_windows/v/old/vlib/cli')
-rw-r--r-- | v_windows/v/old/vlib/cli/README.md | 30 | ||||
-rw-r--r-- | v_windows/v/old/vlib/cli/command.v | 305 | ||||
-rw-r--r-- | v_windows/v/old/vlib/cli/command_test.v | 221 | ||||
-rw-r--r-- | v_windows/v/old/vlib/cli/flag.v | 310 | ||||
-rw-r--r-- | v_windows/v/old/vlib/cli/flag_test.v | 216 | ||||
-rw-r--r-- | v_windows/v/old/vlib/cli/help.v | 172 | ||||
-rw-r--r-- | v_windows/v/old/vlib/cli/version.v | 25 |
7 files changed, 1279 insertions, 0 deletions
diff --git a/v_windows/v/old/vlib/cli/README.md b/v_windows/v/old/vlib/cli/README.md new file mode 100644 index 0000000..93d8d62 --- /dev/null +++ b/v_windows/v/old/vlib/cli/README.md @@ -0,0 +1,30 @@ +Usage 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' + execute: fn (cmd cli.Command) ? { + println('hello subcommand') + return + } + }, + ] + } + app.setup() + app.parse(os.args) +} +``` diff --git a/v_windows/v/old/vlib/cli/command.v b/v_windows/v/old/vlib/cli/command.v new file mode 100644 index 0000000..43940d1 --- /dev/null +++ b/v_windows/v/old/vlib/cli/command.v @@ -0,0 +1,305 @@ +module cli + +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 + 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 + sort_commands bool + parent &Command = 0 + commands []Command + flags []Flag + required_args int + args []string +} + +// str returns the `string` representation of the `Command`. +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 ...}' + } + res << ' commands: $cmd.commands' + res << ' flags: $cmd.flags' + res << ' required_args: $cmd.required_args' + res << ' args: $cmd.args' + 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 + if cmd.commands.contains(subcmd.name) { + println('Command with the name `$subcmd.name` already exists') + exit(1) + } + 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.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) { + println('Flag with the name `$flag.name` already exists') + exit(1) + } + cmd.flags << flag +} + +// parse parses `args` into this structured `Command`. +pub fn (mut cmd Command) parse(args []string) { + if !cmd.disable_flags { + cmd.add_default_flags() + } + cmd.add_default_commands() + 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` and +// `-v`/`--version` flags to the `Command`. +fn (mut cmd Command) add_default_flags() { + if !cmd.disable_help && !cmd.flags.contains('help') { + use_help_abbrev := !cmd.flags.contains('h') && cmd.flags.have_abbrev() + cmd.add_flag(help_flag(use_help_abbrev)) + } + 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)) + } +} + +// add_default_commands adds the command functions of the +// commonly used `help` and `version` flags to the `Command`. +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.disable_version && cmd.version != '' && !cmd.commands.contains('version') { + cmd.add_command(version_cmd()) + } +} + +fn (mut cmd Command) parse_flags() { + for { + if cmd.args.len < 1 || !cmd.args[0].starts_with('-') { + break + } + mut found := false + for i in 0 .. cmd.flags.len { + unsafe { + mut flag := &cmd.flags[i] + if flag.matches(cmd.args, cmd.flags.have_abbrev()) { + found = true + flag.found = true + 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('Command `$cmd.name` has no flag `${cmd.args[0]}`') + exit(1) + } + } +} + +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 { + mut command := cmd.commands[j] + if command.name == 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.disable_help { + 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 { + eprintln('Command `$cmd.name` needs at least $cmd.required_args arguments') + exit(1) + } + } + cmd.check_required_flags() + if !isnil(cmd.pre_execute) { + cmd.pre_execute(*cmd) or { + eprintln('cli preexecution error: $err') + exit(1) + } + } + if !isnil(cmd.execute) { + cmd.execute(*cmd) or { + eprintln('cli execution error: $err') + exit(1) + } + } + if !isnil(cmd.post_execute) { + cmd.post_execute(*cmd) or { + eprintln('cli postexecution error: $err') + exit(1) + } + } +} + +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 { + cmd.execute_help() + exit(0) + } + } +} + +fn (cmd Command) check_version_flag() { + 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 + if version_flag { + version_cmd := cmd.commands.get('version') or { return } // ignore error and handle command normally + version_cmd.execute(version_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() + println('Flag `$flag.name` is required by `$full_name`') + exit(1) + } + } +} + +// execute_help executes the callback registered +// for the `-h`/`--help` flag option. +pub 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) or { panic(err) } + } 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 in $cmds') +} + +fn (cmds []Command) contains(name string) bool { + for cmd in cmds { + if cmd.name == name { + return true + } + } + return false +} diff --git a/v_windows/v/old/vlib/cli/command_test.v b/v_windows/v/old/vlib/cli/command_test.v new file mode 100644 index 0000000..aae7199 --- /dev/null +++ b/v_windows/v/old/vlib/cli/command_test.v @@ -0,0 +1,221 @@ +import cli + +fn test_if_command_parses_empty_args() { + mut cmd := cli.Command{ + name: 'command' + execute: empty_func + } + cmd.parse(['command']) + assert cmd.name == 'command' && compare_arrays(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' && compare_arrays(cmd.args, ['arg0', 'arg1']) +} + +fn test_if_subcommands_parse_args() { + mut cmd := cli.Command{ + name: 'command' + } + subcmd := cli.Command{ + name: 'subcommand' + execute: if_subcommands_parse_args_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']) +} + +fn test_if_command_has_default_help_subcommand() { + mut cmd := cli.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' + } + cmd.parse(['command']) + assert has_command(cmd, 'version') +} + +fn flag_should_be_set(cmd cli.Command) ? { + flag := cmd.flags.get_string('flag') ? + assert flag == 'value' +} + +fn test_if_flag_gets_set() { + 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']) +} + +fn test_if_flag_gets_set_with_abbrev() { + 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']) +} + +fn test_if_flag_gets_set_with_long_arg() { + 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', '--flag', 'value']) +} + +fn flag_should_have_value_of_42(cmd cli.Command) ? { + flag := cmd.flags.get_string('flag') ? + assert flag == 'value' + value := cmd.flags.get_int('value') ? + assert value == 42 +} + +fn test_if_multiple_flags_get_set() { + mut cmd := cli.Command{ + name: 'command' + execute: flag_should_have_value_of_42 + } + cmd.add_flag(cli.Flag{ + flag: .string + name: 'flag' + }) + cmd.add_flag(cli.Flag{ + flag: .int + name: 'value' + }) + cmd.parse(['command', '-flag', 'value', '-value', '42']) +} + +fn test_if_required_flags_get_set() { + mut cmd := cli.Command{ + name: 'command' + execute: flag_should_have_value_of_42 + } + cmd.add_flag(cli.Flag{ + flag: .string + name: 'flag' + }) + cmd.add_flag(cli.Flag{ + flag: .int + name: 'value' + required: true + }) + 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) } + assert flag == 'value' +} + +fn test_if_flag_gets_set_in_subcommand() { + mut cmd := cli.Command{ + name: 'command' + execute: empty_func + } + 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']) +} + +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 + }) + subcmd := cli.Command{ + name: 'subcommand' + execute: flag_is_set_in_subcommand + } + cmd.add_command(subcmd) + cmd.parse(['command', '-flag', 'value', 'subcommand']) +} + +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' +} + +// helper functions +fn empty_func(cmd cli.Command) ? { +} + +fn has_command(cmd cli.Command, name string) bool { + for subcmd in cmd.commands { + if subcmd.name == name { + return true + } + } + return false +} + +fn compare_arrays(array0 []string, array1 []string) bool { + if array0.len != array1.len { + return false + } + for i in 0 .. array0.len { + if array0[i] != array1[i] { + return false + } + } + return true +} diff --git a/v_windows/v/old/vlib/cli/flag.v b/v_windows/v/old/vlib/cli/flag.v new file mode 100644 index 0000000..be84f6f --- /dev/null +++ b/v_windows/v/old/vlib/cli/flag.v @@ -0,0 +1,310 @@ +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 + // Desciption of flag + description string + global bool + // If flag is requierd + 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. +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) ? + return new_args + } else { + if flag.value.len > 0 && flag.flag != .int_array && flag.flag != .float_array + && flag.flag != .string_array { + return error('The argument `$flag.name` accept only one value!') + } + + new_args := flag.parse_raw(args) ? + return new_args + } + } else { + return args + } +} + +// matches returns `true` if first arg in `args` matches this flag. +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 { + 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 +} + +fn (flags []Flag) have_abbrev() bool { + mut have_abbrev := false + for flag in flags { + if flag.abbrev != '' { + have_abbrev = true + } + } + return have_abbrev +} + +// 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 + } +} diff --git a/v_windows/v/old/vlib/cli/flag_test.v b/v_windows/v/old/vlib/cli/flag_test.v new file mode 100644 index 0000000..a40a7c0 --- /dev/null +++ b/v_windows/v/old/vlib/cli/flag_test.v @@ -0,0 +1,216 @@ +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'] + + flags := [ + cli.Flag{ + flag: .string_array + name: 'flag' + value: ['a', 'b', 'c'] + }, + cli.Flag{ + flag: .string + name: 'flag2' + }, + ] + + values = flags.get_strings('flag') or { panic(err) } + assert values == ['a', 'b', 'c'] +} + +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] + + flags := [ + cli.Flag{ + flag: .int_array + name: 'flag' + value: ['1', '2', '3'] + }, + cli.Flag{ + flag: .int + name: 'flag2' + }, + ] + + values = flags.get_ints('flag') or { panic(err) } + assert values == [1, 2, 3] +} + +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] + + flags := [ + cli.Flag{ + flag: .float_array + name: 'flag' + value: ['1.1', '2.2', '3.3'] + }, + cli.Flag{ + flag: .float + name: 'flag2' + }, + ] + + values = flags.get_floats('flag') or { panic(err) } + assert values == [1.1, 2.2, 3.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 +} diff --git a/v_windows/v/old/vlib/cli/help.v b/v_windows/v/old/vlib/cli/help.v new file mode 100644 index 0000000..6ce3e41 --- /dev/null +++ b/v_windows/v/old/vlib/cli/help.v @@ -0,0 +1,172 @@ +module cli + +import term +import strings + +const ( + base_indent_len = 2 + min_description_indent_len = 20 + spacing = 2 +) + +fn help_flag(with_abbrev bool) Flag { + sabbrev := if with_abbrev { 'h' } else { '' } + return Flag{ + flag: .bool + name: 'help' + abbrev: sabbrev + description: 'Prints help information.' + } +} + +fn help_cmd() Command { + return Command{ + name: 'help' + usage: '<command>' + description: 'Prints help information.' + execute: print_help_for_command + } +} + +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 = unsafe { &sub_cmd } + found = true + break + } + } + if !found { + args := help_cmd.args.join(' ') + println('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' + } else { + for i in 0 .. cmd.required_args { + help += ' <arg$i>' + } + } + help += '\n' + if cmd.description != '' { + help += '\n$cmd.description\n' + } + mut abbrev_len := 0 + mut name_len := cli.min_description_indent_len + if cmd.flags.have_abbrev() { + for flag in cmd.flags { + abbrev_len = max(abbrev_len, flag.abbrev.len + cli.spacing + 1) // + 1 for '-' in front + name_len = max(name_len, abbrev_len + flag.name.len + cli.spacing + 2) // + 2 for '--' in front + } + for command in cmd.commands { + name_len = max(name_len, command.name.len + cli.spacing) + } + } else { + for flag in cmd.flags { + name_len = max(name_len, abbrev_len + flag.name.len + cli.spacing + 1) // + 1 for '-' in front + } + for command in cmd.commands { + name_len = max(name_len, command.name.len + cli.spacing) + } + } + if cmd.flags.len > 0 { + help += '\nFlags:\n' + for flag in cmd.flags { + mut flag_name := '' + 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.flags.have_abbrev() { + abbrev_indent := ' '.repeat(abbrev_len) + flag_name = '$abbrev_indent--$flag.name' + } else { + flag_name = '-$flag.name' + } + mut required := '' + if flag.required { + required = ' (required)' + } + base_indent := ' '.repeat(cli.base_indent_len) + description_indent := ' '.repeat(name_len - flag_name.len) + help += '$base_indent$flag_name$description_indent' + + pretty_description(flag.description + required, cli.base_indent_len + name_len) + + '\n' + } + } + if cmd.commands.len > 0 { + help += '\nCommands:\n' + for command in cmd.commands { + base_indent := ' '.repeat(cli.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' + } + } + return help +} + +// 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 line[i] != ` ` { + i-- + } + // 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 +} diff --git a/v_windows/v/old/vlib/cli/version.v b/v_windows/v/old/vlib/cli/version.v new file mode 100644 index 0000000..0f3f583 --- /dev/null +++ b/v_windows/v/old/vlib/cli/version.v @@ -0,0 +1,25 @@ +module cli + +fn version_flag(with_abbrev bool) Flag { + sabbrev := if with_abbrev { 'v' } else { '' } + return Flag{ + 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 + } +} + +fn version_func(version_cmd Command) ? { + cmd := version_cmd.parent + version := '$cmd.name version $cmd.version' + println(version) +} |