aboutsummaryrefslogtreecommitdiff
path: root/mingling_picker/src
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-07-16 19:42:28 +0800
committer魏曹先生 <1992414357@qq.com>2026-07-16 19:42:28 +0800
commit20253556eacdcb068210254b540f47a4522fc6f5 (patch)
treefa9724522de731e2c8d416bddf4c88f092083195 /mingling_picker/src
parent2287f5db3c12049afd274496a49b223365fb1e89 (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.rs151
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)
+}