diff options
| -rw-r--r-- | mingling_picker/src/parselib/single_matcher.rs | 38 | ||||
| -rw-r--r-- | mingling_picker/test/src/test.rs | 1 | ||||
| -rw-r--r-- | mingling_picker/test/src/test/priority_test.rs | 85 |
3 files changed, 115 insertions, 9 deletions
diff --git a/mingling_picker/src/parselib/single_matcher.rs b/mingling_picker/src/parselib/single_matcher.rs index 3606d3a..a148ba2 100644 --- a/mingling_picker/src/parselib/single_matcher.rs +++ b/mingling_picker/src/parselib/single_matcher.rs @@ -15,8 +15,9 @@ pub struct SingleMatcher; impl SingleMatcher { /// Match a single positional value or a named flag+value pair. /// - /// For named args, returns `[]` when the flag has no following - /// value and no inline separator — indicating a missing value. + /// For named args, only complete pairs (flag + value) are kept. + /// Flag occurrences without a following value or inline separator + /// are dropped so they remain available for other matchers. #[inline(always)] pub fn tag(ctx: TagPhaseContext) -> Vec<usize> { if ctx.arg_info.positional { @@ -26,14 +27,33 @@ impl SingleMatcher { } else { let args = ctx.args; let positions = ArgMatcher::match_all(ctx.into()); - if positions.len() == 1 { - let sep = ParserStyle::global_style().value_separator; - if let Some(raw) = args.get(positions[0]) - && !raw.contains(sep) { - return vec![]; - } + let sep = ParserStyle::global_style().value_separator; + + // Walk pairs: [flag, value, flag, value, ...] + // Drop any flag that has no following value and no inline separator. + let mut i = 0; + let mut result = Vec::with_capacity(positions.len()); + while i < positions.len() { + let flag_idx = positions[i]; + if let Some(raw) = args.get(flag_idx) + && raw.contains(sep) + { + // Eq mode: value is inline, keep just the flag. + result.push(flag_idx); + i += 1; + continue; + } + if i + 1 < positions.len() { + // Pair: flag + value. + result.push(flag_idx); + result.push(positions[i + 1]); + i += 2; + } else { + // Flag without value — drop it. + i += 1; + } } - positions + result } } } diff --git a/mingling_picker/test/src/test.rs b/mingling_picker/test/src/test.rs index e27615a..7d18c86 100644 --- a/mingling_picker/test/src/test.rs +++ b/mingling_picker/test/src/test.rs @@ -1,6 +1,7 @@ mod arg_matcher_test; mod basic_test; mod pos_matcher_test; +mod priority_test; mod route_test; mod style_test; mod value_flag_test; diff --git a/mingling_picker/test/src/test/priority_test.rs b/mingling_picker/test/src/test/priority_test.rs new file mode 100644 index 0000000..8179389 --- /dev/null +++ b/mingling_picker/test/src/test/priority_test.rs @@ -0,0 +1,85 @@ +use mingling_picker::value::Flag; +use mingling_picker::{IntoPicker, macros::arg}; + +// Same flag name, different Pickable types +// +// PickerArgAttr priority: Flag < Single +// So Single and Multi should parse BEFORE Flag when sharing +// the same flag name. + +#[test] +fn test_single_takes_flag_when_sharing_name() { + // --name Alice: String consumes it, Flag sees nothing. + let (name, verbose): (String, Flag) = vec!["--name", "Alice"] + .to_picker() + .pick(&arg![name: String]) // Single, parsed first + .pick(&arg![name: Flag]) // Flag, parsed second, nothing left + .unwrap(); + assert_eq!(name, "Alice"); + assert_eq!(verbose, Flag::Inactive); +} + +#[test] +fn test_flag_only_triggers_when_single_missing() { + // --verbose: only Flag matches, String gets default. + let (name, verbose): (String, Flag) = vec!["--verbose"] + .to_picker() + .pick(&arg![name: String]) // Single, no match → default "" + .or_default() + .pick(&arg![name: Flag]) // Flag, no --name in args → Inactive + .unwrap(); + assert_eq!(name, ""); + assert_eq!(verbose, Flag::Inactive); +} + +#[test] +fn test_flag_gets_leftovers_after_single_consumes_value() { + // --name Alice --name: String takes "--name Alice", Flag takes "--name". + let (name, verbose): (String, Flag) = vec!["--name", "Alice", "--name"] + .to_picker() + .pick(&arg![name: String]) // Single: tag [0, 1], consumes --name Alice + .pick(&arg![name: Flag]) // Flag: tags position 2 (--name) + .unwrap(); + assert_eq!(name, "Alice"); + assert_eq!( + verbose, + Flag::Active, + "Flag should see --name at position 2 after String consumed positions 0-1" + ); +} + +#[test] +fn test_short_flag_sharing_same_letter() { + // -n Alice: String takes it, Flag misses. + let (name, verbose): (String, Flag) = vec!["-n", "Alice"] + .to_picker() + .pick(&arg![name: String, 'n']) // Single, parsed first + .pick(&arg![name: Flag, 'n']) // Flag, parsed second + .unwrap(); + assert_eq!(name, "Alice"); + assert_eq!(verbose, Flag::Inactive); +} + +#[test] +fn test_flag_captures_remaining_after_single_partial_consume() { + // --name Alice --verbose: String takes --name Alice, Flag takes --verbose. + let (name, verbose): (String, Flag) = vec!["--name", "Alice", "--verbose"] + .to_picker() + .pick(&arg![name: String]) // Single: tag [0, 1] + .pick(&arg![verbose: Flag]) // Flag: tag [2] + .unwrap(); + assert_eq!(name, "Alice"); + assert_eq!(verbose, Flag::Active); +} + +#[test] +fn test_single_skips_already_claimed_positions() { + // --verbose --name Alice: Flag takes --verbose, String takes --name Alice. + let (verbose, name): (Flag, String) = vec!["--verbose", "--name", "Alice"] + .to_picker() + .pick(&arg![verbose: Flag]) // Flag: tag [0] + .pick(&arg![name: String]) // Single: tag [1, 2] + .unwrap(); + assert_eq!(verbose, Flag::Active); + assert_eq!(name, "Alice"); +} |
