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 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}