diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-08-17 04:05:41 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-08-17 04:05:41 +0800 |
| commit | aa251efb87b561f62266628f06ad341253fbbdc5 (patch) | |
| tree | 7d2cc2f34f3e4a7282d8d6f0ec0f5ae4bfc87002 /mingling/src/parser | |
| parent | c23c590330af83afb6e146bcd9b0a274b3689d22 (diff) | |
refactor!: remove legacy parser feature and migrate to picker
The legacy `parser` feature and its module tree (`mingling::parser`,
`Argument`, `Picker`, `Pickable`, etc.) have been fully removed and
replaced by the `picker` feature powered by `arg-picker`.
BREAKING CHANGE: Remove `parser` feature and use `picker` instead.
Migration guide: Replace `features = ["parser"]` with
`features = ["picker"]` and update API usage per the provided table.
Diffstat (limited to 'mingling/src/parser')
| -rw-r--r-- | mingling/src/parser/args.rs | 177 | ||||
| -rw-r--r-- | mingling/src/parser/picker.rs | 815 | ||||
| -rw-r--r-- | mingling/src/parser/picker/bools.rs | 143 | ||||
| -rw-r--r-- | mingling/src/parser/picker/builtin.rs | 113 | ||||
| -rw-r--r-- | mingling/src/parser/picker/path.rs | 145 | ||||
| -rw-r--r-- | mingling/src/parser/picker/path/rule.rs | 231 | ||||
| -rw-r--r-- | mingling/src/parser/test.rs | 731 |
7 files changed, 0 insertions, 2355 deletions
diff --git a/mingling/src/parser/args.rs b/mingling/src/parser/args.rs deleted file mode 100644 index c7139c4..0000000 --- a/mingling/src/parser/args.rs +++ /dev/null @@ -1,177 +0,0 @@ -// Doc Not Optimize -use std::mem::replace; - -use mingling_core::{Flag, special_argument, special_arguments, special_flag}; - -/// User input arguments -#[derive(Debug, Default, Clone)] -pub struct Argument { - vec: Vec<String>, -} - -impl From<Vec<&str>> for Argument { - fn from(vec: Vec<&str>) -> Self { - Self { - vec: vec - .into_iter() - .map(std::string::ToString::to_string) - .collect(), - } - } -} - -impl From<&'static str> for Argument { - fn from(s: &'static str) -> Self { - Self { - vec: vec![s.to_string()], - } - } -} - -impl From<&'static [&'static str]> for Argument { - fn from(slice: &'static [&'static str]) -> Self { - Self { - vec: slice.iter().map(|&s| s.to_string()).collect(), - } - } -} - -impl<const N: usize> From<[&'static str; N]> for Argument { - fn from(slice: [&'static str; N]) -> Self { - Self { - vec: slice.iter().map(|&s| s.to_string()).collect(), - } - } -} - -impl<const N: usize> From<&'static [&'static str; N]> for Argument { - fn from(slice: &'static [&'static str; N]) -> Self { - Self { - vec: slice.iter().map(|&s| s.to_string()).collect(), - } - } -} - -impl From<Vec<String>> for Argument { - fn from(vec: Vec<String>) -> Self { - Self { vec } - } -} - -impl AsRef<[String]> for Argument { - fn as_ref(&self) -> &[String] { - &self.vec - } -} - -impl std::ops::Deref for Argument { - type Target = Vec<String>; - - fn deref(&self) -> &Self::Target { - &self.vec - } -} - -impl std::ops::DerefMut for Argument { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.vec - } -} - -impl Argument { - /// Picks a single argument with the given flag - pub fn pick_argument<F>(&mut self, flag: F) -> Option<String> - where - F: Into<Flag>, - { - if self.is_empty() { - return None; - } - - let flag: Flag = flag.into(); - if flag.is_empty() { - // No flag - return Some(self.vec.remove(0)); - } - // Has any flag - for argument in flag.iter() { - let value = special_argument!(self.vec, argument); - if value.is_some() { - return value; - } - } - None - } - - /// Picks arguments with the given flag - pub fn pick_arguments<F>(&mut self, flag: F) -> Vec<String> - where - F: Into<Flag>, - { - let mut str_result = Vec::new(); - - if self.is_empty() { - return str_result; - } - - let flag: Flag = flag.into(); - if flag.is_empty() { - let value = special_arguments!(self.vec, ""); - str_result.extend(value); - } else { - for argument in flag.iter() { - let value = special_arguments!(self.vec, argument); - str_result.extend(value); - } - } - - str_result - } - - /// Picks a flag with the given flag - pub fn pick_flag<F>(&mut self, flag: F) -> bool - where - F: Into<Flag>, - { - if self.is_empty() { - return false; - } - - let flag: Flag = flag.into(); - if flag.is_empty() { - let first = self.vec.remove(0); - let first_lower = first.to_lowercase(); - let trimmed = first_lower.trim(); - let result = match trimmed { - "y" | "yes" | "true" | "1" => return true, - "n" | "no" | "false" | "0" => return false, - _ => false, - }; - return result; - } - // Has any flag - for argument in flag.iter() { - let enabled = special_flag!(self.vec, argument); - if enabled { - return enabled; - } - } - false - } - - /// Dump all remaining arguments - pub const fn dump_remains(&mut self) -> Vec<String> { - let new = Vec::new(); - replace(&mut self.vec, new) - } - - /// Removes all arguments that start with a dash ('-') - /// - /// This method filters out all command-line style flags from the arguments, - /// returning a new `Argument` instance containing only non-flag arguments. - #[must_use] - pub fn strip_all_flags(mut self) -> Self { - self.vec.retain(|f| !f.starts_with('-')); - self - } -} diff --git a/mingling/src/parser/picker.rs b/mingling/src/parser/picker.rs deleted file mode 100644 index 2f43e8c..0000000 --- a/mingling/src/parser/picker.rs +++ /dev/null @@ -1,815 +0,0 @@ -// Doc Not Optimize -use crate::parser::Argument; -use mingling_core::{EnumTag, Flag}; - -#[doc(hidden)] -pub mod builtin; - -#[doc(hidden)] -pub mod bools; - -#[doc(hidden)] -pub mod path; - -/// A builder for extracting values from command-line arguments. -/// -/// The `Picker` struct holds parsed arguments and provides a fluent interface -/// to extract values associated with specific flags. -#[derive(Default)] -pub struct Picker { - /// The parsed command-line arguments. - pub args: Argument, -} - -impl Picker { - /// Creates a new `Picker` from a value that can be converted into `Argument`. - pub fn new(args: impl Into<Argument>) -> Self { - Self { args: args.into() } - } - - /// Extracts a value for the given flag and returns a `Pick1` builder (no route). - /// - /// The extracted type `TNext` must implement `Pickable` and `Default`. - /// If the flag is not present, the default value for `TNext` is used. - pub fn pick<TNext>(mut self, val: impl Into<Flag>) -> Pick1<TNext> - where - TNext: Pickable<Output = TNext> + Default, - { - let v = TNext::pick(&mut self.args, val.into()).unwrap_or_default(); - Pick1 { - args: self.args, - val_1: v, - } - } - - /// Extracts a value for the given flag, returning the provided default value if not present, - /// and returns a `Pick1` builder (no route). - /// - /// The extracted type `TNext` must implement `Pickable`. - /// If the flag is not present, the provided `or` value is used. - pub fn pick_or<TNext>(mut self, val: impl Into<Flag>, or: impl Into<TNext>) -> Pick1<TNext> - where - TNext: Pickable<Output = TNext>, - { - let v = TNext::pick(&mut self.args, val.into()).unwrap_or_else(|| or.into()); - Pick1 { - args: self.args, - val_1: v, - } - } - - /// Extracts a value for the given flag, storing the provided route if the flag is not present, - /// and returns a `PickWithRoute1` builder (with route). - /// - /// The extracted type `TNext` must implement `Pickable` and `Default`. - /// If the flag is not present, the default value for `TNext` is used and the provided `route` - /// is stored in the returned builder for later error handling. - pub fn pick_or_route<TNext, R>( - mut self, - val: impl Into<Flag>, - route: R, - ) -> PickWithRoute1<TNext, R> - where - TNext: Pickable<Output = TNext> + Default, - { - let Some(v) = TNext::pick(&mut self.args, val.into()) else { - return PickWithRoute1 { - args: self.args, - val_1: TNext::default(), - route: Some(route), - }; - }; - PickWithRoute1 { - args: self.args, - val_1: v, - route: None, - } - } - - /// Extracts a value for the given flag, returning `None` if the flag is not present, - /// and returns an `Option<Pick1<TNext>>` builder (no route). - /// - /// The extracted type `TNext` must implement `Pickable`. - /// If the flag is not present, `None` is returned. - pub fn require<TNext>(mut self, val: impl Into<Flag>) -> Option<Pick1<TNext>> - where - TNext: Pickable<Output = TNext>, - { - let v = TNext::pick(&mut self.args, val.into()); - match v { - Some(s) => Some(Pick1 { - args: self.args, - val_1: s, - }), - None => None, - } - } - - /// Applies an operation to the parsed arguments and returns the modified `Picker`. - /// - /// Takes a closure that receives the current `Argument` and returns a new `Argument`. - /// The returned `Argument` replaces the original arguments in the builder. - /// This method can be used to modify or transform the parsed arguments before extracting values. - #[must_use] - pub fn operate_args<F: FnOnce(Argument) -> Argument>(mut self, operation: F) -> Self { - self.args = operation(self.args); - self - } -} - -impl<T: Into<Argument>> From<T> for Picker { - fn from(value: T) -> Self { - Self::new(value) - } -} - -/// Extracts values from command-line arguments -/// -/// The `Pickable` trait defines how to extract the value of a specific flag from parsed arguments -pub trait Pickable { - /// The output type produced by the extraction operation, must implement the `Default` trait - type Output: Default; - - /// Extracts the value associated with the given flag from the provided arguments - /// - /// If the flag exists and the value can be successfully extracted, returns `Some(Output)`; - /// otherwise returns `None` - fn pick(args: &mut Argument, flag: Flag) -> Option<Self::Output>; -} - -// Non-routed Pick structs (no R parameter, no route field) - -/// Internal macro: generates the struct definition and common methods -/// (after, `after_or_route`, `operate_args`) for non-routed Pick structs. -macro_rules! define_pick_struct { - ($n:ident $final:ident $final_val:ident $route_self:ident $($T:ident $val:ident),+ $(,)?) => { - #[doc(hidden)] - pub struct $n<$($T,)+> - where - $($T: Pickable,)+ - { - #[allow(dead_code)] - args: Argument, - $(pub $val: $T,)+ - } - - impl<$($T,)+> $n<$($T,)+> - where - $($T: Pickable,)+ - { - /// Applies a transformation to the last extracted value. - /// - /// Takes a closure that receives the last extracted value and returns a new value of the same type. - /// The transformed value replaces the original value in the builder. - /// This method can be used to modify or validate the extracted value before final unpacking. - #[must_use] - pub fn after<F>(mut self, mut edit: F) -> Self - where - F: FnMut($final) -> $final, - { - self.$final_val = edit(self.$final_val); - self - } - - /// Applies a transformation to the last extracted value, storing a route if the transformation fails. - /// - /// Takes a closure that receives a reference to the last extracted value and returns a `Result`. - /// If the closure returns `Ok(new_value)`, the new value replaces the original value in the builder. - /// If the closure returns `Err(route)`, the provided `route` is stored in the builder for later error handling. - /// If a route was already stored from a previous `pick_or_route` call, the existing route is preserved. - #[must_use] - pub fn after_or_route<F, R>(mut self, mut edit: F) -> $route_self<$($T,)+ R> - where - F: FnMut(&$final) -> Result<$final, R>, - { - match edit(&self.$final_val) { - Ok(new_value) => { - self.$final_val = new_value; - $route_self { - args: self.args, - $($val: self.$val,)+ - route: None, - } - } - Err(err_route) => { - $route_self { - args: self.args, - $($val: self.$val,)+ - route: Some(err_route), - } - } - } - } - - /// Applies an operation to the parsed arguments and returns the modified builder. - /// - /// Takes a closure that receives the current `Argument` and returns a new `Argument`. - /// The returned `Argument` replaces the original arguments in the builder. - /// This method can be used to modify or transform the parsed arguments before extracting values. - #[must_use] - pub fn operate_args<F: FnOnce(Argument) -> Argument>(mut self, operation: F) -> Self { - self.args = operation(self.args); - self - } - } - }; -} - -// Pick1 special case (single value) - -define_pick_struct! { Pick1 T1 val_1 PickWithRoute1 T1 val_1 } - -impl<T1> From<Pick1<T1>> for (T1,) -where - T1: Pickable, -{ - fn from(pick: Pick1<T1>) -> Self { - (pick.val_1,) - } -} - -impl<T1> Pick1<T1> -where - T1: Pickable, -{ - /// Unpacks the builder into the extracted value. - /// - /// Always returns the value directly since there is no route. - pub fn unpack(self) -> T1 { - self.val_1 - } -} - -// Pick2 .. Pick12 - -macro_rules! impl_pick_from_tuple { - ($n:ident $($T:ident $val:ident),+) => { - impl<$($T,)+> From<$n<$($T,)+>> for ($($T,)+) - where - $($T: Pickable,)+ - { - fn from(pick: $n<$($T,)+>) -> Self { - ($(pick.$val,)+) - } - } - }; -} - -macro_rules! impl_pick_unpack_tuple { - ($n:ident $($T:ident $val:ident),+) => { - impl<$($T,)+> $n<$($T,)+> - where - $($T: Pickable,)+ - { - /// Unpacks the builder into a tuple of extracted values. - /// - /// Always returns the tuple directly since there is no route. - pub fn unpack(self) -> ($($T,)+) { - ($(self.$val,)+) - } - } - }; -} - -define_pick_struct! { Pick2 T2 val_2 PickWithRoute2 T1 val_1, T2 val_2 } -impl_pick_from_tuple! { Pick2 T1 val_1, T2 val_2 } -impl_pick_unpack_tuple! { Pick2 T1 val_1, T2 val_2 } - -define_pick_struct! { Pick3 T3 val_3 PickWithRoute3 T1 val_1, T2 val_2, T3 val_3 } -impl_pick_from_tuple! { Pick3 T1 val_1, T2 val_2, T3 val_3 } -impl_pick_unpack_tuple! { Pick3 T1 val_1, T2 val_2, T3 val_3 } - -define_pick_struct! { Pick4 T4 val_4 PickWithRoute4 T1 val_1, T2 val_2, T3 val_3, T4 val_4 } -impl_pick_from_tuple! { Pick4 T1 val_1, T2 val_2, T3 val_3, T4 val_4 } -impl_pick_unpack_tuple! { Pick4 T1 val_1, T2 val_2, T3 val_3, T4 val_4 } - -define_pick_struct! { Pick5 T5 val_5 PickWithRoute5 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5 } -impl_pick_from_tuple! { Pick5 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5 } -impl_pick_unpack_tuple! { Pick5 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5 } - -define_pick_struct! { Pick6 T6 val_6 PickWithRoute6 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6 } -impl_pick_from_tuple! { Pick6 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6 } -impl_pick_unpack_tuple! { Pick6 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6 } - -define_pick_struct! { Pick7 T7 val_7 PickWithRoute7 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7 } -impl_pick_from_tuple! { Pick7 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7 } -impl_pick_unpack_tuple! { Pick7 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7 } - -define_pick_struct! { Pick8 T8 val_8 PickWithRoute8 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8 } -impl_pick_from_tuple! { Pick8 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8 } -impl_pick_unpack_tuple! { Pick8 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8 } - -define_pick_struct! { Pick9 T9 val_9 PickWithRoute9 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9 } -impl_pick_from_tuple! { Pick9 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9 } -impl_pick_unpack_tuple! { Pick9 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9 } - -define_pick_struct! { Pick10 T10 val_10 PickWithRoute10 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10 } -impl_pick_from_tuple! { Pick10 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10 } -impl_pick_unpack_tuple! { Pick10 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10 } - -define_pick_struct! { Pick11 T11 val_11 PickWithRoute11 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10, T11 val_11 } -impl_pick_from_tuple! { Pick11 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10, T11 val_11 } -impl_pick_unpack_tuple! { Pick11 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10, T11 val_11 } - -define_pick_struct! { Pick12 T12 val_12 PickWithRoute12 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10, T11 val_11, T12 val_12 } -impl_pick_from_tuple! { Pick12 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10, T11 val_11, T12 val_12 } -impl_pick_unpack_tuple! { Pick12 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10, T11 val_11, T12 val_12 } - -// Non-routed Pick chaining methods (pick, pick_or, pick_or_route, require) - -#[doc(hidden)] -macro_rules! impl_pick_next { - ($n:ident $next:ident $next_val:ident $route_next:ident $($T:ident $val:ident),+) => { - impl<$($T,)+> $n<$($T,)+> - where - $($T: Pickable,)+ - { - /// Extracts a value for the given flag and returns a `PickN` builder (no route). - pub fn pick<TNext>(mut self, val: impl Into<mingling_core::Flag>) -> $next<$($T,)+ TNext> - where - TNext: Pickable<Output = TNext> + Default, - { - let v = TNext::pick(&mut self.args, val.into()).unwrap_or_default(); - $next { - args: self.args, - $($val: self.$val,)+ - $next_val: v, - } - } - - /// Extracts a value for the given flag, returning the provided default value if not present, - /// and returns a `PickN` builder (no route). - pub fn pick_or<TNext>(mut self, val: impl Into<mingling_core::Flag>, or: impl Into<TNext>) -> $next<$($T,)+ TNext> - where - TNext: Pickable<Output = TNext>, - { - let v = TNext::pick(&mut self.args, val.into()).unwrap_or_else(|| or.into()); - $next { - args: self.args, - $($val: self.$val,)+ - $next_val: v, - } - } - - /// Extracts a value for the given flag, storing the provided route if the flag is not present, - /// and returns a `PickWithRouteN` builder (with route). - pub fn pick_or_route<TNext, R>( - mut self, - val: impl Into<mingling_core::Flag>, - route: R, - ) -> $route_next<$($T,)+ TNext, R> - where - TNext: Pickable<Output = TNext> + Default, - { - let Some(v) = TNext::pick(&mut self.args, val.into()) else { - return $route_next { - args: self.args, - $($val: self.$val,)+ - $next_val: TNext::default(), - route: Some(route), - }; - }; - $route_next { - args: self.args, - $($val: self.$val,)+ - $next_val: v, - route: None, - } - } - - /// Extracts a value for the given flag, returning `None` if the flag is not present, - /// and returns an `Option<PickN<TNext>>` builder (no route). - pub fn require<TNext>(mut self, val: impl Into<mingling_core::Flag>) -> Option<$next<$($T,)+ TNext>> - where - TNext: Pickable<Output = TNext>, - { - let v = TNext::pick(&mut self.args, val.into()); - match v { - Some(s) => Some($next { - args: self.args, - $($val: self.$val,)+ - $next_val: s, - }), - None => None, - } - } - } - }; -} - -impl_pick_next! { Pick1 Pick2 val_2 PickWithRoute2 T1 val_1 } -impl_pick_next! { Pick2 Pick3 val_3 PickWithRoute3 T1 val_1, T2 val_2 } -impl_pick_next! { Pick3 Pick4 val_4 PickWithRoute4 T1 val_1, T2 val_2, T3 val_3 } -impl_pick_next! { Pick4 Pick5 val_5 PickWithRoute5 T1 val_1, T2 val_2, T3 val_3, T4 val_4 } -impl_pick_next! { Pick5 Pick6 val_6 PickWithRoute6 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5 } -impl_pick_next! { Pick6 Pick7 val_7 PickWithRoute7 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6 } -impl_pick_next! { Pick7 Pick8 val_8 PickWithRoute8 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7 } -impl_pick_next! { Pick8 Pick9 val_9 PickWithRoute9 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8 } -impl_pick_next! { Pick9 Pick10 val_10 PickWithRoute10 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9 } -impl_pick_next! { Pick10 Pick11 val_11 PickWithRoute11 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10 } -impl_pick_next! { Pick11 Pick12 val_12 PickWithRoute12 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10, T11 val_11 } - -// Routed PickWithRoute structs (with R parameter, route field) - -/// Internal macro: generates the routed struct definition and common methods -/// (after, `after_or_route`, `operate_args`) for `PickWithRoute` structs. -macro_rules! define_pick_with_route_struct { - ($n:ident $final:ident $final_val:ident $($T:ident $val:ident),+) => { - #[doc(hidden)] - pub struct $n<$($T,)+ R> - where - $($T: Pickable,)+ - { - #[allow(dead_code)] - args: Argument, - $(pub $val: $T,)+ - route: Option<R>, - } - - impl<$($T,)+ R> $n<$($T,)+ R> - where - $($T: Pickable,)+ - { - /// Applies a transformation to the last extracted value. - /// - /// Takes a closure that receives the last extracted value and returns a new value of the same type. - /// The transformed value replaces the original value in the builder. - /// This method can be used to modify or validate the extracted value before final unpacking. - #[must_use] - pub fn after<F>(mut self, mut edit: F) -> Self - where - F: FnMut($final) -> $final, - { - self.$final_val = edit(self.$final_val); - self - } - - /// Applies a transformation to the last extracted value, storing a route if the transformation fails. - /// - /// Takes a closure that receives a reference to the last extracted value and returns a `Result`. - /// If the closure returns `Ok(new_value)`, the new value replaces the original value in the builder. - /// If the closure returns `Err(route)`, the provided `route` is stored in the builder for later error handling. - /// If a route was already stored from a previous `pick_or_route` call, the existing route is preserved. - #[must_use] - pub fn after_or_route<F>(mut self, mut edit: F) -> Self - where - F: FnMut(&$final) -> Result<$final, R>, - { - let value = &self.$final_val; - match edit(value) { - Ok(new_value) => { - self.$final_val = new_value; - } - Err(err_route) => { - let new_route = match self.route { - Some(existing_route) => Some(existing_route), - None => Some(err_route), - }; - self.route = new_route; - } - } - self - } - - /// Applies an operation to the parsed arguments and returns the modified builder. - /// - /// Takes a closure that receives the current `Argument` and returns a new `Argument`. - /// The returned `Argument` replaces the original arguments in the builder. - /// This method can be used to modify or transform the parsed arguments before extracting values. - #[must_use] - pub fn operate_args<F: FnOnce(Argument) -> Argument>(mut self, operation: F) -> Self { - self.args = operation(self.args); - self - } - } - }; -} - -/// Internal macro: generates `From` impl for routed `PickWithRouteN` into a tuple. -macro_rules! impl_pick_with_route_from_tuple { - ($n:ident $($T:ident $val:ident),+) => { - impl<$($T,)+ R> From<$n<$($T,)+ R>> for ($($T,)+) - where - $($T: Pickable,)+ - { - fn from(pick: $n<$($T,)+ R>) -> Self { - ($(pick.$val,)+) - } - } - }; -} - -/// Internal macro: generates `unpack` and `unpack_directly` for routed `PickWithRouteN` (N >= 2). -macro_rules! impl_pick_with_route_unpack_tuple { - ($n:ident $($T:ident $val:ident),+) => { - impl<$($T,)+ R> $n<$($T,)+ R> - where - $($T: Pickable,)+ - { - /// Unpacks the builder into a tuple of extracted values. - /// - /// Returns `Ok((T1, T2, ...))` if no route was stored. - /// Returns `Err(R)` if a route was stored via `pick_or_route` or `after_or_route`. - /// - /// # Errors - /// - /// Returns `Err(R)` if a route was stored via `pick_or_route` or `after_or_route`. - pub fn unpack(self) -> Result<($($T,)+), R> { - match self.route { - Some(route) => Err(route), - None => Ok(($(self.$val,)+)), - } - } - - /// Unpacks the builder into a tuple of extracted values. - /// - /// Returns the tuple of extracted values regardless of route state. - #[must_use] - pub fn unpack_directly(self) -> ($($T,)+) { - ($(self.$val,)+) - } - } - }; -} - -// PickWithRoute1 special case (single value) - -define_pick_with_route_struct! { PickWithRoute1 T1 val_1 T1 val_1 } - -impl<T1, R> From<PickWithRoute1<T1, R>> for (T1,) -where - T1: Pickable, -{ - fn from(pick: PickWithRoute1<T1, R>) -> Self { - (pick.val_1,) - } -} - -impl<T1, R> PickWithRoute1<T1, R> -where - T1: Pickable, -{ - /// Unpacks the builder into the extracted value. - /// - /// Returns `Ok(T1)` if no route was stored. - /// Returns `Err(R)` if a route was stored via `pick_or_route` or `after_or_route`. - /// - /// # Errors - /// - /// Returns `Err(R)` if a route was stored via `pick_or_route` or `after_or_route`. - pub fn unpack(self) -> Result<T1, R> { - match self.route { - Some(route) => Err(route), - None => Ok(self.val_1), - } - } - - /// Unpacks the builder into the extracted value. - /// - /// Returns the extracted value regardless of route state. - #[must_use] - pub fn unpack_directly(self) -> T1 { - self.val_1 - } -} - -// PickWithRoute2 .. PickWithRoute12 - -define_pick_with_route_struct! { PickWithRoute2 T2 val_2 T1 val_1, T2 val_2 } -impl_pick_with_route_from_tuple! { PickWithRoute2 T1 val_1, T2 val_2 } -impl_pick_with_route_unpack_tuple! { PickWithRoute2 T1 val_1, T2 val_2 } - -define_pick_with_route_struct! { PickWithRoute3 T3 val_3 T1 val_1, T2 val_2, T3 val_3 } -impl_pick_with_route_from_tuple! { PickWithRoute3 T1 val_1, T2 val_2, T3 val_3 } -impl_pick_with_route_unpack_tuple! { PickWithRoute3 T1 val_1, T2 val_2, T3 val_3 } - -define_pick_with_route_struct! { PickWithRoute4 T4 val_4 T1 val_1, T2 val_2, T3 val_3, T4 val_4 } -impl_pick_with_route_from_tuple! { PickWithRoute4 T1 val_1, T2 val_2, T3 val_3, T4 val_4 } -impl_pick_with_route_unpack_tuple! { PickWithRoute4 T1 val_1, T2 val_2, T3 val_3, T4 val_4 } - -define_pick_with_route_struct! { PickWithRoute5 T5 val_5 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5 } -impl_pick_with_route_from_tuple! { PickWithRoute5 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5 } -impl_pick_with_route_unpack_tuple! { PickWithRoute5 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5 } - -define_pick_with_route_struct! { PickWithRoute6 T6 val_6 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6 } -impl_pick_with_route_from_tuple! { PickWithRoute6 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6 } -impl_pick_with_route_unpack_tuple! { PickWithRoute6 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6 } - -define_pick_with_route_struct! { PickWithRoute7 T7 val_7 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7 } -impl_pick_with_route_from_tuple! { PickWithRoute7 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7 } -impl_pick_with_route_unpack_tuple! { PickWithRoute7 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7 } - -define_pick_with_route_struct! { PickWithRoute8 T8 val_8 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8 } -impl_pick_with_route_from_tuple! { PickWithRoute8 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8 } -impl_pick_with_route_unpack_tuple! { PickWithRoute8 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8 } - -define_pick_with_route_struct! { PickWithRoute9 T9 val_9 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9 } -impl_pick_with_route_from_tuple! { PickWithRoute9 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9 } -impl_pick_with_route_unpack_tuple! { PickWithRoute9 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9 } - -define_pick_with_route_struct! { PickWithRoute10 T10 val_10 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10 } -impl_pick_with_route_from_tuple! { PickWithRoute10 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10 } -impl_pick_with_route_unpack_tuple! { PickWithRoute10 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10 } - -define_pick_with_route_struct! { PickWithRoute11 T11 val_11 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10, T11 val_11 } -impl_pick_with_route_from_tuple! { PickWithRoute11 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10, T11 val_11 } -impl_pick_with_route_unpack_tuple! { PickWithRoute11 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10, T11 val_11 } - -define_pick_with_route_struct! { PickWithRoute12 T12 val_12 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10, T11 val_11, T12 val_12 } -impl_pick_with_route_from_tuple! { PickWithRoute12 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10, T11 val_11, T12 val_12 } -impl_pick_with_route_unpack_tuple! { PickWithRoute12 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10, T11 val_11, T12 val_12 } - -// Routed PickWithRoute chaining methods (pick, pick_or, pick_or_route, require) - -#[doc(hidden)] -macro_rules! impl_pick_with_route_next { - ($n:ident $next:ident $next_val:ident $($T:ident $val:ident),+) => { - impl<$($T,)+ R> $n<$($T,)+ R> - where - $($T: Pickable,)+ - { - /// Extracts a value for the given flag and returns a `PickWithRouteN` builder. - pub fn pick<TNext>(mut self, val: impl Into<mingling_core::Flag>) -> $next<$($T,)+ TNext, R> - where - TNext: Pickable<Output = TNext> + Default, - { - let v = TNext::pick(&mut self.args, val.into()).unwrap_or_default(); - $next { - args: self.args, - $($val: self.$val,)+ - $next_val: v, - route: self.route, - } - } - - /// Extracts a value for the given flag, returning the provided default value if not present, - /// and returns a `PickWithRouteN` builder. - pub fn pick_or<TNext>(mut self, val: impl Into<mingling_core::Flag>, or: impl Into<TNext>) -> $next<$($T,)+ TNext, R> - where - TNext: Pickable<Output = TNext>, - { - let v = TNext::pick(&mut self.args, val.into()).unwrap_or_else(|| or.into()); - $next { - args: self.args, - $($val: self.$val,)+ - $next_val: v, - route: self.route, - } - } - - /// Extracts a value for the given flag, storing the provided route if the flag is not present, - /// and returns a `PickWithRouteN` builder. - /// - /// If a route was already stored from a previous `pick_or_route` or `after_or_route` call, - /// the existing route is preserved and the new `route` parameter is ignored. - #[allow(clippy::manual_let_else)] - pub fn pick_or_route<TNext>(mut self, val: impl Into<mingling_core::Flag>, route: R) -> $next<$($T,)+ TNext, R> - where - TNext: Pickable<Output = TNext> + Default, - { - let v = match TNext::pick(&mut self.args, val.into()) { - Some(value) => value, - None => { - let new_route = match self.route { - Some(existing_route) => Some(existing_route), - None => Some(route), - }; - return $next { - args: self.args, - $($val: self.$val,)+ - $next_val: TNext::default(), - route: new_route, - }; - } - }; - $next { - args: self.args, - $($val: self.$val,)+ - $next_val: v, - route: self.route, - } - } - - /// Extracts a value for the given flag, returning `None` if the flag is not present, - /// and returns an `Option<PickWithRouteN>` builder. - pub fn require<TNext>(mut self, val: impl Into<mingling_core::Flag>) -> Option<$next<$($T,)+ TNext, R>> - where - TNext: Pickable<Output = TNext>, - { - let v = TNext::pick(&mut self.args, val.into()); - match v { - Some(s) => Some($next { - args: self.args, - $($val: self.$val,)+ - $next_val: s, - route: self.route, - }), - None => None, - } - } - } - }; -} - -impl_pick_with_route_next! { PickWithRoute1 PickWithRoute2 val_2 T1 val_1 } -impl_pick_with_route_next! { PickWithRoute2 PickWithRoute3 val_3 T1 val_1, T2 val_2 } -impl_pick_with_route_next! { PickWithRoute3 PickWithRoute4 val_4 T1 val_1, T2 val_2, T3 val_3 } -impl_pick_with_route_next! { PickWithRoute4 PickWithRoute5 val_5 T1 val_1, T2 val_2, T3 val_3, T4 val_4 } -impl_pick_with_route_next! { PickWithRoute5 PickWithRoute6 val_6 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5 } -impl_pick_with_route_next! { PickWithRoute6 PickWithRoute7 val_7 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6 } -impl_pick_with_route_next! { PickWithRoute7 PickWithRoute8 val_8 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7 } -impl_pick_with_route_next! { PickWithRoute8 PickWithRoute9 val_9 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8 } -impl_pick_with_route_next! { PickWithRoute9 PickWithRoute10 val_10 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9 } -impl_pick_with_route_next! { PickWithRoute10 PickWithRoute11 val_11 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10 } -impl_pick_with_route_next! { PickWithRoute11 PickWithRoute12 val_12 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10, T11 val_11 } - -/// Trait for types that can be used with `Pickable` to extract enum values from command-line arguments. -/// -/// This trait combines `EnumTag` (for building an enum variant from a string name) and `Default` -/// (for providing a fallback value when the flag is not present). -/// -/// Types implementing this trait can be used with `Picker::pick`, `Picker::pick_or_route`, and -/// the chaining `.pick()` methods to extract and parse enum values from command-line arguments. -pub trait PickableEnum: EnumTag + Default {} - -impl<T> Pickable for T -where - T: PickableEnum, -{ - type Output = T; - - fn pick(args: &mut Argument, flag: Flag) -> Option<Self::Output> { - let name = args.pick_argument(flag)?; - T::build_enum(name) - } -} - -/// Trait for types that can be converted into a `Picker` to extract values from command-line arguments. -/// -/// This trait provides a convenient way to convert a value (such as `Vec<String>`, `&[String]`, etc.) -/// into a `Picker` and immediately start extracting values associated with specific flags. -pub trait AsPicker -where - Self: Into<Vec<String>>, -{ - /// Converts the value into a `Picker` by first converting it into a `Vec<String>`. - fn to_picker(self) -> Picker - where - Self: Sized, - Vec<String>: From<Self>, - { - let vec: Vec<String> = self.into(); - Picker { args: vec.into() } - } - - /// Extracts a value for the given flag and returns a `Pick1` builder (no route). - /// - /// The extracted type `TNext` must implement `Pickable` and `Default`. - /// If the flag is not present, the default value for `TNext` is used. - fn pick<TNext>(self, val: impl Into<Flag>) -> Pick1<TNext> - where - Self: Sized, - TNext: Pickable<Output = TNext> + Default, - { - let vec: Vec<String> = self.into(); - let picker: Picker = vec.into(); - picker.pick(val) - } - - /// Extracts a value for the given flag, returning the provided default value if not present, - /// and returns a `Pick1` builder (no route). - /// - /// The extracted type `TNext` must implement `Pickable`. - /// If the flag is not present, the provided `or` value is used. - fn pick_or<TNext>(self, val: impl Into<Flag>, or: impl Into<TNext>) -> Pick1<TNext> - where - TNext: Pickable<Output = TNext>, - { - let vec: Vec<String> = self.into(); - let picker: Picker = vec.into(); - picker.pick_or(val, or) - } - - /// Extracts a value for the given flag, storing the provided route if the flag is not present, - /// and returns a `PickWithRoute1` builder (with route). - /// - /// The extracted type `TNext` must implement `Pickable` and `Default`. - /// If the flag is not present, the default value for `TNext` is used and the provided `route` - /// is stored in the returned builder for later error handling. - fn pick_or_route<TNext, R>(self, val: impl Into<Flag>, route: R) -> PickWithRoute1<TNext, R> - where - TNext: Pickable<Output = TNext> + Default, - { - let vec: Vec<String> = self.into(); - let picker: Picker = vec.into(); - picker.pick_or_route(val, route) - } -} - -// Implement AsPicker for any type that can be converted into a Vec<String> -impl<T> AsPicker for T -where - T: Sized, - Vec<String>: From<T>, -{ -} diff --git a/mingling/src/parser/picker/bools.rs b/mingling/src/parser/picker/bools.rs deleted file mode 100644 index bc9fd90..0000000 --- a/mingling/src/parser/picker/bools.rs +++ /dev/null @@ -1,143 +0,0 @@ -// Doc Not Optimize -use crate::parser::Pickable; - -/// Represents a boolean-like value with `Yes` and `No` variants. -/// -/// `Yes` can be parsed from command-line arguments using positive keywords such as `"y"` or `"yes"`, -/// and defaults to `No`. -#[derive(Debug, Default)] -#[repr(u8)] -pub enum Yes { - /// The affirmative/positive variant. - Yes, - /// The negative/default variant. - #[default] - No, -} - -impl From<bool> for Yes { - fn from(b: bool) -> Self { - if b { Self::Yes } else { Self::No } - } -} - -impl From<Yes> for bool { - fn from(val: Yes) -> Self { - match val { - Yes::Yes => true, - Yes::No => false, - } - } -} - -impl std::ops::Deref for Yes { - type Target = bool; - - fn deref(&self) -> &Self::Target { - static TRUE: bool = true; - static FALSE: bool = false; - match self { - Self::Yes => &TRUE, - Self::No => &FALSE, - } - } -} - -impl Yes { - #[must_use] - pub const fn is_yes(&self) -> bool { - matches!(self, Self::Yes) - } - - #[must_use] - pub const fn is_no(&self) -> bool { - matches!(self, Self::No) - } -} - -impl Pickable for Yes { - type Output = Self; - - fn pick(args: &mut crate::parser::Argument, flag: mingling_core::Flag) -> Option<Self::Output> { - let value = pick_bool(args, flag, &["y", "yes"]); - Some(value.into()) - } -} - -/// Represents a boolean-like value with `True` and `False` variants. -/// -/// `True` can be parsed from command-line arguments using positive keywords such as `"t"` or `"true"`, -/// and defaults to `False`. -#[derive(Debug, Default)] -#[repr(u8)] -pub enum True { - /// The affirmative/positive variant. - True, - /// The negative/default variant. - #[default] - False, -} - -impl From<bool> for True { - fn from(b: bool) -> Self { - if b { Self::True } else { Self::False } - } -} - -impl From<True> for bool { - fn from(val: True) -> Self { - match val { - True::True => true, - True::False => false, - } - } -} - -impl std::ops::Deref for True { - type Target = bool; - - fn deref(&self) -> &Self::Target { - static TRUE: bool = true; - static FALSE: bool = false; - match self { - Self::True => &TRUE, - Self::False => &FALSE, - } - } -} - -impl True { - #[must_use] - pub const fn is_true(&self) -> bool { - matches!(self, Self::True) - } - - #[must_use] - pub const fn is_false(&self) -> bool { - matches!(self, Self::False) - } -} - -impl Pickable for True { - type Output = Self; - - fn pick(args: &mut crate::parser::Argument, flag: mingling_core::Flag) -> Option<Self::Output> { - let value = pick_bool(args, flag, &["true", "t"]); - Some(value.into()) - } -} - -fn pick_bool( - args: &mut crate::parser::Argument, - flag: mingling_core::Flag, - positive: &[&str], -) -> bool { - let content = args.pick_argument(flag); - content.map_or_else( - || false, - |content| { - let s = content.as_str(); - positive.contains(&s) - }, - ) -} diff --git a/mingling/src/parser/picker/builtin.rs b/mingling/src/parser/picker/builtin.rs deleted file mode 100644 index 6f67c78..0000000 --- a/mingling/src/parser/picker/builtin.rs +++ /dev/null @@ -1,113 +0,0 @@ -// Doc Not Optimize -use size::Size; - -use crate::parser::{Argument, Pickable}; - -impl Pickable for String { - type Output = Self; - - fn pick(args: &mut crate::parser::Argument, flag: mingling_core::Flag) -> Option<Self::Output> { - args.pick_argument(flag) - } -} - -impl Pickable for Vec<String> { - type Output = Self; - - fn pick(args: &mut crate::parser::Argument, flag: mingling_core::Flag) -> Option<Self::Output> { - Some(args.pick_arguments(flag)) - } -} - -macro_rules! impl_pickable_for_number { - ($($t:ty),*) => { - $( - impl Pickable for $t { - type Output = $t; - - fn pick(args: &mut crate::parser::Argument, flag: mingling_core::Flag) -> Option<Self::Output> { - let picked = args.pick_argument(flag)?; - picked.parse().ok() - } - } - - impl Pickable for Vec<$t> { - type Output = Vec<$t>; - - fn pick(args: &mut crate::parser::Argument, flag: mingling_core::Flag) -> Option<Self::Output> { - let picked_vec = args.pick_arguments(flag); - let mut result = Vec::new(); - for picked in picked_vec { - if let Ok(parsed) = picked.parse() { - result.push(parsed); - } else { - return None; - } - } - Some(result) - } - } - )* - }; -} - -impl_pickable_for_number!(i8, i16, i32, i64, i128, u8, u16, u32, u64, u128, f32, f64); - -impl Pickable for bool { - type Output = Self; - - fn pick(args: &mut crate::parser::Argument, flag: mingling_core::Flag) -> Option<Self::Output> { - Some(args.pick_flag(flag)) - } -} - -/// Special: parses a size string (e.g. "10MB") into a `usize` representing the number of bytes. -impl Pickable for usize { - type Output = Self; - - fn pick(args: &mut crate::parser::Argument, flag: mingling_core::Flag) -> Option<Self::Output> { - let picked = args.pick_argument(flag)?; - let size_parse = Size::from_str(picked.as_str()); - size_parse.map_or(None, |size| Self::try_from(size.bytes()).ok()) - } -} - -/// Special: parses a comma-separated list of size strings (e.g. "10MB,20KB") into a `Vec<usize>`. -impl Pickable for Vec<usize> { - type Output = Self; - - fn pick(args: &mut crate::parser::Argument, flag: mingling_core::Flag) -> Option<Self::Output> { - let picked_vec = args.pick_arguments(flag); - let mut result = Self::new(); - for picked in picked_vec { - let size_parse = Size::from_str(picked.as_str()); - match size_parse { - Ok(size) => result.push(usize::try_from(size.bytes()).unwrap_or(usize::MAX)), - Err(_) => return None, - } - } - Some(result) - } -} - -/// Special: dumps the remaining arguments into an `Argument` struct. -impl Pickable for Argument { - type Output = Self; - - fn pick( - args: &mut crate::parser::Argument, - _flag: mingling_core::Flag, - ) -> Option<Self::Output> { - Some(args.dump_remains().into()) - } -} - -/// Special: parses a single value of type `T` using the `Pickable` implementation for `T`, and wraps it in an `Option`. -impl<T: Pickable<Output = T> + Default> Pickable for Option<T> { - type Output = Self; - - fn pick(args: &mut Argument, flag: mingling_core::Flag) -> Option<Self::Output> { - let r = T::pick(args, flag); - Some(r) - } -} diff --git a/mingling/src/parser/picker/path.rs b/mingling/src/parser/picker/path.rs deleted file mode 100644 index 1caecfa..0000000 --- a/mingling/src/parser/picker/path.rs +++ /dev/null @@ -1,145 +0,0 @@ -// Doc Not Optimize -use std::path::{Path, PathBuf}; - -use crate::parser::Pickable; - -mod rule; -pub use rule::*; - -impl Pickable for Vec<PathBuf> { - type Output = Self; - - fn pick(args: &mut crate::parser::Argument, flag: mingling_core::Flag) -> Option<Self::Output> { - let raw: Vec<String> = args.pick_arguments(flag); - let paths = raw.into_iter().map(PathBuf::from).collect(); - Some(paths) - } -} - -impl Pickable for PathBuf { - type Output = Self; - - fn pick(args: &mut crate::parser::Argument, flag: mingling_core::Flag) -> Option<Self::Output> { - let raw: String = args.pick_argument(flag)?; - Some(Self::from(raw)) - } -} - -/// Provides path checking methods for [`Vec<PathBuf>`] -/// -/// This trait automatically provides implementations for `Into<Vec<PathBuf>>` -pub trait PathsChecker { - /// Check if all paths in the list satisfy the rule - fn is_all_passed(&self, rule: &PathCheckRule) -> bool - where - Self: Into<Vec<PathBuf>> + Clone, - { - check_paths(self.clone(), rule).is_ok() - } - - /// Classify paths into (Passed, Stripped) - /// - /// Passed means paths that satisfy the rule, Stripped means paths that do not. - fn classify(self, rule: &PathCheckRule) -> (Vec<PathBuf>, Vec<PathBuf>) - where - Self: Into<Vec<PathBuf>>, - { - let paths = self.into(); - let mut passed = Vec::new(); - let mut stripped = Vec::new(); - for path in paths { - if check_path(&path, rule).is_ok() { - passed.push(path); - } else { - stripped.push(path); - } - } - (passed, stripped) - } - - /// Return paths that satisfy the rule - fn passed(self, rule: &PathCheckRule) -> Vec<PathBuf> - where - Self: Into<Vec<PathBuf>>, - { - self.classify(rule).0 - } - - /// Return paths that do not satisfy the rule - fn stripped(self, rule: &PathCheckRule) -> Vec<PathBuf> - where - Self: Into<Vec<PathBuf>>, - { - self.classify(rule).1 - } -} - -/// Provides path checking methods for [`PathBuf`] -/// -/// This trait automatically provides implementations for `Into<PathBuf>` -pub trait PathChecker { - fn is_passed(&self, rule: &PathCheckRule) -> bool - where - Self: Into<PathBuf> + Clone, - { - check_path(self.clone(), rule).is_ok() - } -} - -impl<T: Into<Vec<PathBuf>>> PathsChecker for T {} -impl<T: Into<PathBuf>> PathChecker for T {} - -fn check_paths(path: impl Into<Vec<PathBuf>>, rule: &PathCheckRule) -> Result<(), ()> { - let paths = path.into(); - for p in &paths { - check_exist(p, rule)?; - check_type(p, rule)?; - } - - Ok(()) -} - -fn check_path(path: impl Into<PathBuf>, rule: &PathCheckRule) -> Result<(), ()> { - let p = path.into(); - check_exist(&p, rule)?; - check_type(&p, rule)?; - - Ok(()) -} - -fn check_exist(path: &Path, rule: &PathCheckRule) -> Result<(), ()> { - let Some(exist_check) = &rule.exist_check else { - return Ok(()); - }; - - match exist_check { - PathExistCheck::Exists => bool_to_result(path.exists()), - PathExistCheck::NotExists => bool_to_result(!path.exists()), - } -} - -fn check_type(path: &Path, rule: &PathCheckRule) -> Result<(), ()> { - let Some(type_check) = &rule.type_check else { - return Ok(()); - }; - - let is_dir = path.is_dir(); - let is_file = path.is_file(); - let is_symlink = path.is_symlink(); - - if type_check.allow_dir && is_dir { - return Ok(()); - } - if type_check.allow_file && is_file { - return Ok(()); - } - if type_check.allow_symlink && is_symlink { - return Ok(()); - } - - Err(()) -} - -const fn bool_to_result(b: bool) -> Result<(), ()> { - if b { Ok(()) } else { Err(()) } -} diff --git a/mingling/src/parser/picker/path/rule.rs b/mingling/src/parser/picker/path/rule.rs deleted file mode 100644 index 5256f35..0000000 --- a/mingling/src/parser/picker/path/rule.rs +++ /dev/null @@ -1,231 +0,0 @@ -// Doc Not Optimize -/// Path check rule -#[derive(Default)] -pub struct PathCheckRule { - pub exist_check: Option<PathExistCheck>, - pub type_check: Option<PathTypeCheck>, -} - -/// Path existence check -pub enum PathExistCheck { - Exists, - NotExists, -} - -/// Path type check -pub struct PathTypeCheck { - /// Whether the path is allowed to be a file - pub allow_file: bool, - - /// Whether the path is allowed to be a directory - pub allow_dir: bool, - - /// Whether the path is allowed to be a symlink - pub allow_symlink: bool, -} - -impl PathCheckRule { - /// Creates a new `PathCheckRule` with default values - #[must_use] - pub const fn new() -> Self { - Self { - exist_check: None, - type_check: None, - } - } - - /// Allows the path to be a file - #[must_use] - pub const fn allow_file(self) -> Self { - match self.type_check { - Some(type_check) => Self { - type_check: Some(PathTypeCheck { - allow_file: true, - allow_dir: type_check.allow_dir, - allow_symlink: type_check.allow_symlink, - }), - ..self - }, - None => Self { - type_check: Some(PathTypeCheck { - allow_file: true, - allow_dir: false, - allow_symlink: false, - }), - ..self - }, - } - } - - /// Allows the path to be a directory - #[must_use] - pub const fn allow_dir(self) -> Self { - match self.type_check { - Some(type_check) => Self { - type_check: Some(PathTypeCheck { - allow_file: type_check.allow_file, - allow_dir: true, - allow_symlink: type_check.allow_symlink, - }), - ..self - }, - None => Self { - type_check: Some(PathTypeCheck { - allow_file: false, - allow_dir: true, - allow_symlink: false, - }), - ..self - }, - } - } - - /// Allows the path to be a symlink - #[must_use] - pub const fn allow_symlink(self) -> Self { - match self.type_check { - Some(type_check) => Self { - type_check: Some(PathTypeCheck { - allow_file: type_check.allow_file, - allow_dir: type_check.allow_dir, - allow_symlink: true, - }), - ..self - }, - None => Self { - type_check: Some(PathTypeCheck { - allow_file: false, - allow_dir: false, - allow_symlink: true, - }), - ..self - }, - } - } - - /// Denies the path from being a file - #[must_use] - pub const fn deny_file(self) -> Self { - match self.type_check { - Some(type_check) => Self { - type_check: Some(PathTypeCheck { - allow_file: false, - allow_dir: type_check.allow_dir, - allow_symlink: type_check.allow_symlink, - }), - ..self - }, - None => Self { - type_check: Some(PathTypeCheck { - allow_file: false, - allow_dir: true, - allow_symlink: true, - }), - ..self - }, - } - } - - /// Denies the path from being a directory - #[must_use] - pub const fn deny_dir(self) -> Self { - match self.type_check { - Some(type_check) => Self { - type_check: Some(PathTypeCheck { - allow_file: type_check.allow_file, - allow_dir: false, - allow_symlink: type_check.allow_symlink, - }), - ..self - }, - None => Self { - type_check: Some(PathTypeCheck { - allow_file: true, - allow_dir: false, - allow_symlink: true, - }), - ..self - }, - } - } - - /// Denies the path from being a symlink - #[must_use] - pub const fn deny_symlink(self) -> Self { - match self.type_check { - Some(type_check) => Self { - type_check: Some(PathTypeCheck { - allow_file: type_check.allow_file, - allow_dir: type_check.allow_dir, - allow_symlink: false, - }), - ..self - }, - None => Self { - type_check: Some(PathTypeCheck { - allow_file: true, - allow_dir: true, - allow_symlink: false, - }), - ..self - }, - } - } - - /// Requires the path to be a file (overrides type checks) - #[must_use] - pub const fn must_file(self) -> Self { - Self { - type_check: Some(PathTypeCheck { - allow_file: true, - allow_dir: false, - allow_symlink: false, - }), - ..self - } - } - - /// Requires the path to be a directory (overrides type checks) - #[must_use] - pub const fn must_dir(self) -> Self { - Self { - type_check: Some(PathTypeCheck { - allow_file: false, - allow_dir: true, - allow_symlink: false, - }), - ..self - } - } - - /// Requires the path to be a symlink (overrides type checks) - #[must_use] - pub const fn must_symlink(self) -> Self { - Self { - type_check: Some(PathTypeCheck { - allow_file: false, - allow_dir: false, - allow_symlink: true, - }), - ..self - } - } - - /// Requires the path to exist - #[must_use] - pub const fn must_exist(self) -> Self { - Self { - exist_check: Some(PathExistCheck::Exists), - ..self - } - } - - /// Requires the path to not exist - #[must_use] - pub const fn must_not_exist(self) -> Self { - Self { - exist_check: Some(PathExistCheck::NotExists), - ..self - } - } -} diff --git a/mingling/src/parser/test.rs b/mingling/src/parser/test.rs deleted file mode 100644 index 172d1da..0000000 --- a/mingling/src/parser/test.rs +++ /dev/null @@ -1,731 +0,0 @@ -// Doc Not Optimize -use crate::parser::picker::bools::{True, Yes}; -use crate::parser::{Argument, Pick1, Picker}; - -#[test] -fn test_argument_from_static_str() { - let arg: Argument = "hello".into(); - assert_eq!(arg.len(), 1); - assert_eq!(arg[0], "hello"); -} - -#[test] -fn test_argument_from_slice() { - let arg: Argument = (&["--name", "value"][..]).into(); - assert_eq!(arg.len(), 2); - assert_eq!(arg[0], "--name"); - assert_eq!(arg[1], "value"); -} - -#[test] -fn test_argument_from_array() { - let arg: Argument = ["--file", "test.txt"].into(); - assert_eq!(arg.len(), 2); -} - -#[test] -fn test_argument_from_vec() { - let arg: Argument = vec!["a".to_string(), "b".to_string()].into(); - assert_eq!(arg.len(), 2); -} - -#[test] -fn test_argument_default_is_empty() { - let arg = Argument::default(); - assert!(arg.is_empty()); -} - -#[test] -fn test_pick_argument_with_flag() { - let mut arg: Argument = vec!["--name", "Alice", "--verbose"].into(); - let value = arg.pick_argument("--name"); - assert_eq!(value, Some("Alice".to_string())); - // After picking, the flag and its value are removed - assert_eq!(arg.as_ref(), &["--verbose"]); -} - -#[test] -fn test_pick_argument_flag_not_found() { - let mut arg: Argument = vec!["--name", "Alice"].into(); - let value = arg.pick_argument("--missing"); - assert_eq!(value, None); - // Original args unchanged - assert_eq!(arg.as_ref(), &["--name", "Alice"]); -} - -#[test] -fn test_pick_argument_empty() { - let mut arg: Argument = Argument::default(); - let value = arg.pick_argument("--flag"); - assert_eq!(value, None); -} - -#[test] -fn test_pick_argument_flag_at_end_no_value() { - let mut arg: Argument = vec!["--name"].into(); - let value = arg.pick_argument("--name"); - assert_eq!(value, None); - assert!(arg.is_empty()); -} - -#[test] -fn test_pick_argument_no_flag_positional() { - let mut arg: Argument = vec!["first", "second", "--flag", "val"].into(); - let value = arg.pick_argument(()); - assert_eq!(value, Some("first".to_string())); - assert_eq!(arg.as_ref(), &["second", "--flag", "val"]); -} - -#[test] -fn test_pick_argument_positional_all() { - let mut arg: Argument = vec!["one", "two", "three"].into(); - let v1 = arg.pick_argument(()); - let v2 = arg.pick_argument(()); - let v3 = arg.pick_argument(()); - let v4 = arg.pick_argument(()); - assert_eq!(v1, Some("one".to_string())); - assert_eq!(v2, Some("two".to_string())); - assert_eq!(v3, Some("three".to_string())); - assert_eq!(v4, None); -} - -#[test] -fn test_pick_argument_empty_args_no_flag() { - let mut arg: Argument = Argument::default(); - let value = arg.pick_argument(()); - assert_eq!(value, None); -} - -#[test] -fn test_pick_argument_with_flag_from_iter() { - let mut arg: Argument = vec!["-f", "data.txt", "--other"].into(); - let value = arg.pick_argument(&["-f", "--file"][..]); - assert_eq!(value, Some("data.txt".to_string())); - assert_eq!(arg.as_ref(), &["--other"]); -} - -#[test] -fn test_pick_arguments_multiple_values() { - let mut arg: Argument = vec!["--files", "a.txt", "b.txt", "c.txt", "--other"].into(); - let values = arg.pick_arguments("--files"); - assert_eq!(values, vec!["a.txt", "b.txt", "c.txt"]); - assert_eq!(arg.as_ref(), &["--other"]); -} - -#[test] -fn test_pick_arguments_single_value() { - let mut arg: Argument = vec!["--name", "Alice", "--verbose"].into(); - let values = arg.pick_arguments("--name"); - assert_eq!(values, vec!["Alice"]); - assert_eq!(arg.as_ref(), &["--verbose"]); -} - -#[test] -fn test_pick_arguments_no_values() { - let mut arg: Argument = vec!["--flag", "--other", "val"].into(); - let values = arg.pick_arguments("--flag"); - assert!(values.is_empty()); - assert_eq!(arg.as_ref(), &["--other", "val"]); -} - -#[test] -fn test_pick_arguments_flag_not_found() { - let mut arg: Argument = vec!["--name", "Alice"].into(); - let values = arg.pick_arguments("--missing"); - assert!(values.is_empty()); - assert_eq!(arg.as_ref(), &["--name", "Alice"]); -} - -#[test] -fn test_pick_arguments_stops_at_next_flag() { - let mut arg: Argument = vec!["--list", "a", "b", "-c", "d", "e"].into(); - let values = arg.pick_arguments("--list"); - assert_eq!(values, vec!["a", "b"]); - assert_eq!(arg.as_ref(), &["-c", "d", "e"]); -} - -#[test] -fn test_pick_arguments_empty_flag_positional() { - let mut arg: Argument = vec!["pos1", "pos2", "--flag", "val"].into(); - let values = arg.pick_arguments(()); - assert_eq!(values, vec!["pos1", "pos2"]); - assert_eq!(arg.as_ref(), &["--flag", "val"]); -} - -#[test] -fn test_pick_arguments_empty_args() { - let mut arg: Argument = Argument::default(); - let values = arg.pick_arguments("--flag"); - assert!(values.is_empty()); -} - -#[test] -fn test_pick_flag_found() { - let mut arg: Argument = vec!["--verbose", "--name", "Alice"].into(); - let result = arg.pick_flag("--verbose"); - assert!(result); - assert_eq!(arg.as_ref(), &["--name", "Alice"]); -} - -#[test] -fn test_pick_flag_not_found() { - let mut arg: Argument = vec!["--name", "Alice"].into(); - let result = arg.pick_flag("--verbose"); - assert!(!result); - assert_eq!(arg.as_ref(), &["--name", "Alice"]); -} - -#[test] -fn test_pick_flag_empty_args() { - let mut arg: Argument = Argument::default(); - let result = arg.pick_flag("--flag"); - assert!(!result); -} - -#[test] -fn test_pick_flag_with_flag_iter() { - let mut arg: Argument = vec!["-h", "--name", "Alice"].into(); - let result = arg.pick_flag(&["-h", "--help"][..]); - assert!(result); -} - -#[test] -fn test_pick_flag_second_not_first() { - let mut arg: Argument = vec!["--name", "Alice"].into(); - let result = arg.pick_flag(&["-h", "--help"][..]); - assert!(!result); -} - -#[test] -fn test_pick_flag_positional_yes() { - let mut arg: Argument = vec!["yes"].into(); - let result = arg.pick_flag(()); - assert!(result); - assert!(arg.is_empty()); -} - -#[test] -fn test_pick_flag_positional_no() { - let mut arg: Argument = vec!["no"].into(); - let result = arg.pick_flag(()); - assert!(!result); -} - -#[test] -fn test_pick_flag_positional_true() { - let mut arg: Argument = vec!["true"].into(); - let result = arg.pick_flag(()); - assert!(result); -} - -#[test] -fn test_pick_flag_positional_false() { - let mut arg: Argument = vec!["false"].into(); - let result = arg.pick_flag(()); - assert!(!result); -} - -#[test] -fn test_pick_flag_positional_1() { - let mut arg: Argument = vec!["1"].into(); - let result = arg.pick_flag(()); - assert!(result); -} - -#[test] -fn test_pick_flag_positional_0() { - let mut arg: Argument = vec!["0"].into(); - let result = arg.pick_flag(()); - assert!(!result); -} - -#[test] -fn test_pick_flag_positional_unknown() { - let mut arg: Argument = vec!["unknown_value"].into(); - let result = arg.pick_flag(()); - assert!(!result); -} - -#[test] -fn test_pick_flag_positional_case_insensitive_yes() { - let mut arg: Argument = vec!["YeS"].into(); - let result = arg.pick_flag(()); - assert!(result); -} - -#[test] -fn test_dump_remains() { - let mut arg: Argument = vec!["a", "b", "c"].into(); - let remains = arg.dump_remains(); - assert_eq!(remains, vec!["a", "b", "c"]); - assert!(arg.is_empty()); -} - -#[test] -fn test_dump_remains_empty() { - let mut arg: Argument = Argument::default(); - let remains = arg.dump_remains(); - assert!(remains.is_empty()); -} - -#[test] -fn test_dump_remains_after_pick() { - let mut arg: Argument = vec!["--flag", "value", "extra"].into(); - let _ = arg.pick_argument("--flag"); - let remains = arg.dump_remains(); - assert_eq!(remains, vec!["extra"]); -} - -#[test] -fn test_strip_all_flags() { - let arg: Argument = vec!["--verbose", "file.txt", "--format", "json"].into(); - let result = arg.strip_all_flags(); - assert_eq!(result.as_ref(), &["file.txt", "json"]); -} - -#[test] -fn test_strip_all_flags_no_flags() { - let arg: Argument = vec!["just", "positional", "args"].into(); - let result = arg.strip_all_flags(); - assert_eq!(result.as_ref(), &["just", "positional", "args"]); -} - -#[test] -fn test_strip_all_flags_all_flags() { - let arg: Argument = vec!["--a", "-b", "--c"].into(); - let result = arg.strip_all_flags(); - assert!(result.is_empty()); -} - -#[test] -fn test_strip_all_flags_empty() { - let arg: Argument = Argument::default(); - let result = arg.strip_all_flags(); - assert!(result.is_empty()); -} - -#[test] -fn test_picker_new() { - let picker = Picker::new(vec!["--name", "Alice"]); - assert_eq!(picker.args.len(), 2); -} - -#[test] -fn test_picker_from_trait() { - let picker: Picker = vec!["--name", "Alice"].into(); - assert_eq!(picker.args.len(), 2); -} - -#[test] -fn test_picker_pick_string() { - let result: String = Picker::new(vec!["--name", "Alice"]).pick("--name").unpack(); - assert_eq!(result, "Alice"); -} - -#[test] -fn test_picker_pick_string_default_when_missing() { - let result: String = Picker::new(vec!["--other", "val"]) - .pick::<String>("--name") - .unpack(); - assert_eq!(result, ""); -} - -#[test] -fn test_picker_pick_string_default_when_missing_with_or() { - let result: String = Picker::new(vec!["--other", "val"]) - .pick_or("--name", "default_name") - .unpack(); - assert_eq!(result, "default_name"); -} - -#[test] -fn test_picker_pick_bool_flag_present() { - let result: bool = Picker::new(vec!["--verbose", "--name", "Alice"]) - .pick::<bool>("--verbose") - .unpack(); - assert!(result); -} - -#[test] -fn test_picker_pick_bool_flag_absent() { - let result: bool = Picker::new(vec!["--name", "Alice"]) - .pick::<bool>("--verbose") - .unpack(); - assert!(!result); -} - -#[test] -fn test_picker_pick_i32() { - let result: i32 = Picker::new(vec!["--count", "42"]).pick("--count").unpack(); - assert_eq!(result, 42); -} - -#[test] -fn test_picker_pick_i32_default_zero() { - let result: i32 = Picker::new(vec!["--other"]).pick::<i32>("--count").unpack(); - assert_eq!(result, 0); -} - -#[test] -fn test_picker_pick_f64() { - let result: f64 = Picker::new(vec!["--ratio", "5.16"]) - .pick("--ratio") - .unpack(); - let expected: f64 = 5.16; - assert!((result - expected).abs() < 1e-10); -} - -#[test] -fn test_picker_pick_u64() { - let result: u64 = Picker::new(vec!["--size", "100"]).pick("--size").unpack(); - assert_eq!(result, 100); -} - -#[test] -fn test_picker_pick_i32_parse_failure_returns_default() { - let result: i32 = Picker::new(vec!["--count", "not-a-number"]) - .pick::<i32>("--count") - .unpack(); - assert_eq!(result, 0); -} - -#[test] -fn test_picker_pick_usize_bytes() { - let result: usize = Picker::new(vec!["--limit", "1024"]) - .pick("--limit") - .unpack(); - assert_eq!(result, 1024); -} - -#[test] -fn test_picker_pick_usize_kib() { - let result: usize = Picker::new(vec!["--limit", "1KiB"]) - .pick("--limit") - .unpack(); - assert_eq!(result, 1024); -} - -#[test] -fn test_picker_pick_usize_mib() { - let result: usize = Picker::new(vec!["--limit", "2MiB"]) - .pick("--limit") - .unpack(); - assert_eq!(result, 2 * 1024 * 1024); -} - -#[test] -fn test_picker_pick_usize_parse_failure_returns_default() { - let result: usize = Picker::new(vec!["--limit", "invalid"]) - .pick::<usize>("--limit") - .unpack(); - assert_eq!(result, 0); -} - -#[test] -fn test_picker_pick_vec_string() { - let result: Vec<String> = Picker::new(vec!["--files", "a.txt", "b.txt", "c.txt"]) - .pick("--files") - .unpack(); - assert_eq!(result, vec!["a.txt", "b.txt", "c.txt"]); -} - -#[test] -fn test_picker_pick_vec_string_missing() { - let result: Vec<String> = Picker::new(vec!["--other", "val"]) - .pick::<Vec<String>>("--files") - .unpack(); - assert!(result.is_empty()); -} - -#[test] -fn test_picker_pick_vec_usize() { - let result: Vec<usize> = Picker::new(vec!["--sizes", "100", "1KiB", "2MiB"]) - .pick("--sizes") - .unpack(); - assert_eq!(result, vec![100, 1024, 2 * 1024 * 1024]); -} - -#[test] -fn test_picker_pick_vec_i32() { - let result: Vec<i32> = Picker::new(vec!["--nums", "10", "20", "30"]) - .pick("--nums") - .unpack(); - assert_eq!(result, vec![10, 20, 30]); -} - -#[test] -fn test_picker_pick_yes_yes() { - let result: Yes = Picker::new(vec!["--flag", "y"]).pick("--flag").unpack(); - assert!(result.is_yes()); - assert!(*result); -} - -#[test] -fn test_picker_pick_yes_no() { - let result: Yes = Picker::new(vec!["--flag", "no"]).pick("--flag").unpack(); - assert!(result.is_no()); - assert!(!*result); -} - -#[test] -fn test_picker_pick_yes_default_no() { - let result: Yes = Picker::new(vec!["--other"]).pick::<Yes>("--flag").unpack(); - assert!(result.is_no()); -} - -#[test] -fn test_picker_pick_true_true() { - let result: True = Picker::new(vec!["--flag", "true"]).pick("--flag").unpack(); - assert!(result.is_true()); - assert!(*result); -} - -#[test] -fn test_picker_pick_true_false() { - let result: True = Picker::new(vec!["--flag", "anything"]) - .pick("--flag") - .unpack(); - assert!(result.is_false()); - assert!(!*result); -} - -#[test] -fn test_picker_pick_true_default_false() { - let result: True = Picker::new(vec!["--other"]).pick::<True>("--flag").unpack(); - assert!(result.is_false()); -} - -#[test] -fn test_picker_pick_or_fallback() { - let result: String = Picker::new(vec!["--other", "val"]) - .pick_or("--name", "fallback") - .unpack(); - assert_eq!(result, "fallback"); -} - -#[test] -fn test_picker_pick_or_existing() { - let result: String = Picker::new(vec!["--name", "Alice"]) - .pick_or("--name", "fallback") - .unpack(); - assert_eq!(result, "Alice"); -} - -#[test] -fn test_picker_pick_or_numeric_fallback() { - let result: i32 = Picker::new(vec!["--other"]).pick_or("--count", 99).unpack(); - assert_eq!(result, 99); -} - -#[test] -fn test_picker_pick_or_route_present() { - let result = Picker::new(vec!["--name", "Alice"]) - .pick_or_route::<String, _>("--name", "missing_name") - .unpack(); - assert_eq!(result, Ok("Alice".to_string())); -} - -#[test] -fn test_picker_pick_or_route_missing() { - let result = Picker::new(vec!["--other"]) - .pick_or_route::<String, _>("--name", "missing_name") - .unpack(); - assert_eq!(result, Err("missing_name")); -} - -#[test] -fn test_picker_require_present() { - let result: Option<String> = Picker::new(vec!["--name", "Alice"]) - .require::<String>("--name") - .map(super::picker::Pick1::unpack); - assert_eq!(result, Some("Alice".to_string())); -} - -#[test] -fn test_picker_require_missing() { - let result: Option<Pick1<String>> = Picker::new(vec!["--other"]).require::<String>("--name"); - assert!(result.is_none()); -} - -#[test] -fn test_picker_chaining_two_values() { - let (name, count): (String, i32) = Picker::new(vec!["--name", "Alice", "--count", "42"]) - .pick::<String>("--name") - .pick::<i32>("--count") - .unpack(); - assert_eq!(name, "Alice"); - assert_eq!(count, 42); -} - -#[test] -fn test_picker_chaining_three_values() { - let (_name, _verbose, count): (String, bool, i32) = - Picker::new(vec!["--name", "Alice", "--count", "42", "--verbose"]) - .pick::<String>("--name") - .pick::<bool>("--verbose") - .pick::<i32>("--count") - .unpack(); - assert_eq!(count, 42); -} - -#[test] -fn test_picker_chaining_with_pick_or() { - let (name, count): (String, i32) = Picker::new(vec!["--name", "Alice"]) - .pick::<String>("--name") - .pick_or("--count", 10) - .unpack(); - assert_eq!(name, "Alice"); - assert_eq!(count, 10); -} - -#[test] -fn test_picker_chaining_with_mixed_flag_styles() { - let (name, verbose): (String, bool) = Picker::new(vec!["-n", "Bob", "--verbose"]) - .pick::<String>("-n") - .pick::<bool>("--verbose") - .unpack(); - assert_eq!(name, "Bob"); - assert!(verbose); -} - -#[test] -fn test_pick_after_modification() { - let result: String = Picker::new(vec!["--name", " Alice "]) - .pick::<String>("--name") - .after(|s| s.trim().to_string()) - .unpack(); - assert_eq!(result, "Alice"); -} - -#[test] -fn test_pick_after_chained() { - let (name, count): (String, i32) = Picker::new(vec!["--name", "alice", "--count", "7"]) - .pick::<String>("--name") - .after(|s| s.to_uppercase()) - .pick::<i32>("--count") - .after(|n| n * 2) - .unpack(); - assert_eq!(name, "ALICE"); - assert_eq!(count, 14); -} - -#[test] -fn test_pick_after_or_route_ok() { - let result = Picker::new(vec!["--name", "Alice"]) - .pick::<String>("--name") - .after_or_route(|s| { - if s.len() > 3 { - Ok(s.clone()) - } else { - Err("too_short") - } - }) - .unpack(); - assert_eq!(result, Ok("Alice".to_string())); -} - -#[test] -fn test_pick_after_or_route_err() { - let result = Picker::new(vec!["--name", "Ab"]) - .pick::<String>("--name") - .after_or_route(|s| { - if s.len() > 3 { - Ok(s.clone()) - } else { - Err("too_short") - } - }) - .unpack(); - assert_eq!(result, Err("too_short")); -} - -#[test] -fn test_pick_with_route_unpack_ok() { - let result = Picker::new(vec!["--name", "Alice"]) - .pick_or_route::<String, _>("--name", "error") - .unpack(); - assert_eq!(result, Ok("Alice".to_string())); -} - -#[test] -fn test_pick_with_route_unpack_err() { - let result: Result<String, &str> = Picker::new(vec!["--other"]) - .pick_or_route::<String, _>("--name", "missing") - .unpack(); - assert_eq!(result, Err("missing")); -} - -#[test] -fn test_pick_with_route_unpack_directly() { - let result: String = Picker::new(vec!["--other"]) - .pick_or_route::<String, _>("--name", "fallback_in_route") - .unpack_directly(); - // When route is set, unpack_directly returns the default value (empty string for String) - assert_eq!(result, ""); -} - -#[test] -fn test_pick_with_route_chaining_present() { - let result = Picker::new(vec!["--name", "Alice", "--count", "42"]) - .pick_or_route::<String, _>("--name", "err_name") - .pick::<i32>("--count") - .unpack(); - assert_eq!(result, Ok(("Alice".to_string(), 42))); -} - -#[test] -fn test_pick_with_route_chaining_missing_first_route_propagates() { - let result = Picker::new(vec!["--count", "42"]) - .pick_or_route::<String, _>("--name", "err_name") - .pick::<i32>("--count") - .unpack(); - assert_eq!(result, Err("err_name")); -} - -#[test] -fn test_pick_with_route_chaining_pick_or_route_second_missing() { - let result = Picker::new(vec!["--name", "Alice"]) - .pick_or_route::<String, _>("--name", "err_name") - .pick_or_route::<i32>("--count", "err_count") - .unpack(); - assert_eq!(result, Err("err_count")); -} - -#[test] -fn test_pick_with_route_after_or_route_preserves_existing_route() { - let result = Picker::new(vec!["--other"]) - .pick_or_route::<String, _>("--name", "missing_name") - .after_or_route(|_s: &String| { - // This won't be called because route is already set, but let's see behavior - Ok("should_not_matter".to_string()) - }) - .unpack(); - assert_eq!(result, Err("missing_name")); -} - -#[test] -fn test_picker_operate_args_filter() { - let result: String = Picker::new(vec!["--name", "Alice", "--verbose"]) - .operate_args(Argument::strip_all_flags) - .pick_or("--name", "fallback_name") - .unpack(); - // After stripping flags, "--name" and "--verbose" are gone, "Alice" is a positional arg. - // But --name with a value won't be present as a flag, so it falls back to positional. - // Actually, strip_all_flags removes anything starting with '-'. - // So "--name" is removed, and "Alice" remains as a positional argument. - // When we try to pick "--name", it won't find it, so we get the fallback. - assert_eq!(result, "fallback_name"); -} - -#[test] -fn test_picker_operate_args_transform() { - let result: Vec<String> = Picker::new(vec!["--files", "a.txt", "b.txt", "c.txt"]) - .operate_args(|mut args| { - // Add an extra file - args.push("d.txt".to_string()); - args - }) - .pick::<Vec<String>>("--files") - .unpack(); - assert_eq!(result, vec!["a.txt", "b.txt", "c.txt", "d.txt"]); -} |
