diff options
Diffstat (limited to 'arg_picker/src/picker.rs')
| -rw-r--r-- | arg_picker/src/picker.rs | 432 |
1 files changed, 432 insertions, 0 deletions
diff --git a/arg_picker/src/picker.rs b/arg_picker/src/picker.rs new file mode 100644 index 0000000..ecab648 --- /dev/null +++ b/arg_picker/src/picker.rs @@ -0,0 +1,432 @@ +use std::{marker::PhantomData, ops::Index}; + +mod parse; + +mod patterns; +pub use patterns::*; + +mod result; +pub use result::*; + +use crate::{Pickable, PickerArg, PickerArgResult}; + +#[doc = include_str!("../README.md")] +pub struct Picker<'a, Route = ()> { + pub(crate) route_phantom: PhantomData<Route>, + + /// Internal arguments of Picker + pub(crate) args: PickerArgs<'a>, +} + +impl<'a> Picker<'a> { + /// Creates a new `Picker` from the command-line arguments (excluding the program name). + /// + /// This is equivalent to calling `std::env::args().skip(1)`, which + /// collects all arguments passed to the program except the first one + /// (the executable path). + pub fn from_args() -> Picker<'a, ()> { + Self::from_args_skip(1) + } + + /// Creates a new `Picker` from the command-line arguments, skipping the + /// first `skip` entries. + /// + /// This method is useful when you want more control over which arguments + /// are included. For example, pass `skip = 2` to skip both the program + /// name and the first argument. + pub fn from_args_skip(skip: usize) -> Picker<'a, ()> { + let args = std::env::args().skip(skip).collect::<Vec<String>>(); + Picker { + route_phantom: PhantomData, + args: PickerArgs::Owned(args), + } + } + + /// Changes the route (phantom type parameter) of the `Picker`. + /// + /// This method allows converting a `Picker` from one route type to another, + /// while preserving the same underlying arguments. The route type is typically + /// used to distinguish different parsing contexts or to carry compile-time + /// state information through the picking chain. + pub fn with_route<NewRoute>(self) -> Picker<'a, NewRoute> + where + Self: Sized, + { + Picker { + route_phantom: PhantomData, + args: self.args, + } + } +} + +/// Internal arguments of Picker +/// +/// - `Slice` - borrowed slice of string slices +/// - `Vec` - owned vector of borrowed string slices +/// - `Owned` - owned vector of owned strings +pub enum PickerArgs<'a> { + /// Borrowed slice of string slices + Slice(&'a [&'a str]), + /// Owned vector of borrowed string slices + Vec(Vec<&'a str>), + /// Owned vector of owned strings + Owned(Vec<String>), +} + +impl<'a> Default for PickerArgs<'a> { + fn default() -> Self { + Self::Vec(vec![]) + } +} + +impl<'a> PickerArgs<'a> { + /// Returns the number of arguments. + pub fn len(&self) -> usize { + match self { + PickerArgs::Slice(items) => items.len(), + PickerArgs::Vec(items) => items.len(), + PickerArgs::Owned(items) => items.len(), + } + } + + /// Returns `true` if there are no arguments. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Returns an iterator over the arguments, yielding `&str` values. + pub fn iter(&'a self) -> PickerIter<'a> { + match self { + PickerArgs::Slice(items) => PickerIter::Slice(items.iter()), + PickerArgs::Vec(items) => PickerIter::Vec(items.iter()), + PickerArgs::Owned(items) => PickerIter::Owned(items.iter()), + } + } + + /// Returns a reference to the argument at `index`, if it exists. + pub fn get(&self, index: usize) -> Option<&str> { + match self { + PickerArgs::Slice(items) => items.get(index).copied(), + PickerArgs::Vec(items) => items.get(index).copied(), + PickerArgs::Owned(items) => items.get(index).map(|s| s.as_str()), + } + } +} + +impl<'a> Index<usize> for PickerArgs<'a> { + type Output = str; + + fn index(&self, index: usize) -> &Self::Output { + match self { + PickerArgs::Slice(items) => items[index], + PickerArgs::Vec(items) => items[index], + PickerArgs::Owned(items) => &items[index], + } + } +} + +impl<'a> IntoIterator for &'a PickerArgs<'a> { + type Item = &'a str; + type IntoIter = PickerIter<'a>; + + fn into_iter(self) -> Self::IntoIter { + match self { + PickerArgs::Slice(items) => PickerIter::Slice(items.iter()), + PickerArgs::Vec(items) => PickerIter::Vec(items.iter()), + PickerArgs::Owned(items) => PickerIter::Owned(items.iter()), + } + } +} + +impl<'a, Route> From<&'a [&'a str]> for Picker<'a, Route> { + fn from(value: &'a [&'a str]) -> Self { + Picker { + route_phantom: PhantomData, + args: PickerArgs::Slice(value), + } + } +} + +impl<'a, Route> From<Vec<&'a str>> for Picker<'a, Route> { + fn from(value: Vec<&'a str>) -> Self { + Picker { + route_phantom: PhantomData, + args: PickerArgs::Vec(value), + } + } +} + +impl<'a, Route> From<Vec<String>> for Picker<'a, Route> { + fn from(value: Vec<String>) -> Self { + Picker { + route_phantom: PhantomData, + args: PickerArgs::Owned(value), + } + } +} + +impl<'a, Route> Picker<'a, Route> { + /// Returns a reference to the internal `PickerArgs`. + pub fn args(&self) -> &PickerArgs<'a> { + &self.args + } + + /// Returns a mutable reference to the internal `PickerArgs`. + pub fn args_mut(&mut self) -> &mut PickerArgs<'a> { + &mut self.args + } + + /// Consumes `self` and returns the internal `PickerArgs`. + pub fn into_args(self) -> PickerArgs<'a> { + self.args + } + + /// Returns the number of arguments. + pub fn len(&self) -> usize { + self.args.len() + } + + /// Returns `true` if there are no arguments. + pub fn is_empty(&self) -> bool { + self.args.is_empty() + } + + /// Returns an iterator over the arguments, yielding `&str` values. + pub fn iter(&'a self) -> PickerIter<'a> { + self.args.iter() + } +} + +impl<'a, Route> Index<usize> for Picker<'a, Route> { + type Output = str; + + fn index(&self, index: usize) -> &Self::Output { + &self.args[index] + } +} + +impl<'a, Route> Index<usize> for &Picker<'a, Route> { + type Output = str; + + fn index(&self, index: usize) -> &Self::Output { + &self.args[index] + } +} + +impl<'a, Route> IntoIterator for &'a Picker<'a, Route> { + type Item = &'a str; + type IntoIter = PickerIter<'a>; + + fn into_iter(self) -> Self::IntoIter { + self.args.iter() + } +} + +/// Iterator for `Picker` (and `PickerArgs`), yielding `&'a str` values. +pub enum PickerIter<'a> { + /// Iterates over a borrowed slice (`&[&str]`) + Slice(std::slice::Iter<'a, &'a str>), + /// Iterates over an owned vector of borrowed string slices (`Vec<&str>`) + Vec(std::slice::Iter<'a, &'a str>), + /// Iterates over an owned vector of owned strings (`Vec<String>`) + Owned(std::slice::Iter<'a, String>), +} + +impl<'a> Iterator for PickerIter<'a> { + type Item = &'a str; + + fn next(&mut self) -> Option<Self::Item> { + match self { + PickerIter::Slice(iter) => iter.next().copied(), + PickerIter::Vec(iter) => iter.next().copied(), + PickerIter::Owned(iter) => iter.next().map(|s| s.as_str()), + } + } + + fn size_hint(&self) -> (usize, Option<usize>) { + match self { + PickerIter::Slice(iter) => iter.size_hint(), + PickerIter::Vec(iter) => iter.size_hint(), + PickerIter::Owned(iter) => iter.size_hint(), + } + } +} + +impl<'a> ExactSizeIterator for PickerIter<'a> {} + +impl<'a, Route> Picker<'a, Route> { + /// Creates a `PickerPattern1` from the given arg to start a picking chain. + /// + /// This method initiates a parameter picking chain with one arg. + /// The result is initially `Unparsed`. + pub fn pick<N>(self, arg: impl Into<&'a PickerArg<'a, N>>) -> PickerPattern1<'a, N, Route> + where + N: Pickable<'a>, + { + Self::build_pattern1(self.args, arg.into(), None::<Route>) + } + + /// Creates a `PickerPattern1` from the given arg. + /// If parsing fails, attempts the fallback arg. + pub fn pick_or<N, F>( + self, + arg: impl Into<&'a PickerArg<'a, N>>, + or_arg: F, + ) -> PickerPattern1<'a, N, Route> + where + N: Pickable<'a>, + F: FnMut() -> N + 'static, + { + self.pick(arg).or(or_arg) + } + + /// Creates a `PickerPattern1` from the given arg. + /// If parsing fails, uses the provided default value. + pub fn pick_or_default<N>( + self, + arg: impl Into<&'a PickerArg<'a, N>>, + ) -> PickerPattern1<'a, N, Route> + where + N: Pickable<'a> + Default, + { + self.pick(arg).or_default() + } + + /// Creates a `PickerPattern1` from the given arg. + /// If parsing fails, switches to the given error route. + pub fn pick_or_route<N, F>( + self, + arg: impl Into<&'a PickerArg<'a, N>>, + error_route: F, + ) -> PickerPattern1<'a, N, Route> + where + N: Pickable<'a>, + F: FnMut() -> Route + 'static, + { + self.pick(arg).or_route(error_route) + } +} + +/// Trait for converting types into a `Picker` +/// +/// Implemented for: +/// - `&[&str]` (borrowed slice) +/// - `&[String]` (borrowed slice of owned strings) +/// - `Vec<&str>` (owned vector of borrowed strings) +/// - `Vec<String>` (owned vector of owned strings) +pub trait IntoPicker<'a> { + /// Converts the value into a `Picker` + /// + /// # Examples + /// + /// ``` + /// use arg_picker::{IntoPicker, Picker}; + /// + /// let args: Picker = (&["hello", "world"][..]).to_picker(); + /// assert_eq!(args.len(), 2); + /// + /// let args: Picker = vec!["foo", "bar"].to_picker(); + /// assert_eq!(args.len(), 2); + /// + /// let args: Picker = vec!["a".to_string(), "b".to_string()].to_picker(); + /// assert_eq!(args.len(), 2); + /// ``` + fn to_picker(self) -> Picker<'a, ()>; + + /// Creates a `PickerPattern1` from the given arg for the `pick` method. + /// + /// This method converts the value into a `Picker` and starts a parameter + /// picking chain with one arg. The result is initially `Unparsed`. + fn pick<N>(self, arg: impl Into<&'a PickerArg<'a, N>>) -> PickerPattern1<'a, N, ()> + where + Self: Sized, + N: Pickable<'a> + Default + Sized, + { + Picker::build_pattern1(self.to_picker().args, arg.into(), None::<()>) + } + + /// Converts the value into a `Picker` with a specified route type. + /// + /// This method allows changing the route (phantom type parameter) of the picker. + /// The route type is typically used to distinguish different parsing contexts or + /// to carry compile-time state information through the picking chain. + fn with_route<NewRoute>(self) -> Picker<'a, NewRoute> + where + Self: Sized, + { + Picker { + route_phantom: PhantomData, + args: self.to_picker().args, + } + } +} + +impl<'a> IntoPicker<'a> for &'a [&'a str] { + fn to_picker(self) -> Picker<'a, ()> { + Picker { + route_phantom: PhantomData, + args: PickerArgs::Slice(self), + } + } +} + +impl<'a> IntoPicker<'a> for &'a [String] { + fn to_picker(self) -> Picker<'a, ()> { + let vec: Vec<&str> = self.iter().map(|s| s.as_str()).collect(); + Picker { + route_phantom: PhantomData, + args: PickerArgs::Vec(vec), + } + } +} + +impl<'a> IntoPicker<'a> for Vec<&'a str> { + fn to_picker(self) -> Picker<'a, ()> { + Picker { + route_phantom: PhantomData, + args: PickerArgs::Vec(self), + } + } +} + +impl<'a> IntoPicker<'a> for &'a Vec<String> { + fn to_picker(self) -> Picker<'a, ()> { + let slice: Vec<&str> = self.iter().map(|s| s.as_str()).collect(); + Picker { + route_phantom: PhantomData, + args: PickerArgs::Vec(slice), + } + } +} + +impl<'a> IntoPicker<'a> for Vec<String> { + fn to_picker(self) -> Picker<'a, ()> { + Picker { + route_phantom: PhantomData, + args: PickerArgs::Owned(self), + } + } +} + +// Private helper: shared construction logic for `PickerPattern1`. +// Both `Picker::pick` and `IntoPicker::pick` delegate to this. +impl<'a, Route> Picker<'a, Route> { + pub(crate) fn build_pattern1<N>( + args: PickerArgs<'a>, + arg: &'a PickerArg<'a, N>, + error_route: Option<Route>, + ) -> PickerPattern1<'a, N, Route> + where + N: Pickable<'a>, + { + PickerPattern1 { + args, + error_route, + arg_1: arg, + result_1: PickerArgResult::Unparsed, + route_1: None, + default_1: None, + post_1: None, + } + } +} |
