aboutsummaryrefslogtreecommitdiff
path: root/arg_picker/src
diff options
context:
space:
mode:
Diffstat (limited to 'arg_picker/src')
-rw-r--r--arg_picker/src/builtin.rs1
-rw-r--r--arg_picker/src/builtin/pick_paths.rs167
-rw-r--r--arg_picker/src/value.rs3
-rw-r--r--arg_picker/src/value/paths.rs294
4 files changed, 465 insertions, 0 deletions
diff --git a/arg_picker/src/builtin.rs b/arg_picker/src/builtin.rs
index b51590d..76c0645 100644
--- a/arg_picker/src/builtin.rs
+++ b/arg_picker/src/builtin.rs
@@ -3,6 +3,7 @@ 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_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/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()
+ }
+}