diff options
| -rw-r--r-- | mingling_picker/src/builtin/pick_numbers.rs | 62 | ||||
| -rw-r--r-- | mingling_picker/src/parselib.rs | 3 | ||||
| -rw-r--r-- | mingling_picker/src/parselib/multi_arg_matcher.rs | 141 | ||||
| -rw-r--r-- | mingling_picker/src/pickable.rs | 3 | ||||
| -rw-r--r-- | mingling_picker/src/pickable/multi_pickable.rs | 200 | ||||
| -rw-r--r-- | mingling_picker/test/src/test.rs | 2 | ||||
| -rw-r--r-- | mingling_picker/test/src/test/multi_arg_test.rs | 181 | ||||
| -rw-r--r-- | mingling_picker/test/src/test/multi_value_test.rs | 66 |
8 files changed, 657 insertions, 1 deletions
diff --git a/mingling_picker/src/builtin/pick_numbers.rs b/mingling_picker/src/builtin/pick_numbers.rs index 6c0f0b7..a5ab0a9 100644 --- a/mingling_picker/src/builtin/pick_numbers.rs +++ b/mingling_picker/src/builtin/pick_numbers.rs @@ -1,4 +1,4 @@ -use crate::{SinglePickable, pickable_needed::*}; +use crate::{BoundaryCheck, SinglePickable, pickable_needed::*}; macro_rules! impl_single_pickable_num { ($($t:ty),+ $(,)?) => { @@ -13,8 +13,68 @@ macro_rules! impl_single_pickable_num { }; } +/// 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::<f32>().is_err() + } +} + +impl BoundaryCheck for f64 { + fn check_boundary(raw: &str) -> bool { + !is_float_like(raw) || raw.parse::<f64>().is_err() + } +} diff --git a/mingling_picker/src/parselib.rs b/mingling_picker/src/parselib.rs index 456a34d..7fbd606 100644 --- a/mingling_picker/src/parselib.rs +++ b/mingling_picker/src/parselib.rs @@ -4,6 +4,9 @@ pub use flag_matcher::*; mod arg_matcher; pub use arg_matcher::*; +mod multi_arg_matcher; +pub use multi_arg_matcher::*; + mod pos_matcher; pub use pos_matcher::*; diff --git a/mingling_picker/src/parselib/multi_arg_matcher.rs b/mingling_picker/src/parselib/multi_arg_matcher.rs new file mode 100644 index 0000000..e8bdb71 --- /dev/null +++ b/mingling_picker/src/parselib/multi_arg_matcher.rs @@ -0,0 +1,141 @@ +use crate::{ + matcher_needed::*, + parselib::{build_possible_flags, seek_end_of_options}, +}; + +/// `MultiArgMatcher` matches a named flag and **all** consecutive arguments +/// that follow it, stopping at the next flag, the `--` marker, or the end +/// of the argument list. +/// +/// This is the tag implementation for `Multi` and `GreedyMulti` types +/// such as `Vec<String>` (`--files a.txt b.txt`). +/// +/// # Behavior +/// +/// | Input | `on_match_all` | +/// |-------|----------------| +/// | `--val a b --val d e` | `[0, 1, 2, 5, 6]` (two groups) | +/// | `--val=1 2` | `[0, 1]` (eq mode + one extra value) | +/// +/// Args after `--` are ignored. +pub struct MultiArgMatcher; + +impl Matcher for MultiArgMatcher { + fn on_match_one( + args: &[MaskedArg], + style: &ParserStyle, + arg_info: &PickerArgInfo, + ) -> Option<usize> { + if arg_info.positional { + return args.first().map(|a| a.raw_idx); + } + + 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) { + break; + } + let matched = possible_flags + .iter() + .any(|f| Self::flag_match(arg.raw, f, style.case_sensitive, sep)); + if matched { + return Some(arg.raw_idx); + } + } + None + } + + fn on_match_all( + args: &[MaskedArg], + style: &ParserStyle, + arg_info: &PickerArgInfo, + ) -> Vec<usize> { + if arg_info.positional { + let end = seek_end_of_options(args, style); + return args + .iter() + .take_while(|a| end.is_none_or(|e| a.raw_idx < e)) + .map(|a| a.raw_idx) + .collect(); + } + + let possible_flags = build_possible_flags(style, arg_info); + let end = seek_end_of_options(args, style); + let sep = style.value_separator; + let is_flag = |raw: &str| { + raw.starts_with(style.long_prefix) || raw.starts_with(style.short_prefix) + }; + let is_our_flag = |raw: &str| { + possible_flags + .iter() + .any(|f| Self::flag_match(raw, f, style.case_sensitive, sep)) + }; + + let mut result = Vec::new(); + let mut i = 0; + while i < args.len() { + if end.is_some_and(|e| args[i].raw_idx >= e) { + break; + } + + let matched = is_our_flag(args[i].raw); + + if matched { + result.push(args[i].raw_idx); + + if Self::is_eq_match(args[i].raw, &possible_flags, style.case_sensitive, sep) { + i += 1; + while i < args.len() + && end.is_none_or(|e| args[i].raw_idx < e) + && !is_flag(args[i].raw) + { + result.push(args[i].raw_idx); + i += 1; + } + continue; + } + + i += 1; + while i < args.len() + && end.is_none_or(|e| args[i].raw_idx < e) + && !is_flag(args[i].raw) + { + result.push(args[i].raw_idx); + i += 1; + } + continue; + } + i += 1; + } + + result + } +} + +impl MultiArgMatcher { + #[inline(always)] + fn flag_match(raw: &str, flag_str: &str, case_sensitive: bool, sep: char) -> bool { + let eq = |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) && eq(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()] == sep as u8) + } + } + + #[inline(always)] + fn is_eq_match(raw: &str, flags: &[String], case_sensitive: bool, sep: char) -> bool { + flags.iter().any(|f| { + Self::flag_match(raw, f, case_sensitive, sep) + && raw.len() > f.len() + && raw.as_bytes().get(f.len()) == Some(&(sep as u8)) + }) + } +} diff --git a/mingling_picker/src/pickable.rs b/mingling_picker/src/pickable.rs index 959aad8..758ae9a 100644 --- a/mingling_picker/src/pickable.rs +++ b/mingling_picker/src/pickable.rs @@ -3,6 +3,9 @@ use crate::{PickerArg, PickerArgAttr, PickerArgInfo, PickerArgResult, PickerArgs mod single_pickable; pub use single_pickable::*; +mod multi_pickable; +pub use multi_pickable::*; + /// `Pickable` trait defines how to parse a type instance from command-line arguments. /// /// This trait is the core abstraction of the `Picker` argument parsing system, dividing the diff --git a/mingling_picker/src/pickable/multi_pickable.rs b/mingling_picker/src/pickable/multi_pickable.rs new file mode 100644 index 0000000..bbed6c6 --- /dev/null +++ b/mingling_picker/src/pickable/multi_pickable.rs @@ -0,0 +1,200 @@ +use std::marker::PhantomData; +use std::ops::{Deref, DerefMut}; + +use crate::{ + matcher_needed::Matcher, + parselib::{MultiArgMatcher, ParserStyle}, + Pickable, PickerArg, PickerArgAttr, PickerArgResult, + SinglePickable, TagPhaseContext, +}; + +/// 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>; +} + +/// Marker: unit type that always accepts — no boundary. +pub struct NoBoundary; + +impl BoundaryCheck for NoBoundary { + #[inline(always)] + fn check_boundary(_raw: &str) -> bool { + false // never stop: greedy + } +} + +/// `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) + } +} + +/// `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] { + 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 +} + +impl<T> DerefMut for VecUntil<T> { + fn deref_mut(&mut self) -> &mut Vec<T> { + &mut self.inner + } +} + +// Concrete Pickable impls + +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) + } +} + +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/test/src/test.rs b/mingling_picker/test/src/test.rs index 7d18c86..9c53514 100644 --- a/mingling_picker/test/src/test.rs +++ b/mingling_picker/test/src/test.rs @@ -1,5 +1,7 @@ mod arg_matcher_test; mod basic_test; +mod multi_arg_test; +mod multi_value_test; mod pos_matcher_test; mod priority_test; mod route_test; diff --git a/mingling_picker/test/src/test/multi_arg_test.rs b/mingling_picker/test/src/test/multi_arg_test.rs new file mode 100644 index 0000000..377357e --- /dev/null +++ b/mingling_picker/test/src/test/multi_arg_test.rs @@ -0,0 +1,181 @@ +use mingling_picker::parselib::{Matcher, MultiArgMatcher, UNIX_STYLE}; +use mingling_picker::PickerArgInfo; + +use crate::make_args; + +// on_match_one — basic + +#[test] +fn test_multi_one_finds_first_flag() { + let mut info = PickerArgInfo::new(); + info.set_long("val"); + let args = make_args(&[("--other", 0), ("--val", 1), ("a", 2), ("b", 3)]); + let result = MultiArgMatcher::on_match_one(&args, &UNIX_STYLE, &info); + assert_eq!(result, Some(1)); +} + +#[test] +fn test_multi_one_no_match() { + let mut info = PickerArgInfo::new(); + info.set_long("val"); + let args = make_args(&[("--other", 0)]); + let result = MultiArgMatcher::on_match_one(&args, &UNIX_STYLE, &info); + assert_eq!(result, None); +} + +// on_match_all — named, basic multi-value + +#[test] +fn test_multi_all_flag_takes_all_values_until_next_flag() { + // --val a b c → all three values belong to --val + let mut info = PickerArgInfo::new(); + info.set_long("val"); + let args = make_args(&[("--val", 0), ("a", 1), ("b", 2), ("c", 3)]); + let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info); + assert_eq!(result, vec![0, 1, 2, 3]); +} + +#[test] +fn test_multi_all_stops_at_next_flag() { + // --val a b --other c d → only a,b belong to --val + let mut info = PickerArgInfo::new(); + info.set_long("val"); + let args = make_args(&[("--val", 0), ("a", 1), ("b", 2), ("--other", 3), ("c", 4)]); + let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info); + assert_eq!(result, vec![0, 1, 2]); +} + +#[test] +fn test_multi_all_flag_no_values() { + // --val at end with no values → just the flag + let mut info = PickerArgInfo::new(); + info.set_long("val"); + let args = make_args(&[("--val", 0)]); + let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info); + assert_eq!(result, vec![0]); +} + +#[test] +fn test_multi_all_empty() { + let mut info = PickerArgInfo::new(); + info.set_long("val"); + let args = vec![]; + let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info); + assert!(result.is_empty()); +} + +// on_match_all — named, multiple occurrences of same flag + +#[test] +fn test_multi_all_two_occurrences() { + // --val a b --val c d → two groups + let mut info = PickerArgInfo::new(); + info.set_long("val"); + let args = make_args(&[ + ("--val", 0), ("a", 1), ("b", 2), + ("--val", 3), ("c", 4), ("d", 5), + ]); + let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info); + assert_eq!(result, vec![0, 1, 2, 3, 4, 5]); +} + +#[test] +fn test_multi_all_skips_non_matching_args() { + // --val a --other b --val c → only --val groups + let mut info = PickerArgInfo::new(); + info.set_long("val"); + let args = make_args(&[ + ("--val", 0), ("a", 1), + ("--other", 2), ("b", 3), + ("--val", 4), ("c", 5), + ]); + let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info); + assert_eq!(result, vec![0, 1, 4, 5]); +} + +// on_match_all — named, eq mode + +#[test] +fn test_multi_all_eq_mode() { + // --val=a b → eq mode + one extra value + let mut info = PickerArgInfo::new(); + info.set_long("val"); + let args = make_args(&[("--val=a", 0), ("b", 1)]); + let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info); + assert_eq!(result, vec![0, 1]); +} + +#[test] +fn test_multi_all_eq_mode_no_extra() { + // --val=a alone → just the eq flag + let mut info = PickerArgInfo::new(); + info.set_long("val"); + let args = make_args(&[("--val=a", 0)]); + let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info); + assert_eq!(result, vec![0]); +} + +#[test] +fn test_multi_all_mixed_eq_and_regular() { + // --val=a b --val c d + let mut info = PickerArgInfo::new(); + info.set_long("val"); + let args = make_args(&[ + ("--val=a", 0), ("b", 1), ("--val", 2), ("c", 3), ("d", 4), + ]); + let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info); + assert_eq!(result, vec![0, 1, 2, 3, 4]); +} + +// on_match_all — short flag + +#[test] +fn test_multi_all_short_flag() { + let mut info = PickerArgInfo::new(); + info.set_short('v'); + info.set_long("val"); + let args = make_args(&[("-v", 0), ("a", 1), ("b", 2)]); + let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info); + assert_eq!(result, vec![0, 1, 2]); +} + +// on_match_all — end-of-options marker + +#[test] +fn test_multi_all_stops_at_end_of_options() { + // --val a -- b → stops before `--` + let mut info = PickerArgInfo::new(); + info.set_long("val"); + let args = make_args(&[("--val", 0), ("a", 1), ("--", 2), ("b", 3)]); + let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info); + assert_eq!(result, vec![0, 1]); +} + +#[test] +fn test_multi_all_flag_after_end_ignored() { + let mut info = PickerArgInfo::new(); + info.set_long("val"); + let args = make_args(&[("--", 0), ("--val", 1), ("a", 2)]); + let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info); + assert!(result.is_empty()); +} + +// on_match_all — positional + +#[test] +fn test_multi_all_positional() { + let mut info = PickerArgInfo::new(); + info.set_positional(true); + let args = make_args(&[("a.txt", 0), ("b.txt", 1)]); + let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info); + assert_eq!(result, vec![0, 1]); +} + +#[test] +fn test_multi_all_positional_stops_at_end_of_options() { + let mut info = PickerArgInfo::new(); + info.set_positional(true); + let args = make_args(&[("a.txt", 0), ("--", 1), ("b.txt", 2)]); + let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info); + assert_eq!(result, vec![0]); +} diff --git a/mingling_picker/test/src/test/multi_value_test.rs b/mingling_picker/test/src/test/multi_value_test.rs new file mode 100644 index 0000000..50d08af --- /dev/null +++ b/mingling_picker/test/src/test/multi_value_test.rs @@ -0,0 +1,66 @@ +use mingling_picker::value::Flag; +use mingling_picker::{IntoPicker, VecUntil, macros::arg}; + +#[test] +fn test_vec_until_i16_named() { + let (nums, rest): (VecUntil<i16>, Flag) = vec!["--nums", "1", "2", "3", "abc"] + .to_picker() + .pick(&arg![nums: VecUntil<i16>]) + .pick(&arg![rest: Flag]) + .unwrap(); + assert_eq!(*nums, vec![1i16, 2, 3]); + assert_eq!(rest, Flag::Inactive); +} + +#[test] +fn test_vec_until_i16_stops_at_non_number() { + let nums: VecUntil<i16> = vec!["--nums", "42", "abc", "100"] + .to_picker() + .pick(&arg![nums: VecUntil<i16>]) + .unwrap(); + assert_eq!(*nums, vec![42i16]); +} + +#[test] +fn test_vec_until_i16_empty() { + let nums: VecUntil<i16> = vec!["--nums"] + .to_picker() + .pick(&arg![nums: VecUntil<i16>]) + .unwrap(); + assert!(nums.is_empty()); +} + +#[test] +fn test_vec_until_i16_stops_at_next_flag() { + let nums: VecUntil<i16> = vec!["--nums", "1", "2", "--other", "3"] + .to_picker() + .pick(&arg![nums: VecUntil<i16>]) + .unwrap(); + assert_eq!(*nums, vec![1i16, 2]); +} + +#[test] +fn test_vec_until_i16_stops_at_end_of_options() { + let nums: VecUntil<i16> = vec!["--nums", "1", "2", "--", "3"] + .to_picker() + .pick(&arg![nums: VecUntil<i16>]) + .unwrap(); + assert_eq!(*nums, vec![1i16, 2]); +} + +// Two VecUntil<T> with different boundary behaviours + +#[test] +fn test_vec_until_f64_and_i16_positional() { + // Both are positional VecUntil. f64 takes all valid floats, + // i16 takes what's left. But note: "1", "2", "3" are also valid + // f64 values, so f64's check_boundary never fires — it consumes + // everything, and i16 gets nothing. + let (floats, ints): (VecUntil<f64>, VecUntil<i16>) = vec!["1.5", "2.5", "3.5", "1", "2", "3"] + .to_picker() + .pick(&arg![VecUntil<f64>]) + .pick(&arg![VecUntil<i16>]) + .unwrap(); + assert_eq!(*floats, vec![1.5, 2.5, 3.5]); + assert_eq!(*ints, vec![1i16, 2, 3]); +} |
