diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-07-16 19:42:28 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-07-16 19:42:28 +0800 |
| commit | 20253556eacdcb068210254b540f47a4522fc6f5 (patch) | |
| tree | fa9724522de731e2c8d416bddf4c88f092083195 /mingling_picker/src | |
| parent | 2287f5db3c12049afd274496a49b223365fb1e89 (diff) | |
feat(parselib): implement ArgMatcher for named and positional args
Implement `on_match_one` and `on_match_all` for `ArgMatcher`,
supporting named flags (with eq-mode and short flags), positional
arguments, end-of-options marker (`--`), and case-insensitive matching
Diffstat (limited to 'mingling_picker/src')
| -rw-r--r-- | mingling_picker/src/parselib/arg_matcher.rs | 151 |
1 files changed, 142 insertions, 9 deletions
diff --git a/mingling_picker/src/parselib/arg_matcher.rs b/mingling_picker/src/parselib/arg_matcher.rs index e8be999..931652a 100644 --- a/mingling_picker/src/parselib/arg_matcher.rs +++ b/mingling_picker/src/parselib/arg_matcher.rs @@ -1,21 +1,154 @@ -use crate::matcher_needed::*; +use crate::{matcher_needed::*, parselib::build_possible_flags}; +/// `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` (exact or eq-separated). + #[inline(always)] + fn matches(raw: &str, flag_str: &str, case_sensitive: bool) -> bool { + if case_sensitive { + raw == flag_str + || raw.starts_with(flag_str) && raw.as_bytes().get(flag_str.len()) == Some(&b'=') + } 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()] == b'=') + } + } + + /// Check whether the argument at the given position (in the masked slice) + /// contains its value inline (eq mode), so no extra slot is needed. + #[inline(always)] + fn is_eq_mode(raw: &str, flag_str: &str) -> bool { + raw.len() > flag_str.len() && raw.as_bytes().get(flag_str.len()) == Some(&b'=') + } +} + impl Matcher for ArgMatcher { fn on_match_one( - _args: &[MaskedArg], - _style: &ParserStyle, - _arg_info: &PickerArgInfo, + args: &[MaskedArg], + style: &ParserStyle, + arg_info: &PickerArgInfo, ) -> Option<usize> { - todo!() + if arg_info.positional { + // Positional: first available position. + 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); + + for arg in args { + // Stop at end-of-options marker. + 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)); + if matched { + return Some(arg.raw_idx); + } + } + + None } fn on_match_all( - _args: &[MaskedArg], - _style: &ParserStyle, - _arg_info: &PickerArgInfo, + args: &[MaskedArg], + style: &ParserStyle, + arg_info: &PickerArgInfo, ) -> Vec<usize> { - todo!() + if arg_info.positional { + // Positional: all available positions before `--`. + 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 mut result = Vec::new(); + let mut i = 0; + while i < args.len() { + // Stop at end-of-options marker. + 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)); + + if matched { + // Find which flag matched to check eq mode. + let flag_str = possible_flags + .iter() + .find(|f| Self::matches(args[i].raw, f, style.case_sensitive)) + .expect("already matched"); + + result.push(args[i].raw_idx); // flag position + + if !Self::is_eq_mode(args[i].raw, flag_str) { + // Non-eq mode: the next argument is the value. + // Always tag it — even if it looks like a flag — so that + // the mask reserves it. Validation is the Pickable's job. + if i + 1 < args.len() { + result.push(args[i + 1].raw_idx); + i += 2; // skip flag + value + continue; + } + // No value available: tag just the flag. + i += 1; + continue; + } + // eq mode: value is inline, no extra slot. + i += 1; + continue; + } + i += 1; + } + + result } } + +/// Locate the end-of-options marker (`--`) in the argument list. +fn seek_end_of_options(args: &[MaskedArg], style: &ParserStyle) -> Option<usize> { + args.iter() + .find(|arg| { + if style.case_sensitive { + arg.raw == style.end_of_options + } else { + arg.raw.eq_ignore_ascii_case(style.end_of_options) + } + }) + .map(|arg| arg.raw_idx) +} |
