From b9f208deed7b8e012fbcd84202e2fb1d5eb8eeb9 Mon Sep 17 00:00:00 2001 From: 魏曹先生 <1992414357@qq.com> Date: Fri, 17 Jul 2026 03:24:51 +0800 Subject: feat(picker2): complete Picker2 prototype MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 — greedy multi-value (all SinglePickable types) - VecUntil — bounded multi-value via BoundaryCheck trait - VecUntil, 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 --- mingling_picker/src/builtin/pick_bool.rs | 22 ++++++++ mingling_picker/src/builtin/pick_flag.rs | 21 ++++++++ mingling_picker/src/builtin/pick_numbers.rs | 80 +++++++++++++++++++++++++++++ mingling_picker/src/builtin/pick_string.rs | 11 ++++ 4 files changed, 134 insertions(+) create mode 100644 mingling_picker/src/builtin/pick_bool.rs create mode 100644 mingling_picker/src/builtin/pick_flag.rs create mode 100644 mingling_picker/src/builtin/pick_numbers.rs create mode 100644 mingling_picker/src/builtin/pick_string.rs (limited to 'mingling_picker/src/builtin') diff --git a/mingling_picker/src/builtin/pick_bool.rs b/mingling_picker/src/builtin/pick_bool.rs new file mode 100644 index 0000000..ccc4424 --- /dev/null +++ b/mingling_picker/src/builtin/pick_bool.rs @@ -0,0 +1,22 @@ +use crate::parselib::{FlagMatcher, Matcher}; +use crate::pickable_needed::*; + +impl<'a> Pickable<'a> for bool { + fn get_attr(_: &'a PickerArg<'a, Self>) -> PickerArgAttr { + PickerArgAttr::Flag + } + + fn tag(ctx: TagPhaseContext) -> Vec { + FlagMatcher::match_all(ctx.into()) + } + + fn pick(raw_strs: &[&str]) -> PickerArgResult { + if raw_strs.is_empty() { + // No matching flag found — signal NotFound so the fallback chain + // (default → route) gets a chance to run. + PickerArgResult::NotFound + } else { + PickerArgResult::Parsed(true) + } + } +} diff --git a/mingling_picker/src/builtin/pick_flag.rs b/mingling_picker/src/builtin/pick_flag.rs new file mode 100644 index 0000000..b642a9a --- /dev/null +++ b/mingling_picker/src/builtin/pick_flag.rs @@ -0,0 +1,21 @@ +use crate::parselib::{FlagMatcher, Matcher}; +use crate::pickable_needed::*; +use crate::value::Flag; + +impl<'a> Pickable<'a> for Flag { + fn get_attr(_: &'a PickerArg<'a, Self>) -> PickerArgAttr { + PickerArgAttr::Flag + } + + fn tag(ctx: TagPhaseContext) -> Vec { + FlagMatcher::match_all(ctx.into()) + } + + fn pick(raw_strs: &[&str]) -> PickerArgResult { + if raw_strs.is_empty() { + PickerArgResult::Parsed(Flag::Inactive) + } else { + PickerArgResult::Parsed(Flag::Active) + } + } +} diff --git a/mingling_picker/src/builtin/pick_numbers.rs b/mingling_picker/src/builtin/pick_numbers.rs new file mode 100644 index 0000000..a5ab0a9 --- /dev/null +++ b/mingling_picker/src/builtin/pick_numbers.rs @@ -0,0 +1,80 @@ +use crate::{BoundaryCheck, SinglePickable, pickable_needed::*}; + +macro_rules! impl_single_pickable_num { + ($($t:ty),+ $(,)?) => { + $(impl SinglePickable for $t { + fn pick_single(str: Option<&str>) -> PickerArgResult { + match str { + Some(s) => s.parse::<$t>().map(PickerArgResult::Parsed).unwrap_or(PickerArgResult::NotFound), + None => PickerArgResult::NotFound, + } + } + })+ + }; +} + +/// Returns `true` if `raw` looks like an integer (digits, optional `+`/`-` prefix, +/// no decimal point or exponent). +fn is_int_like(raw: &str) -> bool { + let s = raw.trim(); + let bytes = s.as_bytes(); + if bytes.is_empty() { + return false; + } + let mut i = 0; + if bytes[0] == b'-' || bytes[0] == b'+' { + i = 1; + } + if i >= bytes.len() { + return false; + } + for &b in &bytes[i..] { + if !b.is_ascii_digit() { + return false; + } + } + true +} + +/// Returns `true` if `raw` looks like a float (contains `.`, `e`, or `E`). +fn is_float_like(raw: &str) -> bool { + let s = raw.trim(); + s.contains('.') || s.contains('e') || s.contains('E') +} + +impl_single_pickable_num! { + i8, i16, i32, i64, i128, isize, + u8, u16, u32, u64, u128, usize, + f32, f64, +} + +// Integer boundary: only accept strings that look like integers. +// Float-like strings trigger a boundary. +macro_rules! impl_boundary_check_int { + ($($t:ty),+ $(,)?) => { + $(impl BoundaryCheck for $t { + fn check_boundary(raw: &str) -> bool { + !is_int_like(raw) + } + })+ + }; +} + +impl_boundary_check_int! { + i8, i16, i32, i64, i128, isize, + u8, u16, u32, u64, u128, usize, +} + +// Float boundary: only accept strings that look like floats. +// Integer-like strings trigger a boundary. +impl BoundaryCheck for f32 { + fn check_boundary(raw: &str) -> bool { + !is_float_like(raw) || raw.parse::().is_err() + } +} + +impl BoundaryCheck for f64 { + fn check_boundary(raw: &str) -> bool { + !is_float_like(raw) || raw.parse::().is_err() + } +} diff --git a/mingling_picker/src/builtin/pick_string.rs b/mingling_picker/src/builtin/pick_string.rs new file mode 100644 index 0000000..c96f667 --- /dev/null +++ b/mingling_picker/src/builtin/pick_string.rs @@ -0,0 +1,11 @@ +use crate::PickerArgResult::NotFound; +use crate::{SinglePickable, pickable_needed::*}; + +impl SinglePickable for String { + fn pick_single(str: Option<&str>) -> PickerArgResult { + match str { + Some(str) => PickerArgResult::Parsed(str.to_string()), + None => NotFound, + } + } +} -- cgit