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.
pull/3119/head
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

@ -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 '<arg>' 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 '<arg>' 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, '<bool>')
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, '<bool>:'+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, '<multiple ints>')
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, '<int>')
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, '<int>:$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, '<multiple floats>')
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, '<float>')
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, '<float>:$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, '<multiple floats>')
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, '<string>')
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, '<string>:$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
}

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
}