diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-07-17 03:24:51 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-07-17 03:25:24 +0800 |
| commit | b9f208deed7b8e012fbcd84202e2fb1d5eb8eeb9 (patch) | |
| tree | 2e0f00651b0cff09a2f6bafd6ce35046532ffa81 /mingling_picker/src/parselib/multi_arg_matcher.rs | |
| parent | 8b2c209c2f111e0e208de5dde9df6c4bd3ea1052 (diff) | |
feat(picker2): complete Picker2 prototype
Picker2 replaces the original Picker1 with a two-phase (tag → pick) +
mask-bitmap architecture, decoupling argument matching from type
conversion via a composable matcher pipeline.
Architecture
- FlagMatcher — boolean flags (--verbose)
- ArgMatcher — single flag+value pairs (--name Alice)
- MultiArgMatcher — multi-value flag groups (--files a.txt b.txt)
- PositionalMatcher — positional arguments, respects `--`
- SingleMatcher — composite for Single-type Pickables
Types
- Flag (Active/Inactive) — semantic bool flag value
- String + 14 numeric types via SinglePickable trait
- Vec<T> — greedy multi-value (all SinglePickable types)
- VecUntil<T> — bounded multi-value via BoundaryCheck trait
- VecUntil<T>, pick_string, pick_numbers, pick_bool, pick_flag
Style system
- UNIX_STYLE (kebab), POWERSHELL_STYLE (Pascal), WINDOWS_STYLE (Pascal)
- naming_case auto-conversion via just_fmt integration
- Style-aware separator (= for Unix, : for PS/Windows)
Infrastructure
- internal_repeat! macro generates PickerPattern1..=32
- SinglePickable blanket impl → Pickable
- MultiPickableWithBoundary trait with greedy/bounded variants
- 151 integration tests
- Docs updated for parser feature
Diffstat (limited to 'mingling_picker/src/parselib/multi_arg_matcher.rs')
| -rw-r--r-- | mingling_picker/src/parselib/multi_arg_matcher.rs | 141 |
1 files changed, 141 insertions, 0 deletions
diff --git a/mingling_picker/src/parselib/multi_arg_matcher.rs b/mingling_picker/src/parselib/multi_arg_matcher.rs new file mode 100644 index 0000000..e8bdb71 --- /dev/null +++ b/mingling_picker/src/parselib/multi_arg_matcher.rs @@ -0,0 +1,141 @@ +use crate::{ + matcher_needed::*, + parselib::{build_possible_flags, seek_end_of_options}, +}; + +/// `MultiArgMatcher` matches a named flag and **all** consecutive arguments +/// that follow it, stopping at the next flag, the `--` marker, or the end +/// of the argument list. +/// +/// This is the tag implementation for `Multi` and `GreedyMulti` types +/// such as `Vec<String>` (`--files a.txt b.txt`). +/// +/// # Behavior +/// +/// | Input | `on_match_all` | +/// |-------|----------------| +/// | `--val a b --val d e` | `[0, 1, 2, 5, 6]` (two groups) | +/// | `--val=1 2` | `[0, 1]` (eq mode + one extra value) | +/// +/// Args after `--` are ignored. +pub struct MultiArgMatcher; + +impl Matcher for MultiArgMatcher { + fn on_match_one( + args: &[MaskedArg], + style: &ParserStyle, + arg_info: &PickerArgInfo, + ) -> Option<usize> { + if arg_info.positional { + return args.first().map(|a| a.raw_idx); + } + + let possible_flags = build_possible_flags(style, arg_info); + let end = seek_end_of_options(args, style); + let sep = style.value_separator; + + for arg in args { + if end.is_some_and(|e| arg.raw_idx >= e) { + break; + } + let matched = possible_flags + .iter() + .any(|f| Self::flag_match(arg.raw, f, style.case_sensitive, sep)); + if matched { + return Some(arg.raw_idx); + } + } + None + } + + fn on_match_all( + args: &[MaskedArg], + style: &ParserStyle, + arg_info: &PickerArgInfo, + ) -> Vec<usize> { + if arg_info.positional { + let end = seek_end_of_options(args, style); + return args + .iter() + .take_while(|a| end.is_none_or(|e| a.raw_idx < e)) + .map(|a| a.raw_idx) + .collect(); + } + + let possible_flags = build_possible_flags(style, arg_info); + let end = seek_end_of_options(args, style); + let sep = style.value_separator; + let is_flag = |raw: &str| { + raw.starts_with(style.long_prefix) || raw.starts_with(style.short_prefix) + }; + let is_our_flag = |raw: &str| { + possible_flags + .iter() + .any(|f| Self::flag_match(raw, f, style.case_sensitive, sep)) + }; + + let mut result = Vec::new(); + let mut i = 0; + while i < args.len() { + if end.is_some_and(|e| args[i].raw_idx >= e) { + break; + } + + let matched = is_our_flag(args[i].raw); + + if matched { + result.push(args[i].raw_idx); + + if Self::is_eq_match(args[i].raw, &possible_flags, style.case_sensitive, sep) { + i += 1; + while i < args.len() + && end.is_none_or(|e| args[i].raw_idx < e) + && !is_flag(args[i].raw) + { + result.push(args[i].raw_idx); + i += 1; + } + continue; + } + + i += 1; + while i < args.len() + && end.is_none_or(|e| args[i].raw_idx < e) + && !is_flag(args[i].raw) + { + result.push(args[i].raw_idx); + i += 1; + } + continue; + } + i += 1; + } + + result + } +} + +impl MultiArgMatcher { + #[inline(always)] + fn flag_match(raw: &str, flag_str: &str, case_sensitive: bool, sep: char) -> bool { + let eq = |r: &str, f: &str| r.len() > f.len() && r.as_bytes().get(f.len()) == Some(&(sep as u8)); + + if case_sensitive { + raw == flag_str || (raw.starts_with(flag_str) && eq(raw, flag_str)) + } else { + raw.eq_ignore_ascii_case(flag_str) + || (raw.len() > flag_str.len() + && raw[..flag_str.len()].eq_ignore_ascii_case(flag_str) + && raw.as_bytes()[flag_str.len()] == sep as u8) + } + } + + #[inline(always)] + fn is_eq_match(raw: &str, flags: &[String], case_sensitive: bool, sep: char) -> bool { + flags.iter().any(|f| { + Self::flag_match(raw, f, case_sensitive, sep) + && raw.len() > f.len() + && raw.as_bytes().get(f.len()) == Some(&(sep as u8)) + }) + } +} |
