From 081e3c46b475035bd8975d0539f3ac0024f0ee4b Mon Sep 17 00:00:00 2001 From: Emeric MARTINEAU <11473190+emeric-martineau@users.noreply.github.com> Date: Fri, 22 Jan 2021 18:03:02 +0100 Subject: [PATCH] cli: allow flag to be set multi time (#8256) --- examples/cli.v | 15 +++- vlib/cli/command.v | 2 +- vlib/cli/command_test.v | 12 ++++ vlib/cli/flag.v | 131 +++++++++++++++++++++++++++++++--- vlib/cli/flag_test.v | 154 +++++++++++++++++++++++++++++++++++----- 5 files changed, 286 insertions(+), 28 deletions(-) diff --git a/examples/cli.v b/examples/cli.v index d6c7550a07..50810df587 100644 --- a/examples/cli.v +++ b/examples/cli.v @@ -28,10 +28,17 @@ fn main() { greet_cmd.add_flag(Flag{ flag: .int name: 'times' - value: '3' + value: ['3'] description: 'Number of times the message gets printed.' }) + greet_cmd.add_flag(Flag{ + flag: .string + name: 'fun' + multipe: true + description: 'Just a dumby flags to show multiple.' + }) cmd.add_command(greet_cmd) + cmd.setup() cmd.parse(os.args) } @@ -61,6 +68,12 @@ fn greet_func(cmd Command) { } } } + fun := cmd.flags.get_strings('fun') or { + panic('Failed to get `fun` flag: $err') + } + for f in fun { + println('fun: ${f}') + } } fn greet_pre_func(cmd Command) { diff --git a/vlib/cli/command.v b/vlib/cli/command.v index b084bab406..6689e66899 100644 --- a/vlib/cli/command.v +++ b/vlib/cli/command.v @@ -267,7 +267,7 @@ fn (cmd Command) check_version_flag() { fn (cmd Command) check_required_flags() { for flag in cmd.flags { - if flag.required && flag.value == '' { + if flag.required && flag.value.len > 0 { full_name := cmd.full_name() println('Flag `$flag.name` is required by `$full_name`') exit(1) diff --git a/vlib/cli/command_test.v b/vlib/cli/command_test.v index 22ce800692..7543f9c09c 100644 --- a/vlib/cli/command_test.v +++ b/vlib/cli/command_test.v @@ -79,6 +79,18 @@ fn test_if_flag_gets_set_with_abbrev() { abbrev: 'f' }) cmd.parse(['command', '-f', 'value']) +} + +fn test_if_flag_gets_set_with_long_arg() { + mut cmd := cli.Command{ + name: 'command' + execute: flag_should_be_set + } + cmd.add_flag(cli.Flag{ + flag: .string + name: 'flag' + abbrev: 'f' + }) cmd.parse(['command', '--flag', 'value']) } diff --git a/vlib/cli/flag.v b/vlib/cli/flag.v index 4182f76782..31c5840fc1 100644 --- a/vlib/cli/flag.v +++ b/vlib/cli/flag.v @@ -18,9 +18,16 @@ pub mut: description string global bool required bool - value string + value []string = [] + // If allow multiple value. + // If bool, multiple has no impact, bool can only set once. + // If not multiple, and multiple value set at command args, raise an error. + multipe bool mut: + // Set true if flag found. found bool + // Set true at first init value. + init bool } // get_all_found returns an array of all `Flag`s found in the command parameters @@ -34,7 +41,7 @@ pub fn (flag Flag) get_bool() ?bool { if flag.flag != .bool { return error('$flag.name: Invalid flag type `$flag.flag`, expected `bool`') } - return flag.value == 'true' + return flag.value.len > 0 && flag.value[0] == 'true' } // get_bool returns `true` if the flag specified in `name` is set. @@ -50,7 +57,32 @@ pub fn (flag Flag) get_int() ?int { if flag.flag != .int { return error('$flag.name: Invalid flag type `$flag.flag`, expected `int`') } - return flag.value.int() + + if flag.value.len == 0 { + return 0 + } else { + return flag.value[0].int() + } +} + +// get_ints returns the array of `int` value argument of the flag specified in `name`. +// get_ints returns an error if the `FlagType` is not integer. +pub fn (flag Flag) get_ints() ?[]int { + if flag.flag != .int { + return error('$flag.name: Invalid flag type `$flag.flag`, expected `int`') + } + + if flag.value.len == 0 { + return []int{} + } else { + mut val := []int{} + + for f in flag.value { + val << f.int() + } + + return val + } } // get_int returns the `int` value argument of the flag specified in `name`. @@ -60,13 +92,45 @@ pub fn (flags []Flag) get_int(name string) ?int { return flag.get_int() } +// get_ints returns the array of `int` value argument of the flag specified in `name`. +// get_ints returns an error if the `FlagType` is not integer. +pub fn (flags []Flag) get_ints(name string) ?[]int { + flag := flags.get(name) ? + return flag.get_ints() +} + // get_float returns the `f64` value argument of the flag. // get_float returns an error if the `FlagType` is not floating point. pub fn (flag Flag) get_float() ?f64 { if flag.flag != .float { return error('$flag.name: Invalid flag type `$flag.flag`, expected `float`') } - return flag.value.f64() + + if flag.value.len == 0 { + return 0.0 + } else { + return flag.value[0].f64() + } +} + +// get_floats returns the `f64` value argument of the flag. +// get_floats returns an error if the `FlagType` is not floating point. +pub fn (flag Flag) get_floats() ?[]f64 { + if flag.flag != .float { + return error('$flag.name: Invalid flag type `$flag.flag`, expected `float`') + } + + if flag.value.len == 0 { + return []f64{} + } else { + mut val := []f64{} + + for f in flag.value { + val << f.f64() + } + + return val + } } // get_float returns the `f64` value argument of the flag specified in `name`. @@ -76,13 +140,39 @@ pub fn (flags []Flag) get_float(name string) ?f64 { return flag.get_float() } +// get_floats returns the array of `f64` value argument of the flag specified in `name`. +// get_floats returns an error if the `FlagType` is not floating point. +pub fn (flags []Flag) get_floats(name string) ?[]f64 { + flag := flags.get(name) ? + return flag.get_floats() +} + // get_string returns the `string` value argument of the flag. // get_string returns an error if the `FlagType` is not string. pub fn (flag Flag) get_string() ?string { if flag.flag != .string { return error('$flag.name: Invalid flag type `$flag.flag`, expected `string`') } - return flag.value + + if flag.value.len == 0 { + return '' + } else { + return flag.value[0] + } +} + +// get_strings returns the array of `string` value argument of the flag. +// get_strings returns an error if the `FlagType` is not string. +pub fn (flag Flag) get_strings() ?[]string { + if flag.flag != .string { + return error('$flag.name: Invalid flag type `$flag.flag`, expected `string`') + } + + if flag.value.len == 0 { + return []string{} + } else { + return flag.value + } } // get_string returns the `string` value argument of the flag specified in `name`. @@ -92,14 +182,34 @@ pub fn (flags []Flag) get_string(name string) ?string { return flag.get_string() } +// get_strings returns the `string` value argument of the flag specified in `name`. +// get_strings returns an error if the `FlagType` is not string. +pub fn (flags []Flag) get_strings(name string) ?[]string { + flag := flags.get(name) ? + return flag.get_strings() +} + // parse parses flag values from arguments and return // an array of arguments with all consumed elements removed. fn (mut flag Flag) parse(args []string, with_abbrev bool) ?[]string { if flag.matches(args, with_abbrev) { + // TODO + // Si pas multiple generer une erreur + // Permettre de récupérer plusieurs valeur + if flag.init == false { + flag.init = true + // Clear defaut value if set + flag.value = [] + } + if flag.flag == .bool { new_args := flag.parse_bool(args) ? return new_args } else { + if flag.value.len > 0 && !flag.multipe { + return error('The argument `$flag.name` accept only one value!') + } + new_args := flag.parse_raw(args) ? return new_args } @@ -123,10 +233,10 @@ fn (mut flag Flag) matches(args []string, with_abbrev bool) bool { fn (mut flag Flag) parse_raw(args []string) ?[]string { if args[0].len > flag.name.len && args[0].contains('=') { - flag.value = args[0].split('=')[1] + flag.value << args[0].split('=')[1] return args[1..] } else if args.len >= 2 { - flag.value = args[1] + flag.value << args[1] return args[2..] } return error('Missing argument for `$flag.name`') @@ -134,15 +244,16 @@ fn (mut flag Flag) parse_raw(args []string) ?[]string { fn (mut flag Flag) parse_bool(args []string) ?[]string { if args[0].len > flag.name.len && args[0].contains('=') { - flag.value = args[0].split('=')[1] + flag.value = [args[0].split('=')[1]] return args[1..] } else if args.len >= 2 { if args[1] in ['true', 'false'] { - flag.value = args[1] + flag.value = [args[1]] return args[2..] } } - flag.value = 'true' + // In fact bool cannot be multiple + flag.value = ['true'] return args[1..] } diff --git a/vlib/cli/flag_test.v b/vlib/cli/flag_test.v index 9e933da934..6573320257 100644 --- a/vlib/cli/flag_test.v +++ b/vlib/cli/flag_test.v @@ -5,10 +5,43 @@ fn test_if_string_flag_parses() { flag: .string name: 'flag' } - flag.parse(['-flag', 'value'], false) or { panic(err) } - assert flag.value == 'value' - flag.parse(['-flag=value'], false) or { panic(err) } - assert flag.value == 'value' + flag.parse(['-flag', 'value1'], false) or { panic(err) } + mut value := flag.get_string() or { panic(err) } + assert value == 'value1' + + flag = cli.Flag{ + flag: .string + name: 'flag' + } + flag.parse(['-flag=value2'], false) or { panic(err) } + value = flag.get_string() or { panic(err) } + assert value == 'value2' + + flag = cli.Flag{ + flag: .string + name: 'flag' + multipe: true + } + flag.parse(['-flag=value1'], false) or { panic(err) } + flag.parse(['-flag=value2'], false) or { panic(err) } + mut values := flag.get_strings() or { panic(err) } + assert values == ['value1', 'value2'] + + flags := [ + cli.Flag{ + flag: .string + name: 'flag' + multipe: true + value: ['a', 'b', 'c'] + }, + cli.Flag{ + flag: .string + name: 'flag2' + }, + ] + + values = flags.get_strings('flag') or { panic(err) } + assert values == ['a', 'b', 'c'] } fn test_if_bool_flag_parses() { @@ -20,18 +53,18 @@ fn test_if_bool_flag_parses() { flag.parse(['-flag'], false) or { panic(err) } value = flag.get_bool() or { panic(err) } assert value == true - flag.parse(['-flag', 'true'], false) or { panic(err) } - value = flag.get_bool() or { panic(err) } - assert value == true - flag.parse(['-flag=true'], false) or { panic(err) } - value = flag.get_bool() or { panic(err) } - assert value == true flag.parse(['-flag', 'false'], false) or { panic(err) } value = flag.get_bool() or { panic(err) } assert value == false + flag.parse(['-flag', 'true'], false) or { panic(err) } + value = flag.get_bool() or { panic(err) } + assert value == true flag.parse(['-flag=false'], false) or { panic(err) } value = flag.get_bool() or { panic(err) } assert value == false + flag.parse(['-flag=true'], false) or { panic(err) } + value = flag.get_bool() or { panic(err) } + assert value == true } fn test_if_int_flag_parses() { @@ -39,13 +72,47 @@ fn test_if_int_flag_parses() { flag: .int name: 'flag' } + mut value := 0 flag.parse(['-flag', '42'], false) or { panic(err) } value = flag.get_int() or { panic(err) } assert value == 42 - flag.parse(['-flag=42'], false) or { panic(err) } + + flag = cli.Flag{ + flag: .int + name: 'flag' + } + + flag.parse(['-flag=45'], false) or { panic(err) } value = flag.get_int() or { panic(err) } - assert value == 42 + assert value == 45 + + flag = cli.Flag{ + flag: .int + name: 'flag' + multipe: true + } + + flag.parse(['-flag=42'], false) or { panic(err) } + flag.parse(['-flag=45'], false) or { panic(err) } + mut values := flag.get_ints() or { panic(err) } + assert values == [42, 45] + + flags := [ + cli.Flag{ + flag: .int + name: 'flag' + multipe: true + value: ['1', '2', '3'] + }, + cli.Flag{ + flag: .int + name: 'flag2' + }, + ] + + values = flags.get_ints('flag') or { panic(err) } + assert values == [1, 2, 3] } fn test_if_float_flag_parses() { @@ -54,13 +121,46 @@ fn test_if_float_flag_parses() { name: 'flag' } mut value := f64(0) - flag.parse(['-flag', '3.14159'], false) or { panic(err) } + flag.parse(['-flag', '3.14158'], false) or { panic(err) } value = flag.get_float() or { panic(err) } - assert value == 3.14159 + assert value == 3.14158 + + flag = cli.Flag{ + flag: .float + name: 'flag' + } + flag.parse(['-flag=3.14159'], false) or { panic(err) } - assert flag.value.f64() == 3.14159 + assert flag.value[0].f64() == 3.14159 value = flag.get_float() or { panic(err) } assert value == 3.14159 + + flag = cli.Flag{ + flag: .float + name: 'flag' + multipe: true + } + + flag.parse(['-flag=3.1'], false) or { panic(err) } + flag.parse(['-flag=1.3'], false) or { panic(err) } + mut values := flag.get_floats() or { panic(err) } + assert values == [3.1, 1.3] + + flags := [ + cli.Flag{ + flag: .float + name: 'flag' + multipe: true + value: ['1.1', '2.2', '3.3'] + }, + cli.Flag{ + flag: .float + name: 'flag2' + }, + ] + + values = flags.get_floats('flag') or { panic(err) } + assert values == [1.1, 2.2, 3.3] } fn test_if_flag_parses_with_abbrev() { @@ -73,7 +173,29 @@ fn test_if_flag_parses_with_abbrev() { flag.parse(['--flag'], true) or { panic(err) } value = flag.get_bool() or { panic(err) } assert value == true - flag.parse(['-flag'], true) or { panic(err) } + + value = false + flag = cli.Flag{ + flag: .bool + name: 'flag' + abbrev: 'f' + } + flag.parse(['-f'], true) or { panic(err) } value = flag.get_bool() or { panic(err) } assert value == true } + +fn test_if_multiple_value_on_single_value() { + mut flag := cli.Flag{ + flag: .float + name: 'flag' + } + + flag.parse(['-flag', '3.14158'], false) or { panic(err) } + + if _ := flag.parse(['-flag', '3.222'], false) { + panic("No multiple value flag don't raise an error!") + } else { + assert true + } +}