From b0665ea978919a333d029677a75a16a608ae5ab4 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Tue, 14 Apr 2026 12:45:37 +0300 Subject: [PATCH] flag: repeated int option in a single argument does not work (fixes #25362) --- vlib/flag/flag_to.v | 18 ++++++++++++++++-- vlib/flag/posix_style_flags_test.v | 26 ++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/vlib/flag/flag_to.v b/vlib/flag/flag_to.v index 7821eca0d..b4351b022 100644 --- a/vlib/flag/flag_to.v +++ b/vlib/flag/flag_to.v @@ -1433,8 +1433,9 @@ fn (mut fm FlagMapper) map_posix_short_cluster(flag_ctx FlagContext) ! { if flag_name.len <= 1 { return } - // Do not handle multiple `-vv`, `map_posix_short` does that - if flag_name[0] == flag_name[1] { + first_letter := flag_name[0].ascii_str() + // Do not handle repeated-only bundles like `-vv`; `map_posix_short` does that. + if flag_name.count(first_letter) == flag_name.len { return } @@ -1478,6 +1479,19 @@ fn (mut fm FlagMapper) map_posix_short_cluster(flag_ctx FlagContext) ! { 'true', '')}') fm.field_map_flag[mf.field_name] = mf fm.handled_pos << flag_ctx.pos + } else if field.hints.has(.can_repeat) { + repeats := if existing := fm.field_map_flag[mf.field_name] { + existing.repeats + 1 + } else { + 1 + } + trace_println('${@FN}: found match for (repeatable cluster) ${fm.dbg_match(flag_ctx, + field, '${repeats}', '')}') + fm.field_map_flag[mf.field_name] = FlagData{ + ...mf + repeats: repeats + } + fm.handled_pos << flag_ctx.pos } else { mut arg := split[i + 1..].clone().join('') mut next_is_used := false diff --git a/vlib/flag/posix_style_flags_test.v b/vlib/flag/posix_style_flags_test.v index 69c44b6dc..71c5ac516 100644 --- a/vlib/flag/posix_style_flags_test.v +++ b/vlib/flag/posix_style_flags_test.v @@ -12,6 +12,9 @@ const posix_multi_short_args_2 = ['-vv', 'vvv', '-done', '-d', 'two', '-yxz', '2 const posix_multi_short_args_3 = ['-vv', 'vvv', '-xyz', '2', '-done', '-d', 'two'] const posix_multi_short_args_4 = ['-yxz2', '-vv', 'vvv', '-done', '-d', 'two'] const posix_multi_short_args_1_err = ['-zxy'] +const repeatable_cluster_args_1 = ['-mv'] +const repeatable_cluster_args_2 = ['-vmm'] +const repeatable_cluster_args_3 = ['-mmv'] struct Config { linker_option string @[short: m] @@ -25,6 +28,11 @@ struct Config { u int @[short: z] } +struct RepeatableClusterConfig { + show_version bool @[only: v] + multi int @[only: m; repeats] +} + fn test_pure_posix_short() { config, _ := flag.to_struct[Config](exe_and_posix_args, skip: 1, style: .short)! assert config.verbosity == 5 @@ -104,6 +112,24 @@ fn test_pure_posix_multi_short_err_1() { } } +fn test_pure_posix_short_repeatable_cluster_1() { + config, _ := flag.to_struct[RepeatableClusterConfig](repeatable_cluster_args_1, style: .short)! + assert config.show_version + assert config.multi == 1 +} + +fn test_pure_posix_short_repeatable_cluster_2() { + config, _ := flag.to_struct[RepeatableClusterConfig](repeatable_cluster_args_2, style: .short)! + assert config.show_version + assert config.multi == 2 +} + +fn test_pure_posix_short_repeatable_cluster_3() { + config, _ := flag.to_struct[RepeatableClusterConfig](repeatable_cluster_args_3, style: .short)! + assert config.show_version + assert config.multi == 2 +} + fn test_pure_posix_short_no_exe() { config, _ := flag.to_struct[Config](exe_and_posix_args[1..], style: .short)! assert config.verbosity == 5 -- 2.39.5