path_ext/
lib.rs

1use path_slash::PathExt as PathSlashExt;
2use std::fs::{create_dir, create_dir_all, metadata, remove_dir_all, remove_file};
3use std::io::{self, Result};
4use std::path::is_separator;
5use walkdir::WalkDir;
6
7pub use std::path::{Path, PathBuf};
8
9pub trait PathExt {
10    /// output full path with slash
11    ///
12    /// e.g. "test\test.txt" -> "test/test.txt"
13    fn slash_str(&self) -> String;
14    /// output full path with backslash
15    ///
16    /// e.g. "test/test.txt" -> "test\test.txt"
17    // fn backslash_str(&self) -> &str;
18    fn full_str(&self) -> &str;
19    fn ext_str(&self) -> &str;
20    fn stem_str(&self) -> &str;
21    fn name_str(&self) -> &str;
22    fn create_parent_dir_all(&self) -> Result<()>;
23    fn merge<P: AsRef<Path>>(&self, append: P) -> PathBuf;
24    fn is_file(&self) -> bool;
25    fn is_dir(&self) -> bool;
26    fn walk_dir<F: Fn(&Path) -> bool>(&self, filter: F) -> Vec<PathBuf>;
27    fn mkdir_after_remove(&self) -> io::Result<PathBuf>;
28}
29
30impl<T: AsRef<Path>> PathExt for T {
31    #[inline]
32    fn slash_str(&self) -> String {
33        self.as_ref()
34            .to_slash()
35            .map(|s| s.to_string())
36            .unwrap_or_default()
37    }
38
39    #[inline]
40    fn full_str(&self) -> &str {
41        self.as_ref().to_str().unwrap_or_default()
42    }
43
44    #[inline]
45    fn ext_str(&self) -> &str {
46        self.as_ref()
47            .extension()
48            .and_then(|s| s.to_str())
49            .unwrap_or_default()
50    }
51
52    #[inline]
53    fn stem_str(&self) -> &str {
54        self.as_ref()
55            .file_stem()
56            .and_then(|s| s.to_str())
57            .unwrap_or_default()
58    }
59
60    #[inline]
61    fn name_str(&self) -> &str {
62        self.as_ref()
63            .file_name()
64            .and_then(|s| s.to_str())
65            .unwrap_or_default()
66    }
67
68    #[inline]
69    fn create_parent_dir_all(&self) -> io::Result<()> {
70        if let Some(root) = self.as_ref().parent() {
71            if !root.exists() {
72                create_dir_all(root)?;
73            }
74        }
75        Ok(())
76    }
77
78    #[inline]
79    fn mkdir_after_remove(&self) -> io::Result<PathBuf> {
80        if self.as_ref().exists() {
81            if self.as_ref().is_file() {
82                remove_file(self)?;
83            } else {
84                remove_dir_all(self)?;
85            }
86        }
87        create_dir(self)?;
88        Ok(self.as_ref().into())
89    }
90
91    #[inline]
92    fn merge<P: AsRef<Path>>(&self, append: P) -> PathBuf {
93        self.as_ref()
94            .iter()
95            .chain(append.as_ref().iter().filter(|&component| {
96                if let Some(c) = component
97                    .to_os_string()
98                    .into_string()
99                    .unwrap_or_default()
100                    .chars()
101                    .next()
102                {
103                    !is_separator(c)
104                } else {
105                    false
106                }
107            }))
108            .collect()
109    }
110
111    #[inline]
112    fn is_file(&self) -> bool {
113        metadata(self)
114            .map(|metadata| metadata.is_file())
115            .unwrap_or(false)
116    }
117
118    #[inline]
119    fn is_dir(&self) -> bool {
120        metadata(self)
121            .map(|metadata| metadata.is_dir())
122            .unwrap_or(false)
123    }
124
125    #[inline]
126    fn walk_dir<F: Fn(&Path) -> bool>(&self, filter: F) -> Vec<PathBuf> {
127        WalkDir::new(self)
128            .into_iter()
129            .filter_entry(|e| filter(e.path()))
130            .filter_map(|e| e.map(|item| item.into_path()).ok())
131            .collect()
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138
139    #[test]
140    fn test_path() {
141        let path1 = PathBuf::from("Z:\\Movies\\[VCB-Studio] Fate Zero [Ma10p_1080p]\\[VCB-Studio] Fate Zero [04][Ma10p_1080p][x265_flac].mkv");
142        println!("full path: {}", path1.full_str());
143        println!("file ext: {}", path1.ext_str());
144        println!("file stem: {}", path1.stem_str());
145        println!("file name: {}", path1.name_str());
146        let path2 = PathBuf::from("Z:\\Movies");
147        let path3 = PathBuf::from("[VCB-Studio] Fate Zero [Ma10p_1080p]\\[VCB-Studio] Fate Zero [04][Ma10p_1080p][x265_flac].mkv");
148        let path4 = path2.merge(path3);
149        println!("merged full path: {}", path4.full_str());
150        println!("file: {}", path1.is_file());
151        println!("dir: {}", path2.is_dir());
152        if let Some(parent) = path4.parent() {
153            for path in parent.walk_dir(|p| p.is_dir()) {
154                println!("subdir: {}", path.full_str());
155            }
156        }
157    }
158}