1
0
mirror of https://github.com/vlang/v.git synced 2023-08-10 21:13:21 +03:00

flag: implement *_opt() and *_multi()

It's very often that someone will need to process values that were not
provided separately (eg. generating a complex default value like in V).

This commit allows the user to call the *_opt() functions instead of
forcing them to provide some magical default values.

* flag: Implement int_multi(), float_multi() and string_multi()

These 3 functions are useful when the application expects a flag to be
repeated more than once.

A sample would be:
v -cflags someflag -cflags secondflag

This feature exposes the raw array that the flag parser internally keeps
and allow the application to parse it however they want instead of
dropping everything except the first element.

* flag: Add documentation

Add comments describing what *_opt() and *_multi() do.
This commit is contained in:
lutherwenxu
2019-12-17 00:03:38 +08:00
committed by Alexander Medvednikov
parent d1714c4a2a
commit dadf147382
2 changed files with 544 additions and 389 deletions

View File

@@ -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 <int>', 'a_bool', 'bool_without', 'a_float <float>', 'a_string <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 <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')
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
}