diff options
Diffstat (limited to 'arg_picker/src')
| -rw-r--r-- | arg_picker/src/arg.rs | 18 | ||||
| -rw-r--r-- | arg_picker/src/builtin.rs | 4 | ||||
| -rw-r--r-- | arg_picker/src/builtin/pick_ip_attr.rs | 39 | ||||
| -rw-r--r-- | arg_picker/src/builtin/pick_pathbuf.rs | 18 | ||||
| -rw-r--r-- | arg_picker/src/builtin/pick_paths.rs | 167 | ||||
| -rw-r--r-- | arg_picker/src/builtin/pick_socket_attr.rs | 39 | ||||
| -rw-r--r-- | arg_picker/src/corebind.rs | 2 | ||||
| -rw-r--r-- | arg_picker/src/picker/parse.rs | 49 | ||||
| -rw-r--r-- | arg_picker/src/picker/result.rs | 49 | ||||
| -rw-r--r-- | arg_picker/src/value.rs | 3 | ||||
| -rw-r--r-- | arg_picker/src/value/paths.rs | 294 |
11 files changed, 679 insertions, 3 deletions
diff --git a/arg_picker/src/arg.rs b/arg_picker/src/arg.rs index 4fa21b5..0b5176d 100644 --- a/arg_picker/src/arg.rs +++ b/arg_picker/src/arg.rs @@ -1,4 +1,4 @@ -use crate::{Pickable, PickerArgInfo}; +use crate::{Pickable, PickerArgInfo, SinglePickable, parselib::ParserStyle}; use std::marker::PhantomData; /// Represents a constraint definition for a parameter selection. @@ -164,6 +164,22 @@ where } } +impl<'a, Type> From<PickerArg<'a, Type>> for Vec<String> +where + Type: SinglePickable, +{ + fn from(value: PickerArg<'a, Type>) -> Self { + let mut result = Vec::new(); + let info = PickerArgInfo::from(value); + let possible_flags = + crate::parselib::build_possible_flags(ParserStyle::global_style(), &info); + for flag in possible_flags { + result.push(flag); + } + result + } +} + /// Describes the attribute (behavior) of a command-line parameter. /// /// The ordering reflects parse priority (higher = parsed first): diff --git a/arg_picker/src/builtin.rs b/arg_picker/src/builtin.rs index 1c698ba..76c0645 100644 --- a/arg_picker/src/builtin.rs +++ b/arg_picker/src/builtin.rs @@ -1,5 +1,9 @@ mod pick_bool; mod pick_flag; +mod pick_ip_attr; mod pick_numbers; +mod pick_pathbuf; +mod pick_paths; mod pick_picker_args; +mod pick_socket_attr; mod pick_string; diff --git a/arg_picker/src/builtin/pick_ip_attr.rs b/arg_picker/src/builtin/pick_ip_attr.rs new file mode 100644 index 0000000..d68fa15 --- /dev/null +++ b/arg_picker/src/builtin/pick_ip_attr.rs @@ -0,0 +1,39 @@ +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; + +use crate::SinglePickable; + +impl SinglePickable for IpAddr { + fn pick_single(str: Option<&str>) -> crate::PickerArgResult<Self> { + match str { + Some(s) => match s.parse::<IpAddr>() { + Ok(addr) => crate::PickerArgResult::Parsed(addr), + Err(_) => crate::PickerArgResult::NotFound, + }, + None => crate::PickerArgResult::NotFound, + } + } +} + +impl SinglePickable for Ipv4Addr { + fn pick_single(str: Option<&str>) -> crate::PickerArgResult<Self> { + match str { + Some(s) => match s.parse::<Ipv4Addr>() { + Ok(addr) => crate::PickerArgResult::Parsed(addr), + Err(_) => crate::PickerArgResult::NotFound, + }, + None => crate::PickerArgResult::NotFound, + } + } +} + +impl SinglePickable for Ipv6Addr { + fn pick_single(str: Option<&str>) -> crate::PickerArgResult<Self> { + match str { + Some(s) => match s.parse::<Ipv6Addr>() { + Ok(addr) => crate::PickerArgResult::Parsed(addr), + Err(_) => crate::PickerArgResult::NotFound, + }, + None => crate::PickerArgResult::NotFound, + } + } +} diff --git a/arg_picker/src/builtin/pick_pathbuf.rs b/arg_picker/src/builtin/pick_pathbuf.rs new file mode 100644 index 0000000..3bd4410 --- /dev/null +++ b/arg_picker/src/builtin/pick_pathbuf.rs @@ -0,0 +1,18 @@ +use std::path::PathBuf; + +use crate::{ + PickerArgResult::{NotFound, Parsed}, + SinglePickable, +}; + +impl SinglePickable for PathBuf { + fn pick_single(str: Option<&str>) -> crate::PickerArgResult<Self> { + match str { + Some(str) => match just_fmt::fmt_path_str(str) { + Ok(formated) => Parsed(PathBuf::from(formated)), + Err(_) => NotFound, + }, + None => NotFound, + } + } +} diff --git a/arg_picker/src/builtin/pick_paths.rs b/arg_picker/src/builtin/pick_paths.rs new file mode 100644 index 0000000..8e6c0fd --- /dev/null +++ b/arg_picker/src/builtin/pick_paths.rs @@ -0,0 +1,167 @@ +use std::{ + fs, + path::{Path, PathBuf}, +}; + +use crate::{ + PickerArgResult::{self, NotFound, Parsed, Unparsed}, + SinglePickable, + value::{ + DirPath, FilePath, NoDirPath, NoFilePath, NoPath, NoSymlinkPath, RecursiveFiles, + SymlinkPath, + }, +}; + +impl SinglePickable for FilePath { + fn pick_single(str: Option<&str>) -> PickerArgResult<Self> { + match <PathBuf as SinglePickable>::pick_single(str) { + Parsed(path) => { + if path.exists() && path.is_file() { + Parsed(FilePath::from(path)) + } else { + NotFound + } + } + Unparsed => Unparsed, + NotFound => NotFound, + } + } +} + +impl SinglePickable for NoFilePath { + fn pick_single(str: Option<&str>) -> PickerArgResult<Self> { + match <PathBuf as SinglePickable>::pick_single(str) { + Parsed(path) => { + if !path.exists() || !path.is_file() { + Parsed(NoFilePath::from(path)) + } else { + NotFound + } + } + Unparsed => Unparsed, + NotFound => NotFound, + } + } +} + +impl SinglePickable for DirPath { + fn pick_single(str: Option<&str>) -> PickerArgResult<Self> { + match <PathBuf as SinglePickable>::pick_single(str) { + Parsed(path) => { + if path.exists() && path.is_dir() { + Parsed(DirPath::from(path)) + } else { + NotFound + } + } + Unparsed => Unparsed, + NotFound => NotFound, + } + } +} + +impl SinglePickable for NoDirPath { + fn pick_single(str: Option<&str>) -> PickerArgResult<Self> { + match <PathBuf as SinglePickable>::pick_single(str) { + Parsed(path) => { + if !path.exists() || !path.is_dir() { + Parsed(NoDirPath::from(path)) + } else { + NotFound + } + } + Unparsed => Unparsed, + NotFound => NotFound, + } + } +} + +impl SinglePickable for SymlinkPath { + fn pick_single(str: Option<&str>) -> PickerArgResult<Self> { + match <PathBuf as SinglePickable>::pick_single(str) { + Parsed(path) => { + if path.exists() && path.is_symlink() { + Parsed(SymlinkPath::from(path)) + } else { + NotFound + } + } + Unparsed => Unparsed, + NotFound => NotFound, + } + } +} + +impl SinglePickable for NoSymlinkPath { + fn pick_single(str: Option<&str>) -> PickerArgResult<Self> { + match <PathBuf as SinglePickable>::pick_single(str) { + Parsed(path) => { + if !path.exists() || !path.is_symlink() { + Parsed(NoSymlinkPath::from(path)) + } else { + NotFound + } + } + Unparsed => Unparsed, + NotFound => NotFound, + } + } +} + +impl SinglePickable for NoPath { + fn pick_single(str: Option<&str>) -> PickerArgResult<Self> { + match <PathBuf as SinglePickable>::pick_single(str) { + Parsed(path) => { + if !path.exists() { + Parsed(NoPath::from(path)) + } else { + NotFound + } + } + Unparsed => Unparsed, + NotFound => NotFound, + } + } +} + +impl SinglePickable for RecursiveFiles { + fn pick_single(str: Option<&str>) -> PickerArgResult<Self> { + match <PathBuf as SinglePickable>::pick_single(str) { + Parsed(path) => { + if !path.exists() { + return NotFound; + } + if path.is_file() || path.is_symlink() { + return Parsed(RecursiveFiles::from(vec![path])); + } + let mut entries = Vec::new(); + if let Ok(dir_entries) = fs::read_dir(&path) { + for entry in dir_entries.flatten() { + let entry_path = entry.path(); + if entry_path.is_file() || entry_path.is_symlink() { + entries.push(entry_path); + } else if entry_path.is_dir() { + collect_files(&entry_path, &mut entries); + } + } + } + Parsed(RecursiveFiles::from(entries)) + } + Unparsed => Unparsed, + NotFound => NotFound, + } + } +} + +fn collect_files(dir: &Path, entries: &mut Vec<PathBuf>) { + if let Ok(dir_entries) = fs::read_dir(dir) { + for entry in dir_entries.flatten() { + let entry_path = entry.path(); + if entry_path.is_file() || entry_path.is_symlink() { + entries.push(entry_path); + } else if entry_path.is_dir() { + collect_files(&entry_path, entries); + } + } + } +} diff --git a/arg_picker/src/builtin/pick_socket_attr.rs b/arg_picker/src/builtin/pick_socket_attr.rs new file mode 100644 index 0000000..0c0dd71 --- /dev/null +++ b/arg_picker/src/builtin/pick_socket_attr.rs @@ -0,0 +1,39 @@ +use std::net::{SocketAddr, SocketAddrV4, SocketAddrV6}; + +use crate::SinglePickable; + +impl SinglePickable for SocketAddr { + fn pick_single(str: Option<&str>) -> crate::PickerArgResult<Self> { + match str { + Some(s) => match s.parse::<SocketAddr>() { + Ok(addr) => crate::PickerArgResult::Parsed(addr), + Err(_) => crate::PickerArgResult::NotFound, + }, + None => crate::PickerArgResult::NotFound, + } + } +} + +impl SinglePickable for SocketAddrV4 { + fn pick_single(str: Option<&str>) -> crate::PickerArgResult<Self> { + match str { + Some(s) => match s.parse::<SocketAddrV4>() { + Ok(addr) => crate::PickerArgResult::Parsed(addr), + Err(_) => crate::PickerArgResult::NotFound, + }, + None => crate::PickerArgResult::NotFound, + } + } +} + +impl SinglePickable for SocketAddrV6 { + fn pick_single(str: Option<&str>) -> crate::PickerArgResult<Self> { + match str { + Some(s) => match s.parse::<SocketAddrV6>() { + Ok(addr) => crate::PickerArgResult::Parsed(addr), + Err(_) => crate::PickerArgResult::NotFound, + }, + None => crate::PickerArgResult::NotFound, + } + } +} diff --git a/arg_picker/src/corebind.rs b/arg_picker/src/corebind.rs deleted file mode 100644 index 3581871..0000000 --- a/arg_picker/src/corebind.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod entry_picker; -pub use entry_picker::*; diff --git a/arg_picker/src/picker/parse.rs b/arg_picker/src/picker/parse.rs index 9db5bd9..4a959e3 100644 --- a/arg_picker/src/picker/parse.rs +++ b/arg_picker/src/picker/parse.rs @@ -33,6 +33,55 @@ internal_repeat!(1..=32 => { ((p.v$.expect(concat!("missing required argument at position ", $)),+)) } + /// Returns the parsed values, using the default values for any missing + /// required arguments, or panicking if a route was selected. + /// + /// # Panics + /// + /// Panics if a route was selected. + /// + /// # Type Constraints + /// + /// All types in the tuple must implement [`Default`]. + pub fn unwrap_or_default(self) -> ((T$,+)) + where + ( + T$: Default, + +) { + let p = self.parse(); + ((p.v$.unwrap_or_default(),+)) + } + + /// Returns the parsed values, using the provided closure to generate + /// default values for any missing required arguments, or panicking if + /// a route was selected. + /// + /// # Panics + /// + /// Panics if a route was selected. + pub fn unwrap_or_else<F>(self, op: F) -> ((T$,+)) + where + F: FnOnce(Route) -> ((T$,+)), + { + let r = self.to_result(); + r.unwrap_or_else(op) + } + + /// Returns the parsed values, or panics with the given message if + /// a route was selected. + /// + /// # Panics + /// + /// Panics if a route was selected, with the provided message. + /// + /// # Type Constraints + /// + /// `Route` must implement [`std::fmt::Debug`] so that the error + /// message can include the route value. + pub fn expect(self, msg: &str) -> ((T$,+)) where Route: std::fmt::Debug { + self.to_result().expect(msg) + } + /// Returns the individual option values without checking the route. pub fn unpack(self) -> ((Option<T$>,+)) { let p = self.parse(); diff --git a/arg_picker/src/picker/result.rs b/arg_picker/src/picker/result.rs index 2099825..f13f52e 100644 --- a/arg_picker/src/picker/result.rs +++ b/arg_picker/src/picker/result.rs @@ -32,6 +32,55 @@ internal_repeat!(1..=32 => { ((self.v$.expect(concat!("missing required argument at position ", $)),+)) } + /// Returns the parsed values, using the default values for any missing + /// required arguments, or panicking if a route was selected. + /// + /// # Panics + /// + /// Panics if a route was selected. + /// + /// # Type Constraints + /// + /// All types in the tuple must implement [`Default`]. + pub fn unwrap_or_default(self) -> ((T$,+)) + where + ( + T$: Default, + +) { + let p = self; + ((p.v$.unwrap_or_default(),+)) + } + + /// Returns the parsed values, using the provided closure to generate + /// default values for any missing required arguments, or panicking if + /// a route was selected. + /// + /// # Panics + /// + /// Panics if a route was selected. + pub fn unwrap_or_else<F>(self, op: F) -> ((T$,+)) + where + F: FnOnce(Route) -> ((T$,+)), + { + let r = self.to_result(); + r.unwrap_or_else(op) + } + + /// Returns the parsed values, or panics with the given message if + /// a route was selected. + /// + /// # Panics + /// + /// Panics if a route was selected, with the provided message. + /// + /// # Type Constraints + /// + /// `Route` must implement [`std::fmt::Debug`] so that the error + /// message can include the route value. + pub fn expect(self, msg: &str) -> ((T$,+)) where Route: std::fmt::Debug { + self.to_result().expect(msg) + } + /// Returns the individual option values without checking the route. pub fn unpack(self) -> ((Option<T$>,+)) { ((self.v$,+)) diff --git a/arg_picker/src/value.rs b/arg_picker/src/value.rs index 995aa00..04d3e01 100644 --- a/arg_picker/src/value.rs +++ b/arg_picker/src/value.rs @@ -1,5 +1,8 @@ mod flag; pub use flag::*; +mod paths; +pub use paths::*; + mod vec_until; pub use vec_until::*; diff --git a/arg_picker/src/value/paths.rs b/arg_picker/src/value/paths.rs new file mode 100644 index 0000000..403d6cc --- /dev/null +++ b/arg_picker/src/value/paths.rs @@ -0,0 +1,294 @@ +use std::{ + ops::{Deref, DerefMut}, + path::{Path, PathBuf}, +}; + +/// A file path. +/// +/// `FilePath` is a wrapper type around `PathBuf` representing an arbitrary file path. +/// +/// It implements `Pickable` and can be parsed by a `Picker`. +/// +/// # Parsing Behavior +/// +/// At the moment of parsing, checks whether the path exists and is a file. +/// If not a file, returns `NotFound`. +#[non_exhaustive] +pub struct FilePath { + path: PathBuf, +} + +/// A file path that should not exist. +/// +/// `NoFilePath` is a wrapper type around `PathBuf` representing an arbitrary path +/// that must not currently point to an existing file. +/// +/// It implements `Pickable` and can be parsed by a `Picker`. +/// +/// # Parsing Behavior +/// +/// At the moment of parsing, checks whether no file exists at the given path. +/// If a file already exists (regardless of whether it is a regular file, directory, +/// symlink, or other type), returns `NotFound`. +#[non_exhaustive] +pub struct NoFilePath { + path: PathBuf, +} + +/// A directory path. +/// +/// `DirPath` is a wrapper type around `PathBuf` representing an arbitrary existing +/// directory path. +/// +/// It implements `Pickable` and can be parsed by a `Picker`. +/// +/// # Parsing Behavior +/// +/// At the moment of parsing, checks whether a directory exists at the given path. +/// If no directory exists, returns `NotFound`. +#[non_exhaustive] +pub struct DirPath { + path: PathBuf, +} + +/// A directory path that should not exist. +/// +/// `NoDirPath` is a wrapper type around `PathBuf` representing an arbitrary path +/// that must not currently point to an existing directory. +/// +/// It implements `Pickable` and can be parsed by a `Picker`. +/// +/// # Parsing Behavior +/// +/// At the moment of parsing, checks whether no directory exists at the given path. +/// If a directory already exists (regardless of whether it is a regular file, file, +/// symlink, or other type), returns `NotFound`. +#[non_exhaustive] +pub struct NoDirPath { + path: PathBuf, +} + +/// A symbolic link path. +/// +/// `SymlinkPath` is a wrapper type around `PathBuf` representing an existing symbolic link. +/// +/// It implements `Pickable` and can be parsed by a `Picker`. +/// +/// # Parsing Behavior +/// +/// At the moment of parsing, checks whether the path exists and is a symlink. +/// If not a symlink, returns `NotFound`. +#[non_exhaustive] +pub struct SymlinkPath { + path: PathBuf, +} + +/// A symbolic link path that should not exist. +/// +/// `NoSymlinkPath` is a wrapper type around `PathBuf` representing an arbitrary path +/// that must not currently point to an existing symbolic link. +/// +/// It implements `Pickable` and can be parsed by a `Picker`. +/// +/// # Parsing Behavior +/// +/// At the moment of parsing, checks whether no symlink exists at the given path. +/// If a symlink already exists (regardless of whether it points to a file, directory, +/// or other type), returns `NotFound`. +#[non_exhaustive] +pub struct NoSymlinkPath { + path: PathBuf, +} + +/// A path that should not exist at all. +/// +/// `NoPath` is a wrapper type around `PathBuf` representing an arbitrary path +/// that must not currently exist on the filesystem (as a file, directory, symlink, +/// or any other type). +/// +/// It implements `Pickable` and can be parsed by a `Picker`. +/// +/// # Parsing Behavior +/// +/// At the moment of parsing, checks whether any filesystem entry exists at the given path. +/// If anything exists at that path, returns `NotFound`. +#[non_exhaustive] +pub struct NoPath { + path: PathBuf, +} + +/// Implements common trait impls (From, AsRef, Deref, DerefMut) for a path wrapper type. +macro_rules! impl_path_traits { + ($type:ident) => { + impl From<PathBuf> for $type { + fn from(value: PathBuf) -> Self { + $type { path: value } + } + } + + impl From<&PathBuf> for $type { + fn from(value: &PathBuf) -> Self { + $type { + path: value.clone(), + } + } + } + + impl AsRef<Path> for $type { + fn as_ref(&self) -> &Path { + &self.path + } + } + + impl DerefMut for $type { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.path + } + } + + impl Deref for $type { + type Target = PathBuf; + + fn deref(&self) -> &Self::Target { + &self.path + } + } + + impl From<$type> for PathBuf { + fn from(value: $type) -> Self { + value.path + } + } + + impl From<&$type> for PathBuf { + fn from(value: &$type) -> Self { + value.path.clone() + } + } + }; +} + +impl_path_traits!(FilePath); +impl_path_traits!(NoFilePath); +impl_path_traits!(DirPath); +impl_path_traits!(NoDirPath); +impl_path_traits!(SymlinkPath); +impl_path_traits!(NoSymlinkPath); +impl_path_traits!(NoPath); + +/// Recursive file paths. +/// +/// `RecursiveFiles` is a wrapper type around `Vec<PathBuf>` representing a list of +/// existing files. +/// +/// It implements `Pickable` and can be parsed by a `Picker`. +/// +/// # Parsing Behavior +/// +/// - If a file path is given, returns a list of length 1 containing that file. +/// - If a directory path is given, recursively collects all files under that directory +/// and returns them as a list. +#[non_exhaustive] +pub struct RecursiveFiles { + paths: Vec<PathBuf>, +} + +impl From<Vec<PathBuf>> for RecursiveFiles { + fn from(paths: Vec<PathBuf>) -> Self { + Self { paths } + } +} + +impl From<RecursiveFiles> for Vec<PathBuf> { + fn from(value: RecursiveFiles) -> Self { + value.paths + } +} + +impl AsRef<[PathBuf]> for RecursiveFiles { + fn as_ref(&self) -> &[PathBuf] { + &self.paths + } +} + +impl Deref for RecursiveFiles { + type Target = Vec<PathBuf>; + + fn deref(&self) -> &Self::Target { + &self.paths + } +} + +impl DerefMut for RecursiveFiles { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.paths + } +} + +impl RecursiveFiles { + /// Returns the number of file paths. + pub fn len(&self) -> usize { + self.paths.len() + } + + /// Returns `true` if there are no file paths. + pub fn is_empty(&self) -> bool { + self.paths.is_empty() + } + + /// Returns an iterator over the file paths. + pub fn iter(&self) -> std::slice::Iter<'_, PathBuf> { + self.paths.iter() + } +} + +impl From<Vec<RecursiveFiles>> for RecursiveFiles { + fn from(value: Vec<RecursiveFiles>) -> Self { + Self { + paths: value.into_iter().flat_map(|r| r.paths).collect(), + } + } +} + +/// Trait for types that can be combined into a single `RecursiveFiles`. +pub trait IntoRecursiveFiles { + /// Combines multiple sources of file paths into a single `RecursiveFiles`. + fn combine(self) -> RecursiveFiles; +} + +impl<T> IntoRecursiveFiles for Vec<T> +where + T: Into<RecursiveFiles>, +{ + fn combine(self) -> RecursiveFiles { + self.into_iter() + .map(Into::into) + .collect::<Vec<RecursiveFiles>>() + .into() + } +} + +impl<T> IntoRecursiveFiles for &[T] +where + T: Into<RecursiveFiles> + Clone, +{ + fn combine(self) -> RecursiveFiles { + self.iter() + .cloned() + .map(Into::into) + .collect::<Vec<RecursiveFiles>>() + .into() + } +} + +impl<T, const N: usize> IntoRecursiveFiles for [T; N] +where + T: Into<RecursiveFiles>, +{ + fn combine(self) -> RecursiveFiles { + self.into_iter() + .map(Into::into) + .collect::<Vec<RecursiveFiles>>() + .into() + } +} |
