From 160460494f8c56fd42ff9ab96ace74cc4457f479 Mon Sep 17 00:00:00 2001 From: 魏曹先生 <1992414357@qq.com> Date: Tue, 28 Jul 2026 11:42:27 +0800 Subject: feat(picker): add filesystem-aware path value types Add `FilePath`, `NoFilePath`, `DirPath`, `NoDirPath`, `SymlinkPath`, `NoSymlinkPath`, `NoPath`, and `RecursiveFiles` wrapper types with filesystem validation at parse time. Each type implements `SinglePickable` and provides standard conversions via `From`, `AsRef`, `Deref`, and `DerefMut`. --- arg_picker/src/builtin/pick_paths.rs | 167 +++++++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 arg_picker/src/builtin/pick_paths.rs (limited to 'arg_picker/src/builtin/pick_paths.rs') 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 { + match ::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 { + match ::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 { + match ::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 { + match ::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 { + match ::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 { + match ::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 { + match ::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 { + match ::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) { + 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); + } + } + } +} -- cgit