diff --git a/vlib/flag/flag.v b/vlib/flag/flag.v index 4e20128d26..dadd157987 100644 --- a/vlib/flag/flag.v +++ b/vlib/flag/flag.v @@ -5,35 +5,35 @@ module flag // - parsing flags like '--flag' or '--stuff=things' or '--things stuff' // - handles bool, int, float and string args // - is able to print usage -// - handled unknown arguments as error -// +// - handled 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.0') // fp.description('This tool is only designed to show how the flag lib is working') -// +// // fp.skip_executable() -// +// // an_int := fp.int('an_int', 666, 'some int to define 666 is default') // a_bool := fp.bool('a_bool', false, 'some \'real\' flag') // a_float := fp.float('a_float', 1.0, 'also floats') // a_string := fp.string('a_string', 'no text', 'finally, some text') -// +// // additional_args := fp.finalize() or { // eprintln(err) // println(fp.usage()) // return // } -// +// // println(' // an_int: $an_int // a_bool: $a_bool @@ -46,316 +46,418 @@ 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 '' that appears in usage, - // and also the default value, when the flag is not given + pub: + name string // name as it appears on command line + abbr byte // shortcut + usage string // help message + val_desc string // something like '' that appears in usage, + // and also the default value, when the flag is not given } -// +// pub struct FlagParser { -pub mut: - args []string // the arguments to be parsed - flags []Flag // registered flags + pub mut: + args []string // the arguments to be parsed + flags []Flag // registered flags - application_name string - application_version string - application_description string + application_name string + application_version string + application_description string - min_free_args int - max_free_args int - args_description string + min_free_args int + max_free_args int + args_description string } pub const ( - // used for formating usage message - SPACE = ' ' - UNDERLINE = '-----------------------------------------------' - MAX_ARGS_NUMBER = 4048 + // used for formating usage message + SPACE = ' ' + UNDERLINE = '-----------------------------------------------' + MAX_ARGS_NUMBER = 4048 ) // create a new flag set for parsing command line arguments // TODO use INT_MAX some how pub fn new_flag_parser(args []string) &FlagParser { - return &FlagParser{args:args, max_free_args: MAX_ARGS_NUMBER} + return &FlagParser{args:args, max_free_args: MAX_ARGS_NUMBER} } // change the application name to be used in 'usage' output pub fn (fs mut FlagParser) application(n string) { - fs.application_name = n + fs.application_name = n } // change the application version to be used in 'usage' output pub fn (fs mut FlagParser) version(n string) { - fs.application_version = n + fs.application_version = n } // change the application version to be used in 'usage' output pub fn (fs mut FlagParser) description(n string) { - fs.application_description = n + fs.application_description = n } // in most cases you do not need the first argv for flag parsing pub fn (fs mut FlagParser) skip_executable() { - fs.args.delete(0) + fs.args.delete(0) } // private helper to register a flag fn (fs mut FlagParser) add_flag(n string, a byte, u, vd string) { - fs.flags << Flag{ - name: n, - abbr: a, - usage: u, - val_desc: vd - } + fs.flags << Flag{ + name: n, + abbr: a, + usage: u, + val_desc: vd + } } -// private: general parsing a single argument +// private: general parsing a single argument // - search args for existence // if true // extract the defined value as string -// else +// else // return an (dummy) error -> argument is not defined // // - the name, usage are registered // - found arguments and corresponding values are removed from args list -fn (fs mut FlagParser) parse_value(n string, ab byte) ?string { - c := '--$n' - for i, a in fs.args { - if a == c || (a.len == 2 && a[1] == ab) { - if i+1 > fs.args.len { panic('Missing argument for \'$n\'') } - nextarg := fs.args[i+1] - if nextarg.limit(2) == '--' { panic('Missing argument for \'$n\'') } - val := fs.args[i+1] - fs.args.delete(i+1) - fs.args.delete(i) - return val - } else if a.len > c.len && c == a[..c.len] && a[c.len..c.len+1] == '=' { - val := a[c.len+1..] - fs.args.delete(i) - return val - } - } - return error('parameter \'$n\' not found') +fn (fs mut FlagParser) parse_value(longhand string, shorthand byte) []string { + full := '--$longhand' + mut found_entries := []string + mut to_delete := []int + mut should_skip_one := false + for i, arg in fs.args { + if should_skip_one { + should_skip_one = false + continue + } + if arg == '--' { + //End of input. We're done here. + break + } + if arg == full || (arg[0] == `-` && arg[1] == shorthand && arg.len == 2) { + if i+1 > fs.args.len { + panic("Missing argument for '$longhand'") + } + nextarg := fs.args[i+1] + if nextarg.len > 2 && nextarg[..2] == '--' { + //It could be end of input (--) or another argument (--abc). + //Both are invalid so die. + panic("Missing argument for '$longhand'") + } + 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 +// 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 (fs mut FlagParser) parse_bool_value(n string, ab byte) ?string { - c := '--$n' - for i, a in fs.args { - if a == c || (a.len == 2 && a[1] == ab) { - 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 { - val := 'true' - fs.args.delete(i) - return val - } - } else if a.len > c.len && c == a[..c.len] && a[c.len..c.len+1] == '=' { - val := a[c.len+1..] - fs.args.delete(i) - return val - } - } - return error('parameter \'$n\' not found') +fn (fs mut FlagParser) parse_bool_value(longhand string, shorthand byte) ?string { + full := '--$longhand' + for i, arg in fs.args { + if arg == '--' { + //End of input. We're done. + break + } + if arg == full || (arg[0] == `-` && arg[1] == shorthand && arg.len == 2) { + 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[0] == `-` && arg.index_byte(shorthand) != -1 { + // -abc is equivalent to -a -b -c + return 'true' + } + } + return error("parameter '$longhand' not found") } -// defining and parsing a bool flag -// if defined +// 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 (fs mut FlagParser) bool_opt(n string, a byte, u string) ?bool { + fs.add_flag(n, a, u, '') + parsed := fs.parse_bool_value(n, a) or { + return error("parameter '$n' not provided") + } + return parsed == 'true' +} + +// defining and parsing a bool flag +// if defined // the value is returned (true/false) -// else +// else // the default value is returned // version with abbreviation //TODO error handling for invalid string to bool conversion pub fn (fs mut FlagParser) bool_(n string, a byte, v bool, u string) bool { - fs.add_flag(n, a, u, ':'+v.str()) - parsed := fs.parse_bool_value(n, a) or { - return v - } - return parsed == 'true' + value := fs.bool_opt(n, a, u) or { + return v + } + return value } -// defining and parsing a bool flag -// if defined +// defining and parsing a bool flag +// if defined // the value is returned (true/false) -// else +// else // the default value is returned //TODO error handling for invalid string to bool conversion pub fn (fs mut FlagParser) bool(n string, v bool, u string) bool { - return fs.bool_(n, `\0`, v, u) + return fs.bool_(n, `\0`, v, u) } -// defining and parsing an int flag -// if defined +// 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 (fs mut FlagParser) int_multi(n string, a byte, u string) []int { + fs.add_flag(n, a, u, '') + parsed := fs.parse_value(n, a) + 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 (fs mut FlagParser) int_opt(n string, a byte, u string) ?int { + fs.add_flag(n, a, u, '') + parsed := fs.parse_value(n, a) + if parsed.len == 0 { + return error("parameter '$n' not provided") + } + return parsed[0].int() +} + +// defining and parsing an int flag +// if defined // the value is returned (int) -// else +// else // the default value is returned // version with abbreviation //TODO error handling for invalid string to int conversion pub fn (fs mut FlagParser) int_(n string, a byte, i int, u string) int { - fs.add_flag(n, a, u, ':$i') - parsed := fs.parse_value(n, a) or { - return i - } - return parsed.int() + value := fs.int_opt(n, a, u) or { + return i + } + return value } -// defining and parsing an int flag -// if defined +// defining and parsing an int flag +// if defined // the value is returned (int) -// else +// else // the default value is returned //TODO error handling for invalid string to int conversion pub fn (fs mut FlagParser) int(n string, i int, u string) int { - return fs.int_(n, `\0`, i, u) + return fs.int_(n, `\0`, i, u) } -// defining and parsing a float flag -// if defined +// 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 (fs mut FlagParser) float_multi(n string, a byte, u string) []f32 { + fs.add_flag(n, a, u, '') + parsed := fs.parse_value(n, a) + mut value := []f32 + for val in parsed { + value << val.f32() + } + 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 (fs mut FlagParser) float_opt(n string, a byte, u string) ?f32 { + fs.add_flag(n, a, u, '') + parsed := fs.parse_value(n, a) + if parsed.len == 0 { + return error("parameter '$n' not provided") + } + return parsed[0].f32() +} + +// defining and parsing a float flag +// if defined // the value is returned (float) -// else +// else // the default value is returned // version with abbreviation //TODO error handling for invalid string to float conversion pub fn (fs mut FlagParser) float_(n string, a byte, f f32, u string) f32 { - fs.add_flag(n, a, u, ':$f') - parsed := fs.parse_value(n, a) or { - return f - } - return parsed.f32() + value := fs.float_opt(n, a, u) or { + return f + } + return value } -// defining and parsing a float flag -// if defined +// defining and parsing a float flag +// if defined // the value is returned (float) -// else +// else // the default value is returned //TODO error handling for invalid string to float conversion pub fn (fs mut FlagParser) float(n string, f f32, u string) f32 { - return fs.float_(n, `\0`, f, u) + return fs.float_(n, `\0`, f, u) } -// defining and parsing a string flag -// if defined +// 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 (fs mut FlagParser) string_multi(n string, a byte, u string) []string { + fs.add_flag(n, a, u, '') + return fs.parse_value(n, a) +} + +// 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 (fs mut FlagParser) string_opt(n string, a byte, u string) ?string { + fs.add_flag(n, a, u, '') + parsed := fs.parse_value(n, a) + if parsed.len == 0 { + return error("parameter '$n' not provided") + } + return parsed[0] +} + +// defining and parsing a string flag +// if defined // the value is returned (string) -// else +// else // the default value is returned // version with abbreviation pub fn (fs mut FlagParser) string_(n string, a byte, v, u string) string { - fs.add_flag(n, a, u, ':$v') - parsed := fs.parse_value(n, a) or { - return v - } - return parsed + value := fs.string_opt(n, a, u) or { + return v + } + return value } -// defining and parsing a string flag -// if defined +// defining and parsing a string flag +// if defined // the value is returned (string) -// else +// else // the default value is returned pub fn (fs mut FlagParser) string(n, v, u string) string { - return fs.string_(n, `\0`, v, u) + return fs.string_(n, `\0`, v, u) } pub fn (fs mut FlagParser) limit_free_args_to_at_least(n int) { if n > MAX_ARGS_NUMBER { - panic('flag.limit_free_args_to_at_least expect n to be smaller than $MAX_ARGS_NUMBER') - } + panic('flag.limit_free_args_to_at_least expect n to be smaller than $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 + panic('flag.limit_free_args_to_at_least expect n to be a positive number') + } + fs.min_free_args = n } pub fn (fs mut FlagParser) limit_free_args_to_exactly(n int) { if n > MAX_ARGS_NUMBER { - panic('flag.limit_free_args_to_exactly expect n to be smaller than $MAX_ARGS_NUMBER') - } + panic('flag.limit_free_args_to_exactly expect n to be smaller than $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 + 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 (fs mut FlagParser) limit_free_args(min, 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 + 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 (fs mut FlagParser) arguments_description(description string){ - fs.args_description = description + fs.args_description = description } -// collect all given information and +// 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 != 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 := '' - use += '$fs.application_name $fs.application_version\n' - use += '$UNDERLINE\n' - use += 'Usage: ${fs.application_name} [options] $adesc\n' - use += '\n' - if fs.application_description != '' { - use += 'Description:\n' - use += '$fs.application_description' - use += '\n\n' - } - - // 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\n\n' - goto end_of_arguments_handling - } - 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.\n\n' - } - end_of_arguments_handling: + positive_min_arg := ( fs.min_free_args > 0 ) + positive_max_arg := ( fs.max_free_args > 0 && fs.max_free_args != MAX_ARGS_NUMBER ) + no_arguments := ( fs.min_free_args == 0 && fs.max_free_args == 0 ) - if fs.flags.len > 0 { - use += 'Options:\n' - for f in fs.flags { - flag_desc := ' --$f.name $f.val_desc' - space := if flag_desc.len > SPACE.len-2 { - '\n$SPACE' - } else { - SPACE[flag_desc.len..] - } - abbr_desc := if f.abbr == `\0` { '' } else { ' -${tos(f.abbr, 1)}\n' } - use += '${abbr_desc}${flag_desc}${space}${f.usage}\n' - } - } - - return use + mut adesc := if fs.args_description.len > 0 { fs.args_description } else { '[ARGS]' } + if no_arguments { adesc = '' } + + mut use := '' + use += '$fs.application_name $fs.application_version\n' + use += '$UNDERLINE\n' + use += 'Usage: ${fs.application_name} [options] $adesc\n' + use += '\n' + if fs.application_description != '' { + use += 'Description:\n' + use += '$fs.application_description' + use += '\n\n' + } + + // 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\n\n' + goto end_of_arguments_handling + } + 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.\n\n' + } + end_of_arguments_handling: + + if fs.flags.len > 0 { + use += 'Options:\n' + for f in fs.flags { + flag_desc := ' --$f.name $f.val_desc' + space := if flag_desc.len > SPACE.len-2 { + '\n$SPACE' + } else { + SPACE[flag_desc.len..] + } + abbr_desc := if f.abbr == `\0` { '' } else { ' -${tos(f.abbr, 1)}\n' } + use += '${abbr_desc}${flag_desc}${space}${f.usage}\n' + } + } + + return use } // finalize argument parsing -> call after all arguments are defined @@ -363,23 +465,23 @@ pub fn (fs FlagParser) usage() string { // all remaining arguments are returned in the same order they are defined on // command line // -// if additional flag are found (things starting with '--') an error is returned +// if additional flag are found (things starting with '--') an error is returned // error handling is up to the application developer pub fn (fs FlagParser) finalize() ?[]string { - for a in fs.args { - if a.len >= 2 && a[..2] == '--' { - return error('Unknown argument \'${a[2..]}\'') - } - } - if fs.args.len < fs.min_free_args && fs.min_free_args > 0 { - return error('Expected at least ${fs.min_free_args} arguments, but given $fs.args.len') - } - if fs.args.len > fs.max_free_args && fs.max_free_args > 0 { - return error('Expected at most ${fs.max_free_args} arguments, but given $fs.args.len') - } - if fs.args.len > 0 && fs.max_free_args == 0 && fs.min_free_args == 0 { - return error('Expected no arguments, but given $fs.args.len') - } - return fs.args + for a in fs.args { + if a.len >= 2 && a[..2] == '--' { + return error('Unknown argument \'${a[2..]}\'') + } + } + if fs.args.len < fs.min_free_args && fs.min_free_args > 0 { + return error('Expected at least ${fs.min_free_args} arguments, but given $fs.args.len') + } + if fs.args.len > fs.max_free_args && fs.max_free_args > 0 { + return error('Expected at most ${fs.max_free_args} arguments, but given $fs.args.len') + } + if fs.args.len > 0 && fs.max_free_args == 0 && fs.min_free_args == 0 { + return error('Expected no arguments, but given $fs.args.len') + } + return fs.args } diff --git a/vlib/flag/flag_test.v b/vlib/flag/flag_test.v index 39364db85a..99fd52fc6b 100644 --- a/vlib/flag/flag_test.v +++ b/vlib/flag/flag_test.v @@ -1,266 +1,319 @@ - import flag fn test_if_flag_not_given_return_default_values() { - mut fp := flag.new_flag_parser([]) + mut fp := flag.new_flag_parser([]) - assert false == fp.bool('a_bool', false, '') - && 42 == fp.int('an_int', 42, '') - && 1.0 == fp.float('a_float', 1.0, '') - && 'stuff' == fp.string('a_string', 'stuff', '') + assert false == fp.bool('a_bool', false, '') + && 42 == fp.int('an_int', 42, '') + && 1.0 == fp.float('a_float', 1.0, '') + && 'stuff' == fp.string('a_string', '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') + 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' - && fp.application_version == '0.0.42' - && fp.application_description == 'some text' + assert fp.application_name == 'test app' + && fp.application_version == '0.0.42' + && fp.application_description == 'some text' } fn test_bool_flags_do_not_need_an_value() { - mut fp := flag.new_flag_parser(['--a_bool']) + mut fp := flag.new_flag_parser(['--a_bool']) - assert true == fp.bool('a_bool', false, '') + assert true == fp.bool('a_bool', 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']) + 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', 666, '') - && true == fp.bool('a_bool', false, '') - && true == fp.bool('bool_without', false, '') - && 2.0 == fp.float('a_float', 1.0, '') - && 'stuff' == fp.string('a_string', 'not_stuff', '') + assert 42 == fp.int('an_int', 666, '') + && true == fp.bool('a_bool', false, '') + && true == fp.bool('bool_without', false, '') + && 2.0 == fp.float('a_float', 1.0, '') + && 'stuff' == fp.string('a_string', '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']) + 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', 666, '') - && true == fp.bool('a_bool', false, '') - && true == fp.bool('bool_without', false, '') - && 2.0 == fp.float('a_float', 1.0, '') - && 'stuff' == fp.string('a_string', 'not_stuff', '') + assert 42 == fp.int('an_int', 666, '') + && true == fp.bool('a_bool', false, '') + && true == fp.bool('bool_without', false, '') + && 2.0 == fp.float('a_float', 1.0, '') + && 'stuff' == fp.string('a_string', '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']) + 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', 666, '') - && true == fp.bool('a_bool', false, '') - && true == fp.bool('bool_without', false, '') - && 2.0 == fp.float('a_float', 1.0, '') - && 'stuff' == fp.string('a_string', 'not_stuff', '') + assert 42 == fp.int('an_int', 666, '') + && true == fp.bool('a_bool', false, '') + && true == fp.bool('bool_without', false, '') + && 2.0 == fp.float('a_float', 1.0, '') + && 'stuff' == fp.string('a_string', 'not_stuff', '') } fn test_beaware_for_argument_names_with_same_prefix() { - mut fp := flag.new_flag_parser([ - '--short', '5', - '--shorter=7' - ]) + mut fp := flag.new_flag_parser([ + '--short', '5', + '--shorter=7' + ]) - assert 5 == fp.int('short', 666, '') - && 7 == fp.int('shorter', 666, '') + assert 5 == fp.int('short', 666, '') + && 7 == fp.int('shorter', 666, '') } fn test_beaware_for_argument_names_with_same_prefix_inverse() { - mut fp := flag.new_flag_parser([ - '--shorter=7', - '--short', '5', - ]) + mut fp := flag.new_flag_parser([ + '--shorter=7', + '--short', '5', + ]) - assert 5 == fp.int('short', 666, '') - && 7 == fp.int('shorter', 666, '') + assert 5 == fp.int('short', 666, '') + && 7 == fp.int('shorter', 666, '') } fn test_allow_to_skip_executable_path() { - mut fp := flag.new_flag_parser(['./path/to/execuable']) + mut fp := flag.new_flag_parser(['./path/to/execuable']) - fp.skip_executable() + fp.skip_executable() - args := fp.finalize() or { - assert false - return - } - assert !args.contains('./path/to/execuable') + 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']) + mut fp := flag.new_flag_parser([ + 'file1', '--an_int=2', 'file2', 'file3', '--bool_without', 'file4', '--outfile', 'outfile']) - assert 2 == fp.int('an_int', 666, '') - && 'outfile' == fp.string('outfile', 'bad', '') - && true == fp.bool('bool_without', false, '') + assert 2 == fp.int('an_int', 666, '') + && 'outfile' == fp.string('outfile', 'bad', '') + && true == fp.bool('bool_without', false, '') } fn test_finalize_returns_none_flag_arguments_ordered() { - mut fp := flag.new_flag_parser(['d', 'b', 'x', 'a', '--outfile', 'outfile']) - fp.string('outfile', 'bad', '') + mut fp := flag.new_flag_parser(['d', 'b', 'x', 'a', '--outfile', 'outfile']) + fp.string('outfile', 'bad', '') - finalized := fp.finalize() or { - assert false - return - } + finalized := fp.finalize() or { + assert false + return + } - expected := ['d', 'b', 'x', 'a'] - mut all_as_expected := true - for i, v in finalized { - all_as_expected = all_as_expected && v == expected[i] - } - assert all_as_expected + expected := ['d', 'b', 'x', 'a'] + mut all_as_expected := true + for i, v in finalized { + all_as_expected = all_as_expected && v == expected[i] + } + assert all_as_expected } fn test_finalize_returns_error_for_unknown_flags() { - mut fp := flag.new_flag_parser(['--known', '--unknown']) + mut fp := flag.new_flag_parser(['--known', '--unknown']) - fp.bool('known', false, '') + fp.bool('known', false, '') - finalized := fp.finalize() or { - assert err == 'Unknown argument \'unknown\'' - return - } - assert finalized.len < 0 // expect error to be returned + finalized := fp.finalize() or { + assert err == 'Unknown argument \'unknown\'' + 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') + 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', 666, 'some int to define') - fp.bool('a_bool', false, 'some bool to define') - fp.bool('bool_without_but_really_big', false, 'this should appear on the next line') - fp.float('a_float', 1.0, 'some float as well') - fp.string('a_string', 'not_stuff', 'your credit card number') + fp.int('an_int', 666, 'some int to define') + fp.bool('a_bool', false, 'some bool to define') + fp.bool('bool_without_but_really_big', false, 'this should appear on the next line') + fp.float('a_float', 1.0, 'some float as well') + fp.string('a_string', 'not_stuff', 'your credit card number') - usage := fp.usage() - mut all_strings_found := true - for s in ['flag_tool', 'v0.0.0', - 'an_int ', 'a_bool', 'bool_without', 'a_float ', 'a_string :not_stuff', - '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 + usage := fp.usage() + mut all_strings_found := true + for s in ['flag_tool', 'v0.0.0', + 'an_int ', 'a_bool', 'bool_without', 'a_float ', 'a_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') + mut fp := flag.new_flag_parser([]) + fp.application('flag_tool') + fp.version('v0.0.0') - fp.bool('a_bool', false, '') + fp.bool('a_bool', false, '') - assert !fp.usage().contains('Description:') + 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') + mut fp := flag.new_flag_parser([]) + fp.application('flag_tool') + fp.version('v0.0.0') - assert !fp.usage().contains('Options:') + 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' && args[1] == 'b' && args[2] == 'c' + 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' && args[1] == 'b' && 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.starts_with('Expected at least 5 arguments') - return - } - assert args.len < 0 // expect an error and need to use args + mut fp1 := flag.new_flag_parser(['a', 'b', 'c']) + fp1.limit_free_args(5, 6) + args := fp1.finalize() or { + assert err.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.starts_with('Expected at most 2 arguments') - return - } - assert args.len < 0 // expect an error and need to use args + mut fp1 := flag.new_flag_parser(['a', 'b', 'c']) + fp1.limit_free_args(1, 2) + args := fp1.finalize() or { + assert err.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.starts_with('Expected no arguments') - return - } - assert args.len < 0 // expect an error and need to use args + mut fp1 := flag.new_flag_parser(['a']) + fp1.limit_free_args(0, 0) + args := fp1.finalize() or { + assert err.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']) + 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, '') + 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 && o == 'some_file' && i == 42 && f == 2.0 + assert v && o == 'some_file' && i == 42 && f == 2.0 - u := fp.usage() - assert u.contains(' -v') && u.contains(' -o') && u.contains(' -i') && u.contains(' -f') + u := fp.usage() + assert u.contains(' -v') && u.contains(' -o') && u.contains(' -i') && u.contains(' -f') } 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 ]) + default_value := 'this_is_the_default_value_of_long_option' + long_option_value := 'this_is_a_long_option_value_as_argument' - my_flag := fp.bool('my-long-flag', false, 'flag with long-kebab-name') - my_option := fp.string('my-long-option', default_value, 'string with long-kebab-name') - - assert my_flag == true - assert my_option == long_option_value + mut fp := flag.new_flag_parser(['--my-long-flag', 'true', '--my-long-option', long_option_value ]) - u := fp.usage() - assert u.contains(' --my-long-flag') - assert u.contains(' --my-long-option') + my_flag := fp.bool('my-long-flag', false, 'flag with long-kebab-name') + my_option := fp.string('my-long-option', 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 + } + } + } + } + //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 a && b == 3 && c == 'hello' && 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 && b.len == 3 && c.len == 3 + assert a[0] == 2 && a[1] == 3 && a[2] == 5 + assert b[0] == 'a' && b[1] == 'c' && b[2] == 'b' + assert c[0] == 1.23 && c[1] == 2.34 && c[2] == 3.45 }