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/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/arg_matcher.rs')
| -rw-r--r-- | mingling_picker/src/parselib/arg_matcher.rs | 142 |
1 files changed, 142 insertions, 0 deletions
diff --git a/mingling_picker/src/parselib/arg_matcher.rs b/mingling_picker/src/parselib/arg_matcher.rs new file mode 100644 index 0000000..38bb9cc --- /dev/null +++ b/mingling_picker/src/parselib/arg_matcher.rs @@ -0,0 +1,142 @@ +use crate::{ + matcher_needed::*, + parselib::{build_possible_flags, seek_end_of_options}, +}; + +/// `ArgMatcher` is used for parameters that carry a single value. +/// +/// It handles two scenarios: +/// +/// **Named** — `--name Alice` or `--name=Alice`. +/// Each flag occurrence consumes **one** following argument as its value, +/// regardless of what it is (even if it looks like a flag). +/// This ensures the mask correctly claims the value slot; validation is +/// the `Pickable`'s responsibility. +/// +/// **Positional** — no flag prefix, matched by position. +/// +/// # Examples +/// +/// | Input | `on_match_one` | `on_match_all` | +/// |-------|----------------|----------------| +/// | `--name Alice` | `[0, 1]` (via Pickable tag) | `[0, 1]` | +/// | `--name=Alice` | `[0]` | `[0]` | +/// | `--val a --val b` | `[0, 1]` | `[0, 1, 2, 3]` | +/// +/// Args after `--` are ignored. +pub struct ArgMatcher; + +impl ArgMatcher { + /// Check whether `raw` matches `flag_str`, optionally with an inline value + /// separated by the style's value separator (`=` for Unix, `:` for PowerShell). + #[inline(always)] + fn matches(raw: &str, flag_str: &str, case_sensitive: bool, sep: char) -> bool { + let eq_match = + |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_match(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) + } + } + + /// Check whether the argument contains its value inline via the style's + /// value separator (eq mode), so no extra mask slot is needed. + #[inline(always)] + fn is_inline_value(raw: &str, flag_str: &str, sep: char) -> bool { + raw.len() > flag_str.len() && raw.as_bytes().get(flag_str.len()) == Some(&(sep as u8)) + } +} + +impl Matcher for ArgMatcher { + 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::matches(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 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 = possible_flags + .iter() + .any(|f| Self::matches(args[i].raw, f, style.case_sensitive, sep)); + + if matched { + let flag_str = possible_flags + .iter() + .find(|f| Self::matches(args[i].raw, f, style.case_sensitive, sep)) + .expect("already matched"); + + result.push(args[i].raw_idx); + + if !Self::is_inline_value(args[i].raw, flag_str, sep) { + if i + 1 < args.len() + // Don't consume `--` (end-of-options marker) as a value. + && end.is_none_or(|e| args[i + 1].raw_idx < e) + { + result.push(args[i + 1].raw_idx); + i += 2; + continue; + } + i += 1; + continue; + } + i += 1; + continue; + } + i += 1; + } + + result + } +} |
