aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-07-17 00:56:01 +0800
committer魏曹先生 <1992414357@qq.com>2026-07-17 00:56:01 +0800
commit23bb8d5417a25fc10ca1277ec1c4b5e86e843788 (patch)
tree9d5d0d9e413a3225be8d0afc3d7c273b34ee3d5e
parent73c01a36faced96cd99ef6696cb2e44e8d8e5310 (diff)
fix(single-matcher): drop incomplete named flag pairs
-rw-r--r--mingling_picker/src/parselib/single_matcher.rs38
-rw-r--r--mingling_picker/test/src/test.rs1
-rw-r--r--mingling_picker/test/src/test/priority_test.rs85
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");
+}