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 fn slash_str(&self) -> String;
14 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}