diff options
Diffstat (limited to 'arg_picker/src/picker')
| -rw-r--r-- | arg_picker/src/picker/parse.rs | 177 | ||||
| -rw-r--r-- | arg_picker/src/picker/patterns.rs | 205 | ||||
| -rw-r--r-- | arg_picker/src/picker/result.rs | 56 |
3 files changed, 438 insertions, 0 deletions
diff --git a/arg_picker/src/picker/parse.rs b/arg_picker/src/picker/parse.rs new file mode 100644 index 0000000..366028b --- /dev/null +++ b/arg_picker/src/picker/parse.rs @@ -0,0 +1,177 @@ +// -------------------------------------------------------------------------------------------- +// I have to say, the code generated by this `internal_repeat!` macro is really UGLY. +// +// But I have to admit, this is a **trade-off**. To achieve the syntax of `pick().pick().pick()` +// while ensuring type safety, this is the best approach I could think of. +// +// P.S. If there's a better way, please let me know. Thanks! +// -------------------------------------------------------------------------------------------- +// +// Then, I must disable `clippy::type_complexity` — this guy is way too noisy. +#![allow(clippy::type_complexity)] + +use crate::{Pickable, PickerArgAttr, PickerArgInfo, PickerArgResult, PickerArgs, TagPhaseContext}; +use arg_picker_macros::internal_repeat; + +internal_repeat!(1..=32 => { + use crate::PickerPattern$; + use crate::PickerResult$; +}); + +internal_repeat!(1..=32 => { + impl<'a, (T$,+), Route> PickerPattern$<'a, (T$,+), Route> + where (T$: Pickable<'a>,+) { + /// Unwraps the result, panicking if a route was selected. + /// + /// # Panics + /// + /// Panics if a route was selected. + pub fn unwrap(self) -> ((T$,+)) { + let p = self.parse(); + ((p.v$.unwrap(),+)) + } + + /// Returns the individual option values without checking the route. + pub fn unpack(self) -> ((Option<T$>,+)) { + let p = self.parse(); + ((p.v$,+)) + } + + /// Converts to a `Result`, returning `Err(route)` if a route was selected, + /// or `Ok(values)` otherwise. + pub fn to_result(self) -> Result<((T$,+)), Route> { + let p = self.parse(); + if let Some(r) = p.route { + return Err(r); + } + Ok(p.unwrap()) + } + + /// Converts to an `Option`, returning `None` if a route was selected, + /// or `Some(values)` otherwise. + pub fn to_option(self) -> Option<((T$,+))> { + let p = self.parse(); + if p.route.is_some() { + return None; + } + Some(p.unwrap()) + } + } +}); + +internal_repeat!(1..=32 => { + impl<'a, (T$,+), Route> PickerPattern$<'a, (T$,+), Route> + where (T$: Pickable<'a>,+) + { + pub fn parse(mut self) -> PickerResult$<(T$,+), Route> { + // ArgInfos + let arg_infos: [PickerArgInfo; $] = [ + ( + PickerArgInfo::from(self.arg_$), + +) + ]; + + let mut bundle: [ + ( + // Arg Attr + PickerArgAttr, + + // Tag Func + Box<dyn FnOnce(&PickerArgs<'a>, &[u8]) -> Vec<usize>>, + + // Pick Func + Box<dyn FnOnce(&[&str], &mut Option<Route>)>, + + // Index + usize + ) + ; $] = [ + ( + ( + // Arg Attr + T$::get_attr(self.arg_$), + + // Tag Func + Box::new(|args, mask| { + let ctx = TagPhaseContext { + arg_info: &arg_infos[$-], + args, + mask + }; + T$::tag(ctx) + }), + + // Pick Func + Box::new(|args, error_route| { + self.result_$ = match T$::pick(args) { + PickerArgResult::Parsed(mut value) => { + // Postprocess + if let Some(post) = self.post_$ { + value = post(value); + } + PickerArgResult::Parsed(value) + }, + other => { + if let Some(get_default) = self.default_$ { + let mut value = get_default(); + + // Postprocess + if let Some(post) = self.post_$ { + value = post(value); + } + + PickerArgResult::Parsed(value) + } else { + if error_route.is_none() { + if let Some(get_route) = self.route_$ { + *error_route = Some(get_route().into()); + } + } + other + } + }, + + } + }), + + // Index + $ + ), + +) + ]; + + // Sort by Bundle Ord (descending) + bundle.sort_by(|a, b| b.0.cmp(&a.0)); + + // Mask — size = number of args (not args), so use args length + let mut mask: Vec<u8> = vec![0u8; self.args.len()]; + + // Parsing + for (_, tag_func, pick_func, _idx) in bundle { + + // Tag phase + let tagged = tag_func(&self.args, mask.as_slice()); + let mut args_to_pick: Vec<&str> = vec![]; + + for i in tagged { + mask[i] = 1; + + // Update args to pick + args_to_pick.push(self.args.get(i).unwrap_or_default()); + } + + // Pick phase + pick_func(args_to_pick.as_slice(), &mut self.error_route); + } + + // Combine Result + let result: PickerResult$<(T$,+), Route> = PickerResult$ { + route: self.error_route, + ( + v$: self.result_$.to_option(), + +) + }; + result + } + } +}); diff --git a/arg_picker/src/picker/patterns.rs b/arg_picker/src/picker/patterns.rs new file mode 100644 index 0000000..5806605 --- /dev/null +++ b/arg_picker/src/picker/patterns.rs @@ -0,0 +1,205 @@ +use arg_picker_macros::internal_repeat; + +use crate::{Pickable, PickerArg, PickerArgResult, PickerArgs}; + +internal_repeat!(1..=32 => { + #[doc(hidden)] + pub struct PickerPattern$<'a, (T$,+), Route> + where (T$: Pickable<'a>,+) + { + pub args: PickerArgs<'a>, + pub error_route: Option<Route>, + ( + pub(crate) arg_$: &'a PickerArg<'a, T$>, + pub(crate) result_$: PickerArgResult<T$>, + pub(crate) default_$: Option<Box<dyn FnOnce() -> T$>>, + pub(crate) route_$: Option<Box<dyn FnOnce() -> Route>>, + pub(crate) post_$: Option<Box<dyn FnOnce(T$) -> T$>>, + +) + } +}); + +internal_repeat!(1..=32 => { + impl<'a, (T$,+), Route> PickerPattern$<'a, (T$,+), Route> + where (T$: Pickable<'a>,+) + { + /// Sets a default value provider for this arg. + /// + /// If the arg is not provided by the user at runtime, the given closure will be + /// called to produce a default value. The closure is expected to return `T$`. + /// + /// # Example + /// + #[allow(clippy::type_complexity)] + pub fn or<F>(mut self, func: F) -> Self + where + F: FnMut() -> T$, + F: 'static, + { + self.default_$ = Some(Box::new(func)); + self + } + + /// Uses the default value for this arg's type if the arg is not provided. + /// + /// If the arg is not provided by the user at runtime, the default value for `T$` + /// (as defined by the `Default` trait) will be used. + /// + /// # Example + /// + #[allow(clippy::type_complexity)] + pub fn or_default(mut self) -> Self + where + T$: Default, + { + self.default_$ = Some(Box::new(|| T$::default())); + self + } + + /// Sets a route for when the arg is not provided. + /// + /// If the arg is not provided by the user at runtime, the given closure will be + /// called to produce a route value that will be returned early. + /// + /// # Example + /// + pub fn or_route<F>(mut self, func: F) -> Self + where + F: FnMut() -> Route, + F: 'static, + { + self.route_$ = Some(Box::new(func)); + self + } + + + /// Resets the route for this picker pattern, allowing a different route type. + /// + /// This method converts the current `PickerPattern` into a new one with a different + /// route type `NewRoute`. All existing arg configurations, defaults, and post- + /// processing functions are preserved, but the `error_route` and individual + /// `route_$` fields are cleared (set to `None`). + /// + /// This is useful when you want to change the error/redirect route type mid-chain, + /// for example when composing patterns from different contexts that use different + /// route enums. + #[allow(clippy::type_complexity)] + pub fn with_route<NewRoute>(self) -> PickerPattern$<'a, (T$,+), NewRoute> + { + PickerPattern$ { + args: self.args, + error_route: None, + ( + arg_$: self.arg_$, + result_$: self.result_$, + default_$: self.default_$, + route_$: None, + post_$: self.post_$, + +) + } + } + + /// Attaches a post-processing function to this arg. + /// + /// After the arg's value is parsed (or defaulted), the given closure will be + /// invoked with the parsed value and its return value will be used as the final + /// result. This allows transforming or validating the parsed value. + /// + /// # Example + /// + #[allow(clippy::type_complexity)] + pub fn post<F>(mut self, func: F) -> Self + where + F: FnMut(T$) -> T$, + F: 'static, + { + self.post_$ = Some(Box::new(func)); + self + } + } +}); + +internal_repeat!(1..32 => { + impl<'a, (T$,+), Route> PickerPattern$<'a, (T$,+), Route> + where (T$: Pickable<'a>,+) + { + #[allow(clippy::type_complexity)] + /// Adds a new arg to the picking chain, returning a new `PickerPattern` with one more type parameter. + /// + /// This method extends the current picking pattern by appending an additional arg. + /// The previous args and their results are preserved as part of the new pattern. + /// The new arg's result is initially `Unparsed`. + pub fn pick<N>(self, arg: impl Into<&'a PickerArg<'a, N>>) -> PickerPattern$+<'a, (T$,+), N, Route> + where + N: Pickable<'a>, + { + PickerPattern$+ { + // Args + args: self.args, + error_route: self.error_route, + + // Current + arg_$+: arg.into(), + result_$+: PickerArgResult::Unparsed, + default_$+: None, + route_$+: None, + post_$+: None, + + // Prev + ( + arg_$: self.arg_$, + result_$: self.result_$, + default_$: self.default_$, + route_$: self.route_$, + post_$: self.post_$, + +) + } + } + + /// Picks a new arg with a default value provider in a single call. + /// + /// This is a shorthand for calling `.pick(arg).or(func)`. + /// + /// # Example + /// + #[allow(clippy::type_complexity)] + pub fn pick_or<N, F>(self, arg: impl Into<&'a PickerArg<'a, N>>, func: F) -> PickerPattern$+<'a, (T$,+), N, Route> + where + N: Pickable<'a>, + F: FnMut() -> N + 'static, + F: 'static, + { + self.pick(arg).or(func) + } + + /// Picks a new arg with a default value in a single call. + /// + /// This is a shorthand for calling `.pick(arg).or_default()`. + /// + /// # Example + /// + #[allow(clippy::type_complexity)] + pub fn pick_or_default<N>(self, arg: impl Into<&'a PickerArg<'a, N>>) -> PickerPattern$+<'a, (T$,+), N, Route> + where + N: Pickable<'a> + Default, + { + self.pick(arg).or_default() + } + + /// Picks a new arg with a route in a single call. + /// + /// This is a shorthand for calling `.pick(arg).or_route(func)`. + /// + /// # Example + /// + #[allow(clippy::type_complexity)] + pub fn pick_or_route<N, F>(self, arg: impl Into<&'a PickerArg<'a, N>>, func: F) -> PickerPattern$+<'a, (T$,+), N, Route> + where + N: Pickable<'a>, + F: FnMut() -> Route + 'static, + F: 'static, + { + self.pick(arg).or_route(func) + } + } +}); diff --git a/arg_picker/src/picker/result.rs b/arg_picker/src/picker/result.rs new file mode 100644 index 0000000..83ca2cd --- /dev/null +++ b/arg_picker/src/picker/result.rs @@ -0,0 +1,56 @@ +#![allow(clippy::type_complexity)] // Aha, Type Gymnastics! + +use arg_picker_macros::internal_repeat; + +internal_repeat!(1..=32 => { + #[doc(hidden)] + pub struct PickerResult$<(T$,+), Route> { + /// The route selected by the picker, if any. + /// If this is `Some`, the picker chose to follow a route instead of selecting values, + /// and all value fields (`v1`, `v2`, ...) will be `None`. + /// + /// Note: "route" here refers to an alternative path/choice, not a network route. + pub route: Option<Route>, + + ( + #[doc = concat!("The optional value for the ", $, "th type parameter.")] + pub v$: Option<T$>, + +) + } +}); + +internal_repeat!(1..=32 => { + impl<(T$,+), Route> PickerResult$<(T$,+), Route> { + /// Unwraps the result, panicking if a route was selected. + /// + /// # Panics + /// + /// Panics if `self.route` is `Some(...)`. + pub fn unwrap(self) -> ((T$,+)) { + ((self.v$.unwrap(),+)) + } + + /// Returns the individual option values without checking the route. + pub fn unpack(self) -> ((Option<T$>,+)) { + ((self.v$,+)) + } + + /// Converts to a `Result`, returning `Err(route)` if a route was selected, + /// or `Ok(values)` otherwise. + pub fn to_result(self) -> Result<((T$,+)), Route> { + if let Some(r) = self.route { + return Err(r); + } + Ok(self.unwrap()) + } + + /// Converts to an `Option`, returning `None` if a route was selected, + /// or `Some(values)` otherwise. + pub fn to_option(self) -> Option<((T$,+))> { + if let Some(_) = self.route { + return None; + } + Some(self.unwrap()) + } + } +}); |
