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