diff --git a/vlib/flag/README.md b/vlib/flag/README.md index ea2b477e5ff4fc..7d17e114d0cce7 100644 --- a/vlib/flag/README.md +++ b/vlib/flag/README.md @@ -13,11 +13,12 @@ The module supports several flag "styles" like: * GNU long style (`--long` / `--long=value` * Go `flag` module style (`-flag`, `-flag-name` and GNU long) * V style (`-v`,`-version`) +* V long style (`--v`,`--version`) as supported by `flag.FlagParser` Its main features are: - simplicity of usage. -- parses flags like `-f` or '--flag' or '--stuff=things' or '--things stuff'. +- parses flags like `-f` or `--flag` or `--stuff=things` or `--things stuff`. - handles bool, int, float and string args. - can flexibly generate usage information, listing all the declared flags. @@ -209,4 +210,4 @@ fn main() { println('an_int: ${an_int} | a_bool: ${a_bool} | a_float: ${a_float} | a_string: "${a_string}" ') println(additional_args.join_lines()) } -``` \ No newline at end of file +``` diff --git a/vlib/flag/flag_to.v b/vlib/flag/flag_to.v index fa5d18b3c14418..be97ff447a8788 100644 --- a/vlib/flag/flag_to.v +++ b/vlib/flag/flag_to.v @@ -23,6 +23,7 @@ pub enum Style { long // GNU style long option *only*. E.g.: `--name` or `--name=value` short_long // extends `posix` style shorts with GNU style long options: `--flag` or `--name=value` v // V style flags as found in flags for the `v` compiler. Single flag denote `-` followed by string identifier e.g.: `-verbose`, `-name value`, `-v`, `-n value` or `-d ident=value` + v_long // V long style flags as found in `flag.FlagParser`. Long flag denote `--` followed by string identifier e.g.: `--verbose`, `--name value`, `--v` or `--n value`. go_flag // GO `flag` module style. Single flag denote `-` followed by string identifier e.g.: `-verbose`, `-name value`, `-v` or `-n value` and both long `--name value` and GNU long `--name=value` cmd_exe // `cmd.exe` style flags. Single flag denote `/` followed by lower- or upper-case character } @@ -425,7 +426,7 @@ pub fn (mut fm FlagMapper) parse[T]() ! { } if is_long_delimiter { if style == .v { - return error('long delimiter `${used_delimiter}` encountered in flag `${arg}` in ${style} (V) style parsing mode') + return error('long delimiter `${used_delimiter}` encountered in flag `${arg}` in ${style} (V) style parsing mode. Maybe you meant `.v_long`?') } if style == .short { return error('long delimiter `${used_delimiter}` encountered in flag `${arg}` in ${style} (POSIX) style parsing mode') @@ -436,6 +437,9 @@ pub fn (mut fm FlagMapper) parse[T]() ! { if style == .long { return error('short delimiter `${used_delimiter}` encountered in flag `${arg}` in ${style} (GNU) style parsing mode') } + if style == .v_long { + return error('short delimiter `${used_delimiter}` encountered in flag `${arg}` in ${style} (V long) style parsing mode. Maybe you meant `.v`?') + } if style == .short_long && flag_name.len > 1 && flag_name.contains('-') { return error('long name `${flag_name}` used with short delimiter `${used_delimiter}` in flag `${arg}` in ${style} (POSIX/GNU) style parsing mode') } @@ -515,6 +519,10 @@ pub fn (mut fm FlagMapper) parse[T]() ! { if fm.map_gnu_long(flag_ctx, field)! { continue } + } else if style == .v_long { + if fm.map_v_long(flag_ctx, field)! { + continue + } } else if style == .go_flag { if fm.map_go_flag_long(flag_ctx, field)! { continue @@ -686,11 +694,11 @@ pub fn (fm FlagMapper) to_doc(dc DocConfig) !string { pub fn (fm FlagMapper) fields_docs(dc DocConfig) ![]string { short_delimiter := match dc.style { .short, .short_long, .v, .go_flag, .cmd_exe { dc.delimiter } - .long { dc.delimiter.repeat(2) } + .long, .v_long { dc.delimiter.repeat(2) } } long_delimiter := match dc.style { .short, .v, .go_flag, .cmd_exe { dc.delimiter } - .long, .short_long { dc.delimiter.repeat(2) } + .long, .v_long, .short_long { dc.delimiter.repeat(2) } } pad_desc := if dc.layout.description_padding < 0 { 0 } else { dc.layout.description_padding } @@ -1048,6 +1056,66 @@ fn (mut fm FlagMapper) map_v(flag_ctx FlagContext, field StructField) !bool { return false } +// map_v_long returns `true` if the V long style flag in `flag_ctx` can be mapped to `field`. +// map_v_long adds data of the match in the internal structures for further processing if applicable +fn (mut fm FlagMapper) map_v_long(flag_ctx FlagContext, field StructField) !bool { + flag_raw := flag_ctx.raw + flag_name := flag_ctx.name + pos := flag_ctx.pos + used_delimiter := flag_ctx.delimiter + next := flag_ctx.next + + if flag_raw.contains('=') { + return error('`=` in flag `${flag_raw}` is not supported in V long style parsing mode. Use `--flag value` instead') + } + + if field.hints.has(.is_bool) { + if flag_name == field.match_name { + trace_println('${@FN}: found match for (bool) ${fm.dbg_match(flag_ctx, field, + 'true', '')}') + fm.field_map_flag[field.name] = FlagData{ + raw: flag_raw + field_name: field.name + delimiter: used_delimiter + name: flag_name + pos: pos + } + fm.handled_pos << pos + return true + } + } + + if flag_name == field.match_name || flag_name == field.short { + if field.hints.has(.is_array) { + trace_println('${@FN}: found match for (V long style multiple occurences) ${fm.dbg_match(flag_ctx, + field, next, '')}') + fm.array_field_map_flag[field.name] << FlagData{ + raw: flag_raw + field_name: field.name + delimiter: used_delimiter + name: flag_name + arg: ?string(next) + pos: pos + } + } else { + trace_println('${@FN}: found match for (V long style) ${fm.dbg_match(flag_ctx, + field, next, '')}') + fm.field_map_flag[field.name] = FlagData{ + raw: flag_raw + field_name: field.name + delimiter: used_delimiter + name: flag_name + arg: ?string(next) + pos: pos + } + } + fm.handled_pos << pos + fm.handled_pos << pos + 1 // arg + return true + } + return false +} + // map_go_flag_short returns `true` if the GO short style flag in `flag_ctx` can be mapped to `field`. // map_go_flag_short adds data of the match in the internal structures for further processing if applicable fn (mut fm FlagMapper) map_go_flag_short(flag_ctx FlagContext, field StructField) !bool { diff --git a/vlib/flag/v_long_style_flags_test.v b/vlib/flag/v_long_style_flags_test.v new file mode 100644 index 00000000000000..de1460302ab145 --- /dev/null +++ b/vlib/flag/v_long_style_flags_test.v @@ -0,0 +1,94 @@ +// Test .v (V) parse style +import flag + +const exe_and_v_long_args = ['/path/to/exe', '--version', '--p', 'ident=val', '--o', '/path/to', + '--test', 'abc', '--done', '--pop', 'two', '--live'] +const exe_and_v_long_args_with_tail = ['/path/to/exe', '--version', '--p', 'ident=val', '--test', + 'abc', '--done', '--p', 'two', '--live', 'run', '/path/to'] +const error_wrong_assignment_flags = ['--o=error'] + +struct Prefs { + version bool @[short: v] + is_live bool @[long: live] + is_done bool @[long: done] + test string + pop_flags []string @[long: pop; short: p] + tail []string @[tail] + out string @[only: o] + not_mapped string = 'not changed' +} + +fn test_long_v_style() { + prefs, _ := flag.to_struct[Prefs](exe_and_v_long_args, skip: 1, style: .v_long)! + assert prefs.version + assert prefs.is_live + assert prefs.is_done + assert prefs.test == 'abc' + assert prefs.pop_flags.len == 2 + assert prefs.pop_flags[0] == 'ident=val' + assert prefs.pop_flags[1] == 'two' + assert prefs.tail.len == 0 + assert prefs.out == '/path/to' + assert prefs.not_mapped == 'not changed' +} + +fn test_long_v_style_no_exe() { + prefs, _ := flag.to_struct[Prefs](exe_and_v_long_args[1..], style: .v_long)! + assert prefs.version + assert prefs.is_live + assert prefs.is_done + assert prefs.test == 'abc' + assert prefs.pop_flags.len == 2 + assert prefs.pop_flags[0] == 'ident=val' + assert prefs.pop_flags[1] == 'two' + assert prefs.tail.len == 0 + assert prefs.out == '/path/to' + assert prefs.not_mapped == 'not changed' +} + +fn test_long_v_style_with_tail() { + prefs, _ := flag.to_struct[Prefs](exe_and_v_long_args_with_tail, skip: 1, style: .v_long)! + assert prefs.version + assert prefs.is_live + assert prefs.is_done + assert prefs.test == 'abc' + assert prefs.not_mapped == 'not changed' + assert prefs.pop_flags.len == 2 + assert prefs.pop_flags[0] == 'ident=val' + assert prefs.pop_flags[1] == 'two' + assert prefs.out == '' + assert prefs.not_mapped == 'not changed' + assert prefs.tail.len == 2 + assert prefs.tail[0] == 'run' + assert prefs.tail[1] == '/path/to' +} + +fn test_long_v_style_with_tail_no_exe() { + prefs, _ := flag.to_struct[Prefs](exe_and_v_long_args_with_tail[1..], style: .v_long)! + assert prefs.version + assert prefs.is_live + assert prefs.is_done + assert prefs.test == 'abc' + assert prefs.not_mapped == 'not changed' + assert prefs.pop_flags.len == 2 + assert prefs.pop_flags[0] == 'ident=val' + assert prefs.pop_flags[1] == 'two' + assert prefs.out == '' + assert prefs.not_mapped == 'not changed' + assert prefs.tail.len == 2 + assert prefs.tail[0] == 'run' + assert prefs.tail[1] == '/path/to' +} + +fn test_long_v_style_error_message() { + if _, _ := flag.to_struct[Prefs](exe_and_v_long_args[1..], style: .v) { + assert false, 'flags should not have reached this assert' + } else { + assert err.msg() == 'long delimiter `--` encountered in flag `--version` in v (V) style parsing mode. Maybe you meant `.v_long`?' + } + if _, _ := flag.to_struct[Prefs](error_wrong_assignment_flags, style: .v_long) { + assert false, 'flags should not have reached this assert' + } else { + assert err.msg() == '`=` in flag `--o=error` is not supported in V long style parsing mode. Use `--flag value` instead' + } +}