diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-07-16 19:58:30 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-07-16 19:58:30 +0800 |
| commit | 81049b3abd83115374cfc51b26fb5d5054263f81 (patch) | |
| tree | bea8e56ebacf96d685b545bc7ea3c8d152910bf9 /mingling_picker/src | |
| parent | 4f4a61d493547660260a5aa1db1af8a6ec42bbb6 (diff) | |
feat(picker): add string value support with inline value matching
Implement `Pickable` for `String` supporting both positional and named
flags, and generalize value matching to use style-specific separators
Diffstat (limited to 'mingling_picker/src')
| -rw-r--r-- | mingling_picker/src/builtin.rs | 1 | ||||
| -rw-r--r-- | mingling_picker/src/builtin/pick_string.rs | 48 | ||||
| -rw-r--r-- | mingling_picker/src/parselib/arg_matcher.rs | 31 |
3 files changed, 67 insertions, 13 deletions
diff --git a/mingling_picker/src/builtin.rs b/mingling_picker/src/builtin.rs index 00fe1e5..d179f86 100644 --- a/mingling_picker/src/builtin.rs +++ b/mingling_picker/src/builtin.rs @@ -1,2 +1,3 @@ mod pick_bool; mod pick_flag; +mod pick_string; diff --git a/mingling_picker/src/builtin/pick_string.rs b/mingling_picker/src/builtin/pick_string.rs new file mode 100644 index 0000000..688ac0a --- /dev/null +++ b/mingling_picker/src/builtin/pick_string.rs @@ -0,0 +1,48 @@ +use crate::parselib::{ArgMatcher, Matcher, ParserStyle}; +use crate::pickable_needed::*; + +impl<'a> Pickable<'a> for String { + fn get_attr(flag: &'a PickerArg<'a, Self>) -> PickerArgAttr { + PickerArgAttr::positional_or_single(flag) + } + + fn tag(ctx: TagPhaseContext) -> Vec<usize> { + if ctx.arg_info.positional { + // Positional: take the first available position only. + ArgMatcher::match_one(ctx.into()) + .map(|i| vec![i]) + .unwrap_or_default() + } else { + // Named: find each flag + its single value. + ArgMatcher::match_all(ctx.into()) + } + } + + fn pick(raw_strs: &[&str]) -> PickerArgResult<Self> { + match raw_strs.len() { + 0 => PickerArgResult::NotFound, + 1 => { + let s = raw_strs[0]; + let style = ParserStyle::global_style(); + + // Inline value via style separator (e.g., --name=Alice, -Name:Alice). + if let Some(pos) = s.rfind(style.value_separator) { + return PickerArgResult::Parsed(s[pos + 1..].to_string()); + } + + // If the single element looks like a named flag (starts with a prefix + // such as `--`, `-`, or `/`), it's a flag with no value → NotFound. + if s.starts_with(style.long_prefix) || s.starts_with(style.short_prefix) { + return PickerArgResult::NotFound; + } + + // Positional value. + PickerArgResult::Parsed(s.to_string()) + } + _ => { + // flag + value as separate args: take the second element. + PickerArgResult::Parsed(raw_strs[1].to_string()) + } + } + } +} diff --git a/mingling_picker/src/parselib/arg_matcher.rs b/mingling_picker/src/parselib/arg_matcher.rs index 808e4a2..d4b8793 100644 --- a/mingling_picker/src/parselib/arg_matcher.rs +++ b/mingling_picker/src/parselib/arg_matcher.rs @@ -27,25 +27,28 @@ use crate::{ pub struct ArgMatcher; impl ArgMatcher { - /// Check whether `raw` matches `flag_str` (exact or eq-separated). + /// 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) -> bool { + 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) && raw.as_bytes().get(flag_str.len()) == Some(&b'=') + 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()] == b'=') + && raw.as_bytes()[flag_str.len()] == sep as u8) } } - /// Check whether the argument at the given position (in the masked slice) - /// contains its value inline (eq mode), so no extra slot is needed. + /// 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_eq_mode(raw: &str, flag_str: &str) -> bool { - raw.len() > flag_str.len() && raw.as_bytes().get(flag_str.len()) == Some(&b'=') + 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)) } } @@ -61,6 +64,7 @@ impl Matcher for ArgMatcher { 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) { @@ -69,7 +73,7 @@ impl Matcher for ArgMatcher { let matched = possible_flags .iter() - .any(|f| Self::matches(arg.raw, f, style.case_sensitive)); + .any(|f| Self::matches(arg.raw, f, style.case_sensitive, sep)); if matched { return Some(arg.raw_idx); } @@ -94,6 +98,7 @@ impl Matcher for ArgMatcher { 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; @@ -104,17 +109,17 @@ impl Matcher for ArgMatcher { let matched = possible_flags .iter() - .any(|f| Self::matches(args[i].raw, f, style.case_sensitive)); + .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)) + .find(|f| Self::matches(args[i].raw, f, style.case_sensitive, sep)) .expect("already matched"); result.push(args[i].raw_idx); - if !Self::is_eq_mode(args[i].raw, flag_str) { + if !Self::is_inline_value(args[i].raw, flag_str, sep) { if i + 1 < args.len() { result.push(args[i + 1].raw_idx); i += 2; |
