diff options
Diffstat (limited to 'v_windows/v/old/vlib/flag')
15 files changed, 1199 insertions, 0 deletions
diff --git a/v_windows/v/old/vlib/flag/README.md b/v_windows/v/old/vlib/flag/README.md new file mode 100644 index 0000000..1122f77 --- /dev/null +++ b/v_windows/v/old/vlib/flag/README.md @@ -0,0 +1,36 @@ +The `flag` module helps command-line flag parsing. +Main features are: +- parses flags like `-f` or '--flag' or '--stuff=things' or '--things stuff'. +- handles bool, int, float and string args. +- can print usage information listing all the declrared flags. +- handles unknown arguments as error. + +Usage example: + +```v +module main + +import os +import flag + +fn main() { + mut fp := flag.new_flag_parser(os.args) + fp.application('flag_example_tool') + fp.version('v0.0.1') + fp.limit_free_args(0, 0) // comment this, if you expect arbitrary texts after the options + fp.description('This tool is only designed to show how the flag lib is working') + fp.skip_executable() + an_int := fp.int('an_int', 0, 0o123, 'some int to define 0o123 is its default value') + a_bool := fp.bool('a_bool', 0, false, 'some boolean flag. --a_bool will set it to true.') + a_float := fp.float('a_float', 0, 1.0, 'some floating point value, by default 1.0 .') + a_string := fp.string('a_string', `a`, 'no text', 'finally, some text with ' + + ' `-a` as an abbreviation, so you can pass --a_string abc or just -a abc') + additional_args := fp.finalize() or { + eprintln(err) + println(fp.usage()) + return + } + println('an_int: $an_int | a_bool: $a_bool | a_float: $a_float | a_string: "$a_string" ') + println(additional_args.join_lines()) +} +``` diff --git a/v_windows/v/old/vlib/flag/default_flag_options_test.v b/v_windows/v/old/vlib/flag/default_flag_options_test.v new file mode 100644 index 0000000..fb0a423 --- /dev/null +++ b/v_windows/v/old/vlib/flag/default_flag_options_test.v @@ -0,0 +1,35 @@ +import os + +const source = 'vlib/flag/testdata/simplest_flag_program.v' + +const simple_flag_app_executable = os.real_path(os.join_path(os.cache_dir(), 'simple_flag_app.exe')) + +fn testsuite_begin() { + os.chdir(@VMODROOT) + os.rm(simple_flag_app_executable) or {} + res := os.execute('${@VEXE} -o $simple_flag_app_executable $source') + assert res.exit_code == 0 + assert os.execute(simple_flag_app_executable).exit_code == 0 +} + +fn testsuite_end() { + os.rm(simple_flag_app_executable) or {} + assert true +} + +fn check_program(opts string, extension string) { + result := source.replace('.v', extension) + res := os.execute('$simple_flag_app_executable $opts') + lines := os.read_lines(result) or { panic(err) } + assert res.exit_code == 0 + assert res.output.split_into_lines() == lines +} + +fn test_default_builtin_flag_options() { + check_program('', '.out') + check_program(' -- --help', '.dashdash.help.out') + check_program(' -- --version', '.dashdash.version.out') + check_program(' -h', '.help.out') + check_program(' --help', '.help.out') + check_program(' --version', '.version.out') +} diff --git a/v_windows/v/old/vlib/flag/flag.v b/v_windows/v/old/vlib/flag/flag.v new file mode 100644 index 0000000..85bb09d --- /dev/null +++ b/v_windows/v/old/vlib/flag/flag.v @@ -0,0 +1,624 @@ +module flag + +// data object storing information about a defined flag +pub struct Flag { +pub: + name string // name as it appears on command line + abbr byte // shortcut + usage string // help message + val_desc string // something like '<arg>' that appears in usage, + // and also the default value, when the flag is not given +} + +struct UnkownFlagError { + msg string + code int +} + +struct MinimumArgsCountError { + msg string + code int +} + +struct MaximumArgsCountError { + msg string + code int +} + +struct NoArgsExpectedError { + msg string + code int +} + +[unsafe] +fn (mut f Flag) free() { + unsafe { + f.name.free() + f.usage.free() + f.val_desc.free() + } +} + +pub fn (f Flag) str() string { + return '' + ' flag:\n' + ' name: $f.name\n' + + ' abbr: `$f.abbr.ascii_str()`\n' + ' usag: $f.usage\n' + + ' desc: $f.val_desc' +} + +pub fn (af []Flag) str() string { + mut res := []string{} + res << '\n []Flag = [' + for f in af { + res << f.str() + } + res << ' ]' + return res.join('\n') +} + +// +pub struct FlagParser { +pub: + original_args []string // the original arguments to be parsed + idx_dashdash int // the index of a `--`, -1 if there is not any + all_after_dashdash []string // all options after `--` are ignored, and will be passed to the application unmodified +pub mut: + usage_examples []string // when set, --help will print: + // Usage: $appname $usage_examples[0]` + // or: $appname $usage_examples[1]` + // etc + default_help_label string = 'display this help and exit' + default_version_label string = 'output version information and exit' + args []string // the current list of processed args + max_free_args int + flags []Flag // registered flags + application_name string + application_version string + application_description string + min_free_args int + args_description string + allow_unknown_args bool // whether passing undescribed arguments is allowed + footers []string // when set, --help will display all the collected footers at the bottom. +} + +[unsafe] +fn (mut f FlagParser) free() { + unsafe { + for a in f.args { + a.free() + } + f.args.free() + // + for flag in f.flags { + flag.free() + } + f.flags.free() + // + f.application_name.free() + f.application_version.free() + f.application_description.free() + f.args_description.free() + } +} + +pub const ( + // used for formating usage message + space = ' ' + underline = '-----------------------------------------------' + max_args_number = 4048 +) + +// create a new flag set for parsing command line arguments +pub fn new_flag_parser(args []string) &FlagParser { + original_args := args.clone() + idx_dashdash := args.index('--') + mut all_before_dashdash := args.clone() + mut all_after_dashdash := []string{} + if idx_dashdash >= 0 { + all_before_dashdash.trim(idx_dashdash) + if idx_dashdash < original_args.len { + all_after_dashdash = original_args[idx_dashdash + 1..] + } + } + return &FlagParser{ + original_args: original_args + idx_dashdash: idx_dashdash + all_after_dashdash: all_after_dashdash + args: all_before_dashdash + max_free_args: flag.max_args_number + } +} + +// usage_example - add an usage example +// All examples will be listed in the help screen. +// If you do not give any examples, then a default usage +// will be shown, based on whether the application takes +// options and expects additional parameters. +pub fn (mut fs FlagParser) usage_example(example string) { + fs.usage_examples << example +} + +// add_footer - add a footnote, that will be shown +// at the bottom of the help screen. +pub fn (mut fs FlagParser) footer(footer string) { + fs.footers << footer +} + +// change the application name to be used in 'usage' output +pub fn (mut fs FlagParser) application(name string) { + fs.application_name = name +} + +// change the application version to be used in 'usage' output +pub fn (mut fs FlagParser) version(vers string) { + fs.application_version = vers +} + +// description appends to the application description lines, shown +// in the help/usage screen +pub fn (mut fs FlagParser) description(desc string) { + if fs.application_description.len == 0 { + fs.application_description = desc + } else { + fs.application_description += '\n$desc' + } +} + +// in most cases you do not need the first argv for flag parsing +pub fn (mut fs FlagParser) skip_executable() { + fs.args.delete(0) +} + +// allow_unknown_args - if your program has sub commands, that have +// their own arguments, you can call .allow_unknown_args(), so that +// the subcommand arguments (which generally are not known to your +// parent program), will not cause the validation in .finalize() to fail. +pub fn (mut fs FlagParser) allow_unknown_args() { + fs.allow_unknown_args = true +} + +// private helper to register a flag +fn (mut fs FlagParser) add_flag(name string, abbr byte, usage string, desc string) { + fs.flags << Flag{ + name: name + abbr: abbr + usage: usage + val_desc: desc + } +} + +// private: general parsing a single argument +// - search args for existence +// if true +// extract the defined value as string +// else +// return an (dummy) error -> argument is not defined +// +// - the name, usage are registered +// - found arguments and corresponding values are removed from args list +[manualfree] +fn (mut fs FlagParser) parse_value(longhand string, shorthand byte) []string { + full := '--$longhand' + defer { + unsafe { full.free() } + } + mut found_entries := []string{} + mut to_delete := []int{} + defer { + unsafe { to_delete.free() } + } + mut should_skip_one := false + for i, arg in fs.args { + if should_skip_one { + should_skip_one = false + continue + } + if arg.len == 0 || arg[0] != `-` { + continue + } + if (arg.len == 2 && arg[0] == `-` && arg[1] == shorthand) || arg == full { + if i + 1 >= fs.args.len { + return [] + } + nextarg := fs.args[i + 1] + if nextarg.len > 2 { + nextarg_rest := nextarg[..2] + if nextarg_rest == '--' { + // It could be end of input (--) or another argument (--abc). + // Both are invalid so die. + unsafe { nextarg_rest.free() } + return [] + } + unsafe { nextarg_rest.free() } + } + found_entries << fs.args[i + 1] + to_delete << i + to_delete << i + 1 + should_skip_one = true + continue + } + if arg.len > full.len + 1 && arg[..full.len + 1] == '$full=' { + found_entries << arg[full.len + 1..] + to_delete << i + continue + } + } + for i, del in to_delete { + // i entrys are deleted so it's shifted left i times. + fs.args.delete(del - i) + } + return found_entries +} + +// special parsing for bool values +// see also: parse_value +// +// special: it is allowed to define bool flags without value +// -> '--flag' is parsed as true +// -> '--flag' is equal to '--flag=true' +fn (mut fs FlagParser) parse_bool_value(longhand string, shorthand byte) ?string { + { + full := '--$longhand' + for i, arg in fs.args { + if arg.len == 0 { + continue + } + if arg[0] != `-` { + continue + } + if (arg.len == 2 && arg[0] == `-` && arg[1] == shorthand) || arg == full { + if fs.args.len > i + 1 && (fs.args[i + 1] in ['true', 'false']) { + val := fs.args[i + 1] + fs.args.delete(i + 1) + fs.args.delete(i) + return val + } else { + fs.args.delete(i) + return 'true' + } + } + if arg.len > full.len + 1 && arg[..full.len + 1] == '$full=' { + // Flag abc=true + val := arg[full.len + 1..] + fs.args.delete(i) + return val + } + if arg.len > 1 && arg[0] == `-` && arg[1] != `-` && arg.index_byte(shorthand) != -1 { + // -abc is equivalent to -a -b -c + return 'true' + } + } + } + return error("parameter '$longhand' not found") +} + +// bool_opt returns an optional that returns the value associated with the flag. +// In the situation that the flag was not provided, it returns null. +pub fn (mut fs FlagParser) bool_opt(name string, abbr byte, usage string) ?bool { + mut res := false + { + fs.add_flag(name, abbr, usage, '<bool>') + parsed := fs.parse_bool_value(name, abbr) or { + return error("parameter '$name' not provided") + } + res = parsed == 'true' + } + return res +} + +// defining and parsing a bool flag +// if defined +// the value is returned (true/false) +// else +// the default value is returned +// version with abbr +// TODO error handling for invalid string to bool conversion +pub fn (mut fs FlagParser) bool(name string, abbr byte, bdefault bool, usage string) bool { + value := fs.bool_opt(name, abbr, usage) or { return bdefault } + return value +} + +// int_multi returns all instances of values associated with the flags provided +// In the case that none were found, it returns an empty array. +pub fn (mut fs FlagParser) int_multi(name string, abbr byte, usage string) []int { + fs.add_flag(name, abbr, usage, '<multiple ints>') + parsed := fs.parse_value(name, abbr) + mut value := []int{} + for val in parsed { + value << val.int() + } + return value +} + +// int_opt returns an optional that returns the value associated with the flag. +// In the situation that the flag was not provided, it returns null. +pub fn (mut fs FlagParser) int_opt(name string, abbr byte, usage string) ?int { + mut res := 0 + { + fs.add_flag(name, abbr, usage, '<int>') + parsed := fs.parse_value(name, abbr) + if parsed.len == 0 { + return error("parameter '$name' not provided") + } + parsed0 := parsed[0] + res = parsed0.int() + } + return res +} + +// defining and parsing an int flag +// if defined +// the value is returned (int) +// else +// the default value is returned +// version with abbr +// TODO error handling for invalid string to int conversion +pub fn (mut fs FlagParser) int(name string, abbr byte, idefault int, usage string) int { + value := fs.int_opt(name, abbr, usage) or { return idefault } + return value +} + +// float_multi returns all instances of values associated with the flags provided +// In the case that none were found, it returns an empty array. +pub fn (mut fs FlagParser) float_multi(name string, abbr byte, usage string) []f64 { + fs.add_flag(name, abbr, usage, '<multiple floats>') + parsed := fs.parse_value(name, abbr) + mut value := []f64{} + for val in parsed { + value << val.f64() + } + return value +} + +// float_opt returns an optional that returns the value associated with the flag. +// In the situation that the flag was not provided, it returns null. +pub fn (mut fs FlagParser) float_opt(name string, abbr byte, usage string) ?f64 { + mut res := 0.0 + { + fs.add_flag(name, abbr, usage, '<float>') + parsed := fs.parse_value(name, abbr) + if parsed.len == 0 { + return error("parameter '$name' not provided") + } + res = parsed[0].f64() + } + return res +} + +// defining and parsing a float flag +// if defined +// the value is returned (float) +// else +// the default value is returned +// version with abbr +// TODO error handling for invalid string to float conversion +pub fn (mut fs FlagParser) float(name string, abbr byte, fdefault f64, usage string) f64 { + value := fs.float_opt(name, abbr, usage) or { return fdefault } + return value +} + +// string_multi returns all instances of values associated with the flags provided +// In the case that none were found, it returns an empty array. +pub fn (mut fs FlagParser) string_multi(name string, abbr byte, usage string) []string { + fs.add_flag(name, abbr, usage, '<multiple strings>') + return fs.parse_value(name, abbr) +} + +// string_opt returns an optional that returns the value associated with the flag. +// In the situation that the flag was not provided, it returns null. +pub fn (mut fs FlagParser) string_opt(name string, abbr byte, usage string) ?string { + mut res := '' + { + fs.add_flag(name, abbr, usage, '<string>') + parsed := fs.parse_value(name, abbr) + if parsed.len == 0 { + return error("parameter '$name' not provided") + } + res = parsed[0] + } + return res +} + +// defining and parsing a string flag +// if defined +// the value is returned (string) +// else +// the default value is returned +// version with abbr +pub fn (mut fs FlagParser) string(name string, abbr byte, sdefault string, usage string) string { + value := fs.string_opt(name, abbr, usage) or { return sdefault } + return value +} + +pub fn (mut fs FlagParser) limit_free_args_to_at_least(n int) { + if n > flag.max_args_number { + panic('flag.limit_free_args_to_at_least expect n to be smaller than $flag.max_args_number') + } + if n <= 0 { + panic('flag.limit_free_args_to_at_least expect n to be a positive number') + } + fs.min_free_args = n +} + +pub fn (mut fs FlagParser) limit_free_args_to_exactly(n int) { + if n > flag.max_args_number { + panic('flag.limit_free_args_to_exactly expect n to be smaller than $flag.max_args_number') + } + if n < 0 { + panic('flag.limit_free_args_to_exactly expect n to be a non negative number') + } + fs.min_free_args = n + fs.max_free_args = n +} + +// this will cause an error in finalize() if free args are out of range +// (min, ..., max) +pub fn (mut fs FlagParser) limit_free_args(min int, max int) { + if min > max { + panic('flag.limit_free_args expect min < max, got $min >= $max') + } + fs.min_free_args = min + fs.max_free_args = max +} + +pub fn (mut fs FlagParser) arguments_description(description string) { + fs.args_description = description +} + +// collect all given information and +pub fn (fs FlagParser) usage() string { + positive_min_arg := (fs.min_free_args > 0) + positive_max_arg := (fs.max_free_args > 0 && fs.max_free_args != flag.max_args_number) + no_arguments := (fs.min_free_args == 0 && fs.max_free_args == 0) + mut adesc := if fs.args_description.len > 0 { fs.args_description } else { '[ARGS]' } + if no_arguments { + adesc = '' + } + mut use := []string{} + if fs.application_version != '' { + use << '$fs.application_name $fs.application_version' + use << '$flag.underline' + } + if fs.usage_examples.len == 0 { + use << 'Usage: $fs.application_name [options] $adesc' + } else { + for i, example in fs.usage_examples { + if i == 0 { + use << 'Usage: $fs.application_name $example' + } else { + use << ' or: $fs.application_name $example' + } + } + } + use << '' + if fs.application_description != '' { + use << 'Description: $fs.application_description' + use << '' + } + // show a message about the [ARGS]: + if positive_min_arg || positive_max_arg || no_arguments { + if no_arguments { + use << 'This application does not expect any arguments' + use << '' + } else { + mut s := []string{} + if positive_min_arg { + s << 'at least $fs.min_free_args' + } + if positive_max_arg { + s << 'at most $fs.max_free_args' + } + if positive_min_arg && positive_max_arg && fs.min_free_args == fs.max_free_args { + s = ['exactly $fs.min_free_args'] + } + sargs := s.join(' and ') + use << 'The arguments should be $sargs in number.' + use << '' + } + } + if fs.flags.len > 0 { + use << 'Options:' + for f in fs.flags { + mut onames := []string{} + if f.abbr != 0 { + onames << '-$f.abbr.ascii_str()' + } + if f.name != '' { + if !f.val_desc.contains('<bool>') { + onames << '--$f.name $f.val_desc' + } else { + onames << '--$f.name' + } + } + option_names := ' ' + onames.join(', ') + mut xspace := '' + if option_names.len > flag.space.len - 2 { + xspace = '\n$flag.space' + } else { + xspace = flag.space[option_names.len..] + } + fdesc := '$option_names$xspace$f.usage' + use << fdesc + } + } + for footer in fs.footers { + use << footer + } + return use.join('\n').replace('- ,', ' ') +} + +fn (mut fs FlagParser) find_existing_flag(fname string) ?Flag { + for f in fs.flags { + if f.name == fname { + return f + } + } + return error('no such flag') +} + +fn (mut fs FlagParser) handle_builtin_options() { + mut show_version := false + mut show_help := false + fs.find_existing_flag('help') or { + show_help = fs.bool('help', `h`, false, fs.default_help_label) + } + fs.find_existing_flag('version') or { + show_version = fs.bool('version', 0, false, fs.default_version_label) + } + if show_help { + println(fs.usage()) + exit(0) + } + if show_version { + println('$fs.application_name $fs.application_version') + exit(0) + } +} + +// finalize - return all remaining arguments (non options). +// Call .finalize() after all arguments are defined. +// The remaining arguments are returned in the same order they are +// defined on the command line. If additional flags are found, i.e. +// (things starting with '--' or '-'), it returns an error. +pub fn (mut fs FlagParser) finalize() ?[]string { + fs.handle_builtin_options() + mut remaining := fs.args.clone() + if !fs.allow_unknown_args { + for a in remaining { + if (a.len >= 2 && a[..2] == '--') || (a.len == 2 && a[0] == `-`) { + return IError(&UnkownFlagError{ + msg: 'Unknown flag `$a`' + }) + } + } + } + if remaining.len < fs.min_free_args && fs.min_free_args > 0 { + return IError(&MinimumArgsCountError{ + msg: 'Expected at least $fs.min_free_args arguments, but given $remaining.len' + }) + } + if remaining.len > fs.max_free_args && fs.max_free_args > 0 { + return IError(&MaximumArgsCountError{ + msg: 'Expected at most $fs.max_free_args arguments, but given $remaining.len' + }) + } + if remaining.len > 0 && fs.max_free_args == 0 && fs.min_free_args == 0 { + return IError(&NoArgsExpectedError{ + msg: 'Expected no arguments, but given $remaining.len' + }) + } + remaining << fs.all_after_dashdash + return remaining +} + +// remaining_parameters will return all remaining parameters. +// Call .remaining_parameters() *AFTER* you have defined all options +// that your program needs. remaining_parameters will also print any +// parsing errors and stop the program. Use .finalize() instead, if +// you want more control over the error handling. +pub fn (mut fs FlagParser) remaining_parameters() []string { + return fs.finalize() or { + eprintln(err.msg) + println(fs.usage()) + exit(1) + } +} diff --git a/v_windows/v/old/vlib/flag/flag_test.v b/v_windows/v/old/vlib/flag/flag_test.v new file mode 100644 index 0000000..8326193 --- /dev/null +++ b/v_windows/v/old/vlib/flag/flag_test.v @@ -0,0 +1,412 @@ +import flag + +fn test_if_flag_not_given_return_default_values() { + mut fp := flag.new_flag_parser([]) + assert false == fp.bool('a_bool', 0, false, '') + assert 42 == fp.int('an_int', 0, 42, '') + assert 1.0 == fp.float('a_float', 0, 1.0, '') + assert 'stuff' == fp.string('a_string', 0, 'stuff', '') +} + +fn test_could_define_application_name_and_version() { + mut fp := flag.new_flag_parser([]) + fp.application('test app') + fp.version('0.0.42') + fp.description('some text') + assert fp.application_name == 'test app' + assert fp.application_version == '0.0.42' + assert fp.application_description == 'some text' +} + +fn test_bool_flags_do_not_need_an_value() { + mut fp := flag.new_flag_parser(['--a_bool']) + assert true == fp.bool('a_bool', 0, false, '') +} + +fn test_flags_could_be_defined_with_eq() { + mut fp := flag.new_flag_parser([ + '--an_int=42', + '--a_float=2.0', + '--bool_without', + '--a_string=stuff', + '--a_bool=true', + ]) + assert 42 == fp.int('an_int', 0, 0o666, '') + assert true == fp.bool('a_bool', 0, false, '') + assert true == fp.bool('bool_without', 0, false, '') + assert 2.0 == fp.float('a_float', 0, 1.0, '') + assert 'stuff' == fp.string('a_string', 0, 'not_stuff', '') +} + +fn test_values_could_be_defined_without_eq() { + mut fp := flag.new_flag_parser([ + '--an_int', + '42', + '--a_float', + '2.0', + '--bool_without', + '--a_string', + 'stuff', + '--a_bool', + 'true', + ]) + assert 42 == fp.int('an_int', 0, 0o666, '') + assert true == fp.bool('a_bool', 0, false, '') + assert true == fp.bool('bool_without', 0, false, '') + assert 2.0 == fp.float('a_float', 0, 1.0, '') + assert 'stuff' == fp.string('a_string', 0, 'not_stuff', '') +} + +fn test_values_could_be_defined_mixed() { + mut fp := flag.new_flag_parser([ + '--an_int', + '42', + '--a_float=2.0', + '--bool_without', + '--a_string', + 'stuff', + '--a_bool=true', + ]) + assert 42 == fp.int('an_int', 0, 0o666, '') + assert true == fp.bool('a_bool', 0, false, '') + assert true == fp.bool('bool_without', 0, false, '') + assert 2.0 == fp.float('a_float', 0, 1.0, '') + assert 'stuff' == fp.string('a_string', 0, 'not_stuff', '') +} + +fn test_beaware_for_argument_names_with_same_prefix() { + mut fp := flag.new_flag_parser([ + '--short', + '5', + '--shorter=7', + ]) + assert 5 == fp.int('short', 0, 0o666, '') + assert 7 == fp.int('shorter', 0, 0o666, '') +} + +fn test_beaware_for_argument_names_with_same_prefix_inverse() { + mut fp := flag.new_flag_parser([ + '--shorter=7', + '--short', + '5', + ]) + assert 5 == fp.int('short', 0, 0o666, '') + assert 7 == fp.int('shorter', 0, 0o666, '') +} + +fn test_allow_to_skip_executable_path() { + mut fp := flag.new_flag_parser(['./path/to/execuable']) + fp.skip_executable() + args := fp.finalize() or { + assert false + return + } + assert !args.contains('./path/to/execuable') +} + +fn test_none_flag_arguments_are_allowed() { + mut fp := flag.new_flag_parser([ + 'file1', + '--an_int=2', + 'file2', + 'file3', + '--bool_without', + 'file4', + '--outfile', + 'outfile', + ]) + assert 2 == fp.int('an_int', 0, 0o666, '') + assert 'outfile' == fp.string('outfile', 0, 'bad', '') + assert true == fp.bool('bool_without', 0, false, '') +} + +fn test_finalize_returns_none_flag_arguments_ordered() { + mut fp := flag.new_flag_parser(['d', 'b', 'x', 'a', '--outfile', 'outfile']) + fp.string('outfile', 0, 'bad', '') + finalized := fp.finalize() or { + assert false + return + } + expected := ['d', 'b', 'x', 'a'] + for i, v in finalized { + assert v == expected[i] + } +} + +fn test_finalize_returns_error_for_unknown_flags_long() { + mut fp := flag.new_flag_parser(['--known', '--unknown']) + fp.bool('known', 0, false, '') + finalized := fp.finalize() or { + assert err.msg == 'Unknown flag `--unknown`' + return + } + assert finalized.len < 0 // expect error to be returned +} + +fn test_finalize_returns_error_for_unknown_flags_short() { + mut fp := flag.new_flag_parser(['--known', '-x']) + fp.bool('known', 0, false, '') + finalized := fp.finalize() or { + assert err.msg == 'Unknown flag `-x`' + return + } + assert finalized.len < 0 // expect error to be returned +} + +fn test_allow_to_build_usage_message() { + mut fp := flag.new_flag_parser([]) + fp.limit_free_args(1, 4) + fp.application('flag_tool') + fp.version('v0.0.0') + fp.description('some short information about this tool') + fp.int('an_int', 0, 0o666, 'some int to define') + fp.bool('a_bool', 0, false, 'some bool to define') + fp.bool('bool_without_but_really_big', 0, false, 'this should appear on the next line') + fp.float('a_float', 0, 1.0, 'some float as well') + fp.string('a_string', 0, 'not_stuff', 'your credit card number') + usage := fp.usage() + mut all_strings_found := true + for s in ['flag_tool', 'v0.0.0', 'an_int <int>', 'a_bool', 'bool_without', 'a_float <float>', + 'a_string <string>', 'some int to define', 'some bool to define', + 'this should appear on the next line', 'some float as well', 'your credit card number', + 'The arguments should be at least 1 and at most 4 in number.', 'Usage', 'Options:', + 'Description:', 'some short information about this tool'] { + if !usage.contains(s) { + eprintln(" missing '$s' in usage message") + all_strings_found = false + } + } + assert all_strings_found +} + +fn test_if_no_description_given_usage_message_does_not_contain_descpription() { + mut fp := flag.new_flag_parser([]) + fp.application('flag_tool') + fp.version('v0.0.0') + fp.bool('a_bool', 0, false, '') + assert !fp.usage().contains('Description:') +} + +fn test_if_no_options_given_usage_message_does_not_contain_options() { + mut fp := flag.new_flag_parser([]) + fp.application('flag_tool') + fp.version('v0.0.0') + assert !fp.usage().contains('Options:') +} + +fn test_free_args_could_be_limited() { + mut fp1 := flag.new_flag_parser(['a', 'b', 'c']) + fp1.limit_free_args(1, 4) + args := fp1.finalize() or { + assert false + return + } + assert args[0] == 'a' + assert args[1] == 'b' + assert args[2] == 'c' +} + +fn test_error_for_to_few_free_args() { + mut fp1 := flag.new_flag_parser(['a', 'b', 'c']) + fp1.limit_free_args(5, 6) + args := fp1.finalize() or { + assert err.msg.starts_with('Expected at least 5 arguments') + return + } + assert args.len < 0 // expect an error and need to use args +} + +fn test_error_for_to_much_free_args() { + mut fp1 := flag.new_flag_parser(['a', 'b', 'c']) + fp1.limit_free_args(1, 2) + args := fp1.finalize() or { + assert err.msg.starts_with('Expected at most 2 arguments') + return + } + assert args.len < 0 // expect an error and need to use args +} + +fn test_could_expect_no_free_args() { + mut fp1 := flag.new_flag_parser(['a']) + fp1.limit_free_args(0, 0) + args := fp1.finalize() or { + assert err.msg.starts_with('Expected no arguments') + return + } + assert args.len < 0 // expect an error and need to use args +} + +fn test_allow_abreviations() { + mut fp := flag.new_flag_parser(['-v', '-o', 'some_file', '-i', '42', '-f', '2.0']) + v := fp.bool('version', `v`, false, '') + o := fp.string('output', `o`, 'empty', '') + i := fp.int('count', `i`, 0, '') + f := fp.float('value', `f`, 0.0, '') + assert v == true + assert o == 'some_file' + assert i == 42 + assert f == 2.0 + u := fp.usage() + assert u.contains(' -v') + assert u.contains(' -o') + assert u.contains(' -i') + assert u.contains(' -f') + assert u.contains(' -o, --output <string>') + assert u.contains(' -i, --count <int>') + assert u.contains(' -f, --value <float>') +} + +fn test_allow_kebab_options() { + default_value := 'this_is_the_default_value_of_long_option' + long_option_value := 'this_is_a_long_option_value_as_argument' + mut fp := flag.new_flag_parser(['--my-long-flag', 'true', '--my-long-option', long_option_value]) + my_flag := fp.bool('my-long-flag', 0, false, 'flag with long-kebab-name') + my_option := fp.string('my-long-option', 0, default_value, 'string with long-kebab-name') + assert my_flag == true + assert my_option == long_option_value + u := fp.usage() + assert u.contains(' --my-long-flag') + assert u.contains(' --my-long-option') +} + +fn test_not_provided_option_is_not_returned() { + mut fp := flag.new_flag_parser([]) + fp.bool_opt('some-flag', `a`, '') or { + fp.int_opt('some-flag', `a`, '') or { + fp.float_opt('some-flag', `a`, '') or { + fp.string_opt('some-flag', `a`, '') or { + // Everything should not return + return + } + return + } + return + } + return + } + // If we reach here, one of them returned a value. + assert false +} + +fn test_provided_option_is_returned() { + mut fp := flag.new_flag_parser(['-a', '-b', '3', '-c', 'hello', '-d', '3.14']) + a := fp.bool_opt('some-flag', `a`, '') or { panic('bool_opt did not return a bool') } + b := fp.int_opt('some-flag', `b`, '') or { panic('int_opt did not return an int') } + c := fp.string_opt('some-flag', `c`, '') or { panic('string_opt did not return a string') } + d := fp.float_opt('some-flag', `d`, '') or { panic('float_opt did not return a float') } + assert true == a + assert b == 3 + assert c == 'hello' + assert d == 3.14 +} + +fn test_multiple_arguments() { + mut fp := flag.new_flag_parser([ + '-a', + '2', + '-a', + '3', + '-a', + '5', + '-b', + 'a', + '-b', + 'c', + '-b', + 'b', + '-c', + '1.23', + '-c', + '2.34', + '-c', + '3.45', + ]) + // TODO Move to array comparison once it's implemented + // assert fp.int_multi('some-flag', `a`, '') == [2, 3, 5] && + // fp.string_multi('some-flag', `b`, '') == ['a', 'c', 'b'] && + // fp.float_multi('some-flag', `c`, '') == [1.23, 2.34, 3.45] + a := fp.int_multi('some-flag', `a`, '') + b := fp.string_multi('some-flag', `b`, '') + c := fp.float_multi('some-flag', `c`, '') + assert a.len == 3 + assert b.len == 3 + assert c.len == 3 + assert a[0] == 2 + assert a[1] == 3 + assert a[2] == 5 + assert b[0] == 'a' + assert b[1] == 'c' + assert b[2] == 'b' + assert c[0] == 1.23 + assert c[1] == 2.34 + assert c[2] == 3.45 +} + +fn test_long_options_that_start_with_the_same_letter_as_another_short_option() { + mut fp := flag.new_flag_parser([ + '--vabc', + '/abc', + ]) + verbose := fp.bool('verbose', `v`, false, 'Be more verbose.') + vabc := fp.string('vabc', `x`, 'default', 'Another option that *may* conflict with v, but *should not*') + assert verbose == false + assert vabc == '/abc' +} + +fn test_long_options_that_start_with_the_same_letter_as_another_short_option_both_set() { + mut fp := flag.new_flag_parser([ + '-v', + '--vabc', + '/abc', + ]) + verbose := fp.bool('verbose', `v`, false, 'Be more verbose.') + vabc := fp.string('vabc', `x`, 'default', 'Another option that *may* conflict with v, but *should not*') + assert verbose == true + assert vabc == '/abc' +} + +fn test_single_dash() { + mut fp := flag.new_flag_parser([ + '-', + ]) + flag_update := fp.bool('update', `u`, false, 'Update tools') + assert flag_update == false +} + +fn test_optional_flags() { + mut fp := flag.new_flag_parser(['-a', '10', '-b']) + fp.int_opt('some-flag', `a`, '') or { + assert false + return + } + b := fp.string_opt('another-flag', `b`, '') or { 'some_default_value' } + assert b == 'some_default_value' +} + +fn test_dashdash_acts_as_parser_full_stop() ? { + mut fp := flag.new_flag_parser(['-b', '5', '--', '-d', '-x', '-b', '4', '-a', '-c', 'hello', + 'some', 'other', 'parameters']) + a := fp.bool_opt('a-bool-flag', `a`, '') or { false } + b := fp.int_opt('an-int-flag', `b`, '') or { -1 } + c := fp.string_opt('a-string-flag', `c`, '') or { 'default' } + assert a == false + assert b == 5 + assert c == 'default' + args := fp.finalize() ? + assert args.len > 0 + assert args[0] != '--' + assert args == ['-d', '-x', '-b', '4', '-a', '-c', 'hello', 'some', 'other', 'parameters'] +} + +fn test_dashdash_acts_as_parser_full_stop_dashdash_at_end() ? { + mut fp := flag.new_flag_parser(['-b', '5', '-b', '4', 'other', 'params', '--']) + b := fp.int_multi('an-int-flag', `b`, '') + assert b == [5, 4] + args := fp.finalize() ? + assert args.len > 0 +} + +fn test_empty_string_with_flag() { + mut fp := flag.new_flag_parser(['']) + s := fp.string('something', `s`, 'default', 'Hey parse me') +} diff --git a/v_windows/v/old/vlib/flag/testdata/simplest_flag_program.dashdash.help.out b/v_windows/v/old/vlib/flag/testdata/simplest_flag_program.dashdash.help.out new file mode 100644 index 0000000..2529e9f --- /dev/null +++ b/v_windows/v/old/vlib/flag/testdata/simplest_flag_program.dashdash.help.out @@ -0,0 +1 @@ +[vlib/flag/testdata/simplest_flag_program.v:13] rest_of_args: ['--help'] diff --git a/v_windows/v/old/vlib/flag/testdata/simplest_flag_program.dashdash.version.out b/v_windows/v/old/vlib/flag/testdata/simplest_flag_program.dashdash.version.out new file mode 100644 index 0000000..b0b4d65 --- /dev/null +++ b/v_windows/v/old/vlib/flag/testdata/simplest_flag_program.dashdash.version.out @@ -0,0 +1 @@ +[vlib/flag/testdata/simplest_flag_program.v:13] rest_of_args: ['--version'] diff --git a/v_windows/v/old/vlib/flag/testdata/simplest_flag_program.help.out b/v_windows/v/old/vlib/flag/testdata/simplest_flag_program.help.out new file mode 100644 index 0000000..9b5ab9e --- /dev/null +++ b/v_windows/v/old/vlib/flag/testdata/simplest_flag_program.help.out @@ -0,0 +1,7 @@ +abc 0.0.1 +----------------------------------------------- +Usage: abc [options] [ARGS] + +Options: + -h, --help display this help and exit + --version output version information and exit diff --git a/v_windows/v/old/vlib/flag/testdata/simplest_flag_program.out b/v_windows/v/old/vlib/flag/testdata/simplest_flag_program.out new file mode 100644 index 0000000..02b7cea --- /dev/null +++ b/v_windows/v/old/vlib/flag/testdata/simplest_flag_program.out @@ -0,0 +1 @@ +[vlib/flag/testdata/simplest_flag_program.v:13] rest_of_args: [] diff --git a/v_windows/v/old/vlib/flag/testdata/simplest_flag_program.v b/v_windows/v/old/vlib/flag/testdata/simplest_flag_program.v new file mode 100644 index 0000000..cee2ff7 --- /dev/null +++ b/v_windows/v/old/vlib/flag/testdata/simplest_flag_program.v @@ -0,0 +1,14 @@ +import os +import flag + +fn main() { + mut fp := flag.new_flag_parser(os.args) + fp.application('abc') + fp.version('0.0.1') + fp.skip_executable() + rest_of_args := fp.finalize() or { + eprintln(err) + exit(1) + } + dump(rest_of_args) +} diff --git a/v_windows/v/old/vlib/flag/testdata/simplest_flag_program.version.out b/v_windows/v/old/vlib/flag/testdata/simplest_flag_program.version.out new file mode 100644 index 0000000..de52cb0 --- /dev/null +++ b/v_windows/v/old/vlib/flag/testdata/simplest_flag_program.version.out @@ -0,0 +1 @@ +abc 0.0.1 diff --git a/v_windows/v/old/vlib/flag/testdata/usage_example.help.out b/v_windows/v/old/vlib/flag/testdata/usage_example.help.out new file mode 100644 index 0000000..b40047b --- /dev/null +++ b/v_windows/v/old/vlib/flag/testdata/usage_example.help.out @@ -0,0 +1,13 @@ +xyz 0.0.2 +----------------------------------------------- +Usage: xyz [NUMBER]... + or: xyz OPTION + +Description: description line 1 +description line 2 + +Options: + -h, --help display this help and exit + --version output version information and exit +footer 1 +footer 2 diff --git a/v_windows/v/old/vlib/flag/testdata/usage_example.out b/v_windows/v/old/vlib/flag/testdata/usage_example.out new file mode 100644 index 0000000..b2fd000 --- /dev/null +++ b/v_windows/v/old/vlib/flag/testdata/usage_example.out @@ -0,0 +1 @@ +[vlib/flag/testdata/usage_example.v:16] rest_of_args: ['abc', 'def'] diff --git a/v_windows/v/old/vlib/flag/testdata/usage_example.v b/v_windows/v/old/vlib/flag/testdata/usage_example.v new file mode 100644 index 0000000..522b758 --- /dev/null +++ b/v_windows/v/old/vlib/flag/testdata/usage_example.v @@ -0,0 +1,17 @@ +import os +import flag + +fn main() { + mut fp := flag.new_flag_parser(os.args) + fp.skip_executable() + fp.application('xyz') + fp.version('0.0.2') + fp.usage_example('[NUMBER]...') + fp.usage_example('OPTION') + fp.description('description line 1') + fp.description('description line 2') + fp.footer('footer 1') + fp.footer('footer 2') + rest_of_args := fp.remaining_parameters() + dump(rest_of_args) +} diff --git a/v_windows/v/old/vlib/flag/testdata/usage_example.version.out b/v_windows/v/old/vlib/flag/testdata/usage_example.version.out new file mode 100644 index 0000000..9196894 --- /dev/null +++ b/v_windows/v/old/vlib/flag/testdata/usage_example.version.out @@ -0,0 +1 @@ +xyz 0.0.2 diff --git a/v_windows/v/old/vlib/flag/usage_example_test.v b/v_windows/v/old/vlib/flag/usage_example_test.v new file mode 100644 index 0000000..30fbccc --- /dev/null +++ b/v_windows/v/old/vlib/flag/usage_example_test.v @@ -0,0 +1,35 @@ +import os + +const the_source = 'vlib/flag/testdata/usage_example.v' + +const the_executable = os.real_path(os.join_path(os.cache_dir(), 'flag_usage_example_app.exe')) + +fn testsuite_begin() { + os.chdir(@VMODROOT) + os.rm(the_executable) or {} + res := os.execute('${@VEXE} -o $the_executable $the_source') + assert res.exit_code == 0 + assert os.execute(the_executable).exit_code == 0 + C.atexit(fn () { + os.rm(the_executable) or {} + }) +} + +fn normalise_lines(lines []string) string { + return '\n' + lines.join('\n') +} + +fn check_program(opts string, extension string) { + result := the_source.replace('.v', extension) + res := os.execute('$the_executable $opts') + assert res.exit_code == 0 + assert normalise_lines(res.output.split_into_lines()) == normalise_lines(os.read_lines(result) or { + panic(err) + }) +} + +fn test_normal_usage() { + check_program('abc def', '.out') + check_program(' --help', '.help.out') + check_program(' --version', '.version.out') +} |