|
| 1 | +use once_cell::sync::Lazy; |
| 2 | +use regex::Regex; |
| 3 | +use std::path::Path; |
| 4 | +use std::path::PathBuf; |
| 5 | +use std::{env, ffi::OsStr}; |
| 6 | + |
| 7 | +#[derive(Debug)] |
| 8 | +pub enum PathError { |
| 9 | + IoError(std::io::Error), |
| 10 | + StripPrefixError(std::path::StripPrefixError), |
| 11 | + RegexError(regex::Error), |
| 12 | + VarError(std::env::VarError), |
| 13 | +} |
| 14 | + |
| 15 | +impl From<std::io::Error> for PathError { |
| 16 | + fn from(error: std::io::Error) -> Self { |
| 17 | + PathError::IoError(error) |
| 18 | + } |
| 19 | +} |
| 20 | + |
| 21 | +impl From<std::path::StripPrefixError> for PathError { |
| 22 | + fn from(error: std::path::StripPrefixError) -> Self { |
| 23 | + PathError::StripPrefixError(error) |
| 24 | + } |
| 25 | +} |
| 26 | + |
| 27 | +impl From<regex::Error> for PathError { |
| 28 | + fn from(error: regex::Error) -> Self { |
| 29 | + PathError::RegexError(error) |
| 30 | + } |
| 31 | +} |
| 32 | + |
| 33 | +impl From<std::env::VarError> for PathError { |
| 34 | + fn from(error: std::env::VarError) -> Self { |
| 35 | + PathError::VarError(error) |
| 36 | + } |
| 37 | +} |
| 38 | + |
| 39 | +static RE_VAR: Lazy<Regex> = Lazy::new(|| Regex::new(r"\$\{(.+)\}").unwrap()); |
| 40 | + |
| 41 | +static RE_VAR_WITHOUT_BRACES: Lazy<Regex> = Lazy::new(|| Regex::new(r"\$(.+)").unwrap()); |
| 42 | + |
| 43 | +static TILDE: Lazy<&OsStr> = Lazy::new(|| OsStr::new("~")); |
| 44 | + |
| 45 | +/// extend path::Path |
| 46 | +/// add useful functions to path::Path |
| 47 | +/// |
| 48 | +/// # Examples |
| 49 | +/// |
| 50 | +/// ``` |
| 51 | +/// use lib::path::{ExtendedPath, PathError}; |
| 52 | +/// use std::path::Path; |
| 53 | +/// |
| 54 | +/// fn main() -> Result<(), PathError> { |
| 55 | +/// let path = Path::new("./src/../Cargo.toml"); |
| 56 | +/// let cleaned_path = path.clean()?; |
| 57 | +/// println!("cleaned path: {:?}", cleaned_path); |
| 58 | +/// |
| 59 | +/// let path = Path::new("~/.cargo"); |
| 60 | +/// let expanded_path = path.expand_env()?; |
| 61 | +/// println!("expanded env path: {:?}", expanded_path); |
| 62 | +/// Ok(()) |
| 63 | +/// } |
| 64 | +/// ``` |
| 65 | +pub trait ExtendedPath { |
| 66 | + fn clean(&self) -> Result<PathBuf, PathError>; |
| 67 | + fn expand_env(&self) -> Result<PathBuf, PathError>; |
| 68 | +} |
| 69 | + |
| 70 | +impl ExtendedPath for Path { |
| 71 | + fn clean(&self) -> Result<PathBuf, PathError> { |
| 72 | + let abs_path = self.canonicalize()?; |
| 73 | + if self.is_absolute() { |
| 74 | + return Ok(abs_path); |
| 75 | + } |
| 76 | + let current_dir = env::current_dir()?; |
| 77 | + abs_path |
| 78 | + .strip_prefix(current_dir) |
| 79 | + .map(|path| path.to_path_buf()) |
| 80 | + .map_err(|e| e.into()) |
| 81 | + } |
| 82 | + |
| 83 | + fn expand_env(&self) -> Result<PathBuf, PathError> { |
| 84 | + let mut path_buf = PathBuf::new(); |
| 85 | + for path in self.iter() { |
| 86 | + let path_str = path.to_string_lossy(); |
| 87 | + let caps_opt = RE_VAR |
| 88 | + .captures(&path_str) |
| 89 | + .or_else(|| RE_VAR_WITHOUT_BRACES.captures(&path_str)); |
| 90 | + if let Some(caps) = caps_opt { |
| 91 | + let expanded_var = env::var(&caps[1])?; |
| 92 | + path_buf.push(expanded_var); |
| 93 | + continue; |
| 94 | + } |
| 95 | + if *TILDE == path { |
| 96 | + let home_dir = env::var("HOME")?; |
| 97 | + path_buf.push(home_dir); |
| 98 | + continue; |
| 99 | + } |
| 100 | + |
| 101 | + path_buf.push(path); |
| 102 | + } |
| 103 | + Ok(path_buf) |
| 104 | + } |
| 105 | +} |
0 commit comments