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
parent
d1714c4a2a
commit
dadf147382
180
vlib/flag/flag.v
180
vlib/flag/flag.v
|
@ -46,7 +46,7 @@ module flag
|
|||
|
||||
// data object storing information about a defined flag
|
||||
pub struct Flag {
|
||||
pub:
|
||||
pub:
|
||||
name string // name as it appears on command line
|
||||
abbr byte // shortcut
|
||||
usage string // help message
|
||||
|
@ -56,7 +56,7 @@ pub:
|
|||
|
||||
//
|
||||
pub struct FlagParser {
|
||||
pub mut:
|
||||
pub mut:
|
||||
args []string // the arguments to be parsed
|
||||
flags []Flag // registered flags
|
||||
|
||||
|
@ -121,24 +121,47 @@ fn (fs mut FlagParser) add_flag(n string, a byte, u, vd string) {
|
|||
//
|
||||
// - 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\'') }
|
||||
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.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
|
||||
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
|
||||
}
|
||||
}
|
||||
return error('parameter \'$n\' not found')
|
||||
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
|
||||
|
@ -147,27 +170,46 @@ fn (fs mut FlagParser) parse_value(n string, ab byte) ?string {
|
|||
// 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) {
|
||||
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 {
|
||||
val := 'true'
|
||||
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
|
||||
}
|
||||
} 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
|
||||
if arg[0] == `-` && arg.index_byte(shorthand) != -1 {
|
||||
// -abc is equivalent to -a -b -c
|
||||
return 'true'
|
||||
}
|
||||
}
|
||||
return error('parameter \'$n\' not found')
|
||||
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 (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
|
||||
|
@ -178,11 +220,10 @@ fn (fs mut FlagParser) parse_bool_value(n string, ab byte) ?string {
|
|||
// 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 {
|
||||
value := fs.bool_opt(n, a, u) or {
|
||||
return v
|
||||
}
|
||||
return parsed == 'true'
|
||||
return value
|
||||
}
|
||||
|
||||
// defining and parsing a bool flag
|
||||
|
@ -195,6 +236,29 @@ pub fn (fs mut FlagParser) bool(n string, v bool, u string) bool {
|
|||
return fs.bool_(n, `\0`, v, u)
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
@ -203,11 +267,10 @@ pub fn (fs mut FlagParser) bool(n string, v bool, u string) bool {
|
|||
// 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 {
|
||||
value := fs.int_opt(n, a, u) or {
|
||||
return i
|
||||
}
|
||||
return parsed.int()
|
||||
return value
|
||||
}
|
||||
|
||||
// defining and parsing an int flag
|
||||
|
@ -220,6 +283,29 @@ pub fn (fs mut FlagParser) int(n string, i int, u string) int {
|
|||
return fs.int_(n, `\0`, i, u)
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
@ -228,11 +314,10 @@ pub fn (fs mut FlagParser) int(n string, i int, u string) int {
|
|||
// 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 {
|
||||
value := fs.float_opt(n, a, u) or {
|
||||
return f
|
||||
}
|
||||
return parsed.f32()
|
||||
return value
|
||||
}
|
||||
|
||||
// defining and parsing a float flag
|
||||
|
@ -245,6 +330,24 @@ pub fn (fs mut FlagParser) float(n string, f f32, u string) f32 {
|
|||
return fs.float_(n, `\0`, f, u)
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
@ -252,11 +355,10 @@ pub fn (fs mut FlagParser) float(n string, f f32, u string) f32 {
|
|||
// 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 {
|
||||
value := fs.string_opt(n, a, u) or {
|
||||
return v
|
||||
}
|
||||
return parsed
|
||||
return value
|
||||
}
|
||||
|
||||
// defining and parsing a string flag
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
import flag
|
||||
|
||||
fn test_if_flag_not_given_return_default_values() {
|
||||
|
@ -159,7 +158,7 @@ fn test_allow_to_build_usage_message() {
|
|||
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',
|
||||
'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',
|
||||
|
@ -264,3 +263,57 @@ fn test_allow_kebab_options() {
|
|||
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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue