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/pickable | |
| 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/pickable')
| -rw-r--r-- | mingling_picker/src/pickable/multi_pickable.rs | 77 | ||||
| -rw-r--r-- | mingling_picker/src/pickable/single_pickable.rs | 69 |
2 files changed, 146 insertions, 0 deletions
diff --git a/mingling_picker/src/pickable/multi_pickable.rs b/mingling_picker/src/pickable/multi_pickable.rs new file mode 100644 index 0000000..0ab0508 --- /dev/null +++ b/mingling_picker/src/pickable/multi_pickable.rs @@ -0,0 +1,77 @@ +use crate::{ + matcher_needed::Matcher, + parselib::{MultiArgMatcher, ParserStyle}, + Pickable, PickerArg, PickerArgAttr, PickerArgResult, + SinglePickable, TagPhaseContext, +}; + +/// Boundary check for multi-value positional parameters. +pub trait BoundaryCheck { + fn check_boundary(raw: &str) -> bool; +} + +/// Trait for multi-value parameters. +pub trait MultiPickableWithBoundary: Sized { + type Checker: BoundaryCheck; + fn pick_multi(raw: Vec<String>) -> PickerArgResult<Self>; +} + +/// Marker: unit type that always accepts — no boundary. +pub struct NoBoundary; + +impl BoundaryCheck for NoBoundary { + #[inline(always)] + fn check_boundary(_raw: &str) -> bool { + false + } +} + +/// `Vec<T>` is greedy — it takes everything with `NoBoundary`. +impl<T: SinglePickable> MultiPickableWithBoundary for Vec<T> { + type Checker = NoBoundary; + + fn pick_multi(raw: Vec<String>) -> PickerArgResult<Self> { + let mut result = Vec::with_capacity(raw.len()); + for s in &raw { + match T::pick_single(Some(s)) { + PickerArgResult::Parsed(v) => result.push(v), + PickerArgResult::NotFound => return PickerArgResult::NotFound, + PickerArgResult::Unparsed => {} + } + } + PickerArgResult::Parsed(result) + } +} + +/// If the first raw string looks like a named flag (starts with the +/// style's long or short prefix), strip it — it's the flag, not a value. +fn strip_flag<'a>(raw_strs: &'a [&'a str]) -> &'a [&'a str] { + if let Some(first) = raw_strs.first() { + let style = ParserStyle::global_style(); + if first.starts_with(style.long_prefix) || first.starts_with(style.short_prefix) { + return &raw_strs[1..]; + } + } + raw_strs +} + +// Pickable impl for Vec<T> + +impl<'a, T> Pickable<'a> for Vec<T> +where + T: SinglePickable, +{ + fn get_attr(flag: &'a PickerArg<'a, Self>) -> PickerArgAttr { + PickerArgAttr::positional_or_multi(flag) + } + + fn tag(ctx: TagPhaseContext) -> Vec<usize> { + MultiArgMatcher::match_all(ctx.into()) + } + + fn pick(raw_strs: &[&str]) -> PickerArgResult<Self> { + let strs = strip_flag(raw_strs); + let owned: Vec<String> = strs.iter().map(|&s| s.to_string()).collect(); + <Vec<T> as MultiPickableWithBoundary>::pick_multi(owned) + } +} diff --git a/mingling_picker/src/pickable/single_pickable.rs b/mingling_picker/src/pickable/single_pickable.rs new file mode 100644 index 0000000..8a5b3e6 --- /dev/null +++ b/mingling_picker/src/pickable/single_pickable.rs @@ -0,0 +1,69 @@ +use crate::{Pickable, PickerArg, PickerArgAttr, PickerArgResult, TagPhaseContext}; + +/// `SinglePickable` trait defines how to parse a type from a single command-line argument. +/// +/// This trait provides a simplified interface for types that consume exactly one argument value. +/// It is automatically implemented by the blanket `impl` of [`Pickable`], so types implementing +/// `SinglePickable` will work with the full `Pickable` argument parsing system. +/// +/// Additionally, `Option<S>` where `S: SinglePickable` also implements [`Pickable`], allowing +/// optional arguments to be parsed naturally. +/// +/// # Type Parameters +/// +/// * `Self` - The type to be parsed from a single argument string. +pub trait SinglePickable +where + Self: Sized, +{ + /// Parse a single optional string value into an instance of `Self`. + /// + /// # Parameters + /// + /// * `str` - An `Option<&str>` representing the raw argument value. If `None`, + /// it indicates that no argument value was provided (e.g., for flag-like arguments). + /// + /// # Returns + /// + /// Returns [`PickerArgResult<Self>`], i.e., the parsed `Self` instance on success, + /// or an appropriate error message on failure. + fn pick_single(str: Option<&str>) -> PickerArgResult<Self>; +} + +impl<'a, S> Pickable<'a> for S +where + S: SinglePickable, +{ + fn get_attr(flag: &'a PickerArg<'a, Self>) -> PickerArgAttr { + PickerArgAttr::positional_or_single(flag) + } + + fn tag(ctx: TagPhaseContext) -> Vec<usize> { + crate::parselib::SingleMatcher::tag(ctx) + } + + fn pick(raw_strs: &[&str]) -> PickerArgResult<Self> { + Self::pick_single(crate::parselib::seek_single(raw_strs)) + } +} + +impl<'a, S> Pickable<'a> for Option<S> +where + S: SinglePickable, +{ + fn get_attr(flag: &'a PickerArg<'a, Self>) -> PickerArgAttr { + PickerArgAttr::positional_or_single(flag) + } + + fn tag(ctx: TagPhaseContext) -> Vec<usize> { + crate::parselib::SingleMatcher::tag(ctx) + } + + fn pick(raw_strs: &[&str]) -> PickerArgResult<Self> { + match S::pick(raw_strs) { + PickerArgResult::Unparsed => PickerArgResult::Unparsed, + PickerArgResult::Parsed(r) => PickerArgResult::Parsed(Some(r)), + PickerArgResult::NotFound => PickerArgResult::Parsed(None), + } + } +} |
