aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--mingling_picker/src/pickable/multi_pickable.rs127
-rw-r--r--mingling_picker/src/value.rs3
-rw-r--r--mingling_picker/src/value/vec_until.rs132
-rw-r--r--mingling_picker/test/src/test/multi_value_test.rs4
4 files changed, 139 insertions, 127 deletions
diff --git a/mingling_picker/src/pickable/multi_pickable.rs b/mingling_picker/src/pickable/multi_pickable.rs
index bbed6c6..0ab0508 100644
--- a/mingling_picker/src/pickable/multi_pickable.rs
+++ b/mingling_picker/src/pickable/multi_pickable.rs
@@ -1,6 +1,3 @@
-use std::marker::PhantomData;
-use std::ops::{Deref, DerefMut};
-
use crate::{
matcher_needed::Matcher,
parselib::{MultiArgMatcher, ParserStyle},
@@ -10,26 +7,12 @@ use crate::{
/// Boundary check for multi-value positional parameters.
pub trait BoundaryCheck {
- /// Returns `true` if `raw` is **not** part of the current parameter group.
fn check_boundary(raw: &str) -> bool;
}
-// ============================================================
-// MultiPickableWithBoundary — the single multi-value trait
-// ============================================================
-
/// Trait for multi-value parameters.
-///
-/// - [`pick_multi`](MultiPickableWithBoundary::pick_multi): converts collected
-/// strings into `Self`.
-/// - [`Checker`](MultiPickableWithBoundary::Checker): a type implementing
-/// [`BoundaryCheck`] used by the positional matcher to determine where one
-/// group ends and another begins.
pub trait MultiPickableWithBoundary: Sized {
- /// The boundary checker type.
type Checker: BoundaryCheck;
-
- /// Convert the collected raw strings into `Self`.
fn pick_multi(raw: Vec<String>) -> PickerArgResult<Self>;
}
@@ -39,7 +22,7 @@ pub struct NoBoundary;
impl BoundaryCheck for NoBoundary {
#[inline(always)]
fn check_boundary(_raw: &str) -> bool {
- false // never stop: greedy
+ false
}
}
@@ -60,61 +43,6 @@ impl<T: SinglePickable> MultiPickableWithBoundary for Vec<T> {
}
}
-/// `VecUntil<T>` uses `T` itself as the boundary checker.
-impl<T> MultiPickableWithBoundary for VecUntil<T>
-where
- T: SinglePickable + BoundaryCheck,
-{
- type Checker = T;
-
- fn pick_multi(raw: Vec<String>) -> PickerArgResult<Self> {
- let mut inner = Vec::with_capacity(raw.len());
- for s in &raw {
- match T::pick_single(Some(s)) {
- PickerArgResult::Parsed(v) => inner.push(v),
- PickerArgResult::NotFound => return PickerArgResult::NotFound,
- PickerArgResult::Unparsed => {}
- }
- }
- PickerArgResult::Parsed(VecUntil { inner, _marker: PhantomData })
- }
-}
-
-// ============================================================
-// VecUntil<T> — Vec wrapper with boundary checking
-// ============================================================
-
-#[derive(Debug, Clone, PartialEq, Eq, Default)]
-pub struct VecUntil<T> {
- inner: Vec<T>,
- _marker: PhantomData<T>,
-}
-
-impl<T> VecUntil<T> {
- pub fn into_inner(self) -> Vec<T> {
- self.inner
- }
-}
-
-impl<T> From<Vec<T>> for VecUntil<T> {
- fn from(v: Vec<T>) -> Self {
- VecUntil { inner: v, _marker: PhantomData }
- }
-}
-
-impl<T> From<VecUntil<T>> for Vec<T> {
- fn from(v: VecUntil<T>) -> Self {
- v.inner
- }
-}
-
-impl<T> Deref for VecUntil<T> {
- type Target = Vec<T>;
- fn deref(&self) -> &Vec<T> {
- &self.inner
- }
-}
-
/// 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] {
@@ -127,13 +55,7 @@ fn strip_flag<'a>(raw_strs: &'a [&'a str]) -> &'a [&'a str] {
raw_strs
}
-impl<T> DerefMut for VecUntil<T> {
- fn deref_mut(&mut self) -> &mut Vec<T> {
- &mut self.inner
- }
-}
-
-// Concrete Pickable impls
+// Pickable impl for Vec<T>
impl<'a, T> Pickable<'a> for Vec<T>
where
@@ -153,48 +75,3 @@ where
<Vec<T> as MultiPickableWithBoundary>::pick_multi(owned)
}
}
-
-impl<'a, T> Pickable<'a> for VecUntil<T>
-where
- T: SinglePickable + BoundaryCheck,
-{
- fn get_attr(flag: &'a PickerArg<'a, Self>) -> PickerArgAttr {
- PickerArgAttr::positional_or_multi(flag)
- }
-
- fn tag(ctx: TagPhaseContext) -> Vec<usize> {
- let args = ctx.args;
- let is_positional = ctx.arg_info.positional;
- let positions = MultiArgMatcher::match_all(ctx.into());
- if positions.is_empty() {
- return positions;
- }
-
- // Apply BoundaryCheck to trim positions that don't belong.
- // For named: positions[0] is the flag, values start at [1..].
- // For positional: all positions are values.
- let start = if is_positional { 0 } else { 1 };
- if start >= positions.len() {
- return positions; // flag only, no values
- }
-
- let mut cut = start;
- for &idx in &positions[start..] {
- if let Some(raw) = args.get(idx) {
- if T::check_boundary(raw) {
- break; // boundary hit — stop here
- }
- }
- cut += 1;
- }
-
- positions[..cut].to_vec()
- }
-
- fn pick(raw_strs: &[&str]) -> PickerArgResult<Self> {
- // Strip the flag position from named args.
- let strs = strip_flag(raw_strs);
- let owned: Vec<String> = strs.iter().map(|&s| s.to_string()).collect();
- <VecUntil<T> as MultiPickableWithBoundary>::pick_multi(owned)
- }
-}
diff --git a/mingling_picker/src/value.rs b/mingling_picker/src/value.rs
index c16e6a9..995aa00 100644
--- a/mingling_picker/src/value.rs
+++ b/mingling_picker/src/value.rs
@@ -1,2 +1,5 @@
mod flag;
pub use flag::*;
+
+mod vec_until;
+pub use vec_until::*;
diff --git a/mingling_picker/src/value/vec_until.rs b/mingling_picker/src/value/vec_until.rs
new file mode 100644
index 0000000..b1b49b8
--- /dev/null
+++ b/mingling_picker/src/value/vec_until.rs
@@ -0,0 +1,132 @@
+use std::marker::PhantomData;
+use std::ops::{Deref, DerefMut};
+
+use crate::{
+ matcher_needed::Matcher,
+ parselib::{MultiArgMatcher, ParserStyle},
+ BoundaryCheck, MultiPickableWithBoundary, Pickable, PickerArg, PickerArgAttr,
+ PickerArgResult, SinglePickable, TagPhaseContext,
+};
+
+/// A `Vec`-like container that stops collecting when [`BoundaryCheck`]
+/// returns `true`.
+///
+/// This type exists to signal "I know what I'm doing with boundaries"
+/// at the type level (as opposed to `Vec<T>` which greedily takes
+/// everything).
+#[derive(Debug, Clone, PartialEq, Eq, Default)]
+pub struct VecUntil<T> {
+ pub(crate) inner: Vec<T>,
+ _marker: PhantomData<T>,
+}
+
+impl<T> VecUntil<T> {
+ pub fn into_inner(self) -> Vec<T> {
+ self.inner
+ }
+}
+
+impl<T> From<Vec<T>> for VecUntil<T> {
+ fn from(v: Vec<T>) -> Self {
+ VecUntil { inner: v, _marker: PhantomData }
+ }
+}
+
+impl<T> From<VecUntil<T>> for Vec<T> {
+ fn from(v: VecUntil<T>) -> Self {
+ v.inner
+ }
+}
+
+impl<T> Deref for VecUntil<T> {
+ type Target = Vec<T>;
+ fn deref(&self) -> &Vec<T> {
+ &self.inner
+ }
+}
+
+impl<T> DerefMut for VecUntil<T> {
+ fn deref_mut(&mut self) -> &mut Vec<T> {
+ &mut self.inner
+ }
+}
+
+// ============================================================
+// MultiPickableWithBoundary impl
+// ============================================================
+
+impl<T> MultiPickableWithBoundary for VecUntil<T>
+where
+ T: SinglePickable + BoundaryCheck,
+{
+ type Checker = T;
+
+ fn pick_multi(raw: Vec<String>) -> PickerArgResult<Self> {
+ let mut inner = Vec::with_capacity(raw.len());
+ for s in &raw {
+ match T::pick_single(Some(s)) {
+ PickerArgResult::Parsed(v) => inner.push(v),
+ PickerArgResult::NotFound => return PickerArgResult::NotFound,
+ PickerArgResult::Unparsed => {}
+ }
+ }
+ PickerArgResult::Parsed(VecUntil { inner, _marker: PhantomData })
+ }
+}
+
+// ============================================================
+// Pickable impl
+// ============================================================
+
+impl<'a, T> Pickable<'a> for VecUntil<T>
+where
+ T: SinglePickable + BoundaryCheck,
+{
+ fn get_attr(flag: &'a PickerArg<'a, Self>) -> PickerArgAttr {
+ PickerArgAttr::positional_or_multi(flag)
+ }
+
+ fn tag(ctx: TagPhaseContext) -> Vec<usize> {
+ let args = ctx.args;
+ let is_positional = ctx.arg_info.positional;
+ let positions = MultiArgMatcher::match_all(ctx.into());
+ if positions.is_empty() {
+ return positions;
+ }
+
+ let start = if is_positional { 0 } else { 1 };
+ if start >= positions.len() {
+ return positions;
+ }
+
+ let mut cut = start;
+ for &idx in &positions[start..] {
+ if let Some(raw) = args.get(idx) {
+ if T::check_boundary(raw) {
+ break;
+ }
+ }
+ cut += 1;
+ }
+
+ positions[..cut].to_vec()
+ }
+
+ 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();
+ <VecUntil<T> as MultiPickableWithBoundary>::pick_multi(owned)
+ }
+}
+
+/// 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
+}
diff --git a/mingling_picker/test/src/test/multi_value_test.rs b/mingling_picker/test/src/test/multi_value_test.rs
index 50d08af..0f56517 100644
--- a/mingling_picker/test/src/test/multi_value_test.rs
+++ b/mingling_picker/test/src/test/multi_value_test.rs
@@ -1,5 +1,5 @@
-use mingling_picker::value::Flag;
-use mingling_picker::{IntoPicker, VecUntil, macros::arg};
+use mingling_picker::value::{Flag, VecUntil};
+use mingling_picker::{IntoPicker, macros::arg};
#[test]
fn test_vec_until_i16_named() {