aboutsummaryrefslogtreecommitdiff
path: root/mingling_picker/src
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-07-16 19:58:30 +0800
committer魏曹先生 <1992414357@qq.com>2026-07-16 19:58:30 +0800
commit81049b3abd83115374cfc51b26fb5d5054263f81 (patch)
treebea8e56ebacf96d685b545bc7ea3c8d152910bf9 /mingling_picker/src
parent4f4a61d493547660260a5aa1db1af8a6ec42bbb6 (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.rs1
-rw-r--r--mingling_picker/src/builtin/pick_string.rs48
-rw-r--r--mingling_picker/src/parselib/arg_matcher.rs31
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;