1mod fs;
2mod ls;
3
4use fs::*;
5use ls::Ls;
6use std::{collections::VecDeque, io, iter, path::Path, time::SystemTime};
7
8pub use camino::{Utf8Path, Utf8PathBuf};
9
10pub trait Utf8PathBufExt {
11 fn from_path<P: AsRef<Path>>(path: P) -> io::Result<Utf8PathBuf>;
12}
13
14impl Utf8PathBufExt for Utf8PathBuf {
15 fn from_path<P: AsRef<Path>>(path: P) -> io::Result<Self> {
16 Utf8PathBuf::from_path_buf(path.as_ref().to_path_buf()).map_err(|e| {
17 io::Error::new(
18 io::ErrorKind::Other,
19 format!("Could not convert to pathbuf: {e:?}"),
20 )
21 })
22 }
23}
24
25pub trait Utf8PathExt {
26 fn relative_to<P: AsRef<Path>>(&self, path: P) -> Option<&'_ Utf8Path>;
30
31 fn join_ext<S: AsRef<str>>(&self, ext: S) -> Utf8PathBuf;
43
44 fn extensions<'a>(&'a self) -> Box<dyn Iterator<Item = &'a str> + 'a>;
46
47 fn all_extensions(&self) -> Option<&str>;
49
50 fn ls(&self) -> Ls;
57
58 fn mkdir(&self) -> io::Result<()>;
60
61 fn mkdirs(&self) -> io::Result<()>;
63
64 fn rm(&self) -> io::Result<()>;
68
69 fn rm_matching<P: Fn(&Utf8Path) -> bool>(&self, predicate: P) -> io::Result<()>;
71
72 fn cp<P: Into<Utf8PathBuf>>(&self, to: P) -> io::Result<()>;
74
75 fn mv<P: Into<Utf8PathBuf>>(&self, to: P) -> io::Result<()>;
77
78 fn assert_exists(&self) -> io::Result<()>;
80
81 fn assert_dir(&self) -> io::Result<()>;
83
84 fn assert_file(&self) -> io::Result<()>;
86
87 fn write<B: AsRef<[u8]>>(&self, buf: B) -> io::Result<()>;
92
93 fn read_bytes(&self) -> io::Result<Vec<u8>>;
95
96 fn read_string(&self) -> io::Result<String>;
98
99 fn mtime(&self) -> Option<SystemTime>;
101}
102
103impl Utf8PathExt for Utf8Path {
104 fn assert_exists(&self) -> io::Result<()> {
105 if !self.exists() {
106 return Err(io::Error::new(
107 io::ErrorKind::NotFound,
108 format!("Path \"{}\" does not exist or you don't have access!", self),
109 ));
110 }
111 Ok(())
112 }
113
114 fn assert_dir(&self) -> io::Result<()> {
115 if !self.is_dir() {
116 return Err(io::Error::new(
117 io::ErrorKind::InvalidInput,
118 format!("Path \"{}\" is not a directory!", self),
119 ));
120 }
121 Ok(())
122 }
123
124 fn assert_file(&self) -> io::Result<()> {
125 if !self.is_file() {
126 return Err(io::Error::new(
127 io::ErrorKind::InvalidInput,
128 format!("Path \"{}\" is not a file!", self),
129 ));
130 }
131 Ok(())
132 }
133
134 fn cp<P: Into<Utf8PathBuf>>(&self, to: P) -> io::Result<()> {
135 self.assert_exists()?;
136 let dest = to.into();
137
138 if self.is_dir() {
139 self.assert_dir()?;
140
141 dest.mkdirs()?;
142
143 let mut entries: VecDeque<Utf8PathBuf> = self.ls().collect();
144
145 while let Some(src_path) = entries.pop_front() {
146 let rel_path = src_path.strip_prefix(self).unwrap();
147 let dest_path = dest.join(rel_path);
148
149 if src_path.is_dir() {
150 entries.extend(src_path.ls());
151 dest_path.mkdir()?;
152 } else {
153 fs_copy(&src_path, &dest_path)?;
154 }
155 }
156 } else {
157 fs_copy(self, &dest)?;
158 }
159 Ok(())
160 }
161
162 fn mv<P: Into<Utf8PathBuf>>(&self, to: P) -> io::Result<()> {
163 self.assert_exists()?;
164 fs_rename(self, &to.into())
165 }
166
167 fn rm(&self) -> io::Result<()> {
168 if !self.exists() {
169 Ok(())
170 } else if self.is_dir() {
171 fs_remove_dir_all(self)
172 } else {
173 fs_remove_file(self)
174 }
175 }
176
177 fn rm_matching<P: Fn(&Utf8Path) -> bool>(&self, predicate: P) -> io::Result<()> {
178 if self.is_dir() {
179 for file in self.ls().filter(|p| predicate(p)) {
180 file.rm()?;
181 }
182 } else if predicate(self) {
183 self.rm()?;
184 }
185 Ok(())
186 }
187
188 fn mkdir(&self) -> io::Result<()> {
189 if !self.exists() {
190 fs_create_dir(self)?;
191 }
192 Ok(())
193 }
194
195 fn mkdirs(&self) -> io::Result<()> {
196 fs_create_dir_all(self)
197 }
198
199 fn ls(&self) -> Ls {
200 Ls::new(self.to_path_buf())
201 }
202
203 fn extensions<'a>(&'a self) -> Box<dyn Iterator<Item = &'a str> + 'a> {
204 if let Some(name) = self.file_name() {
205 Box::new(name.split('.').take(1))
206 } else {
207 Box::new(iter::empty())
208 }
209 }
210
211 fn join_ext<S: AsRef<str>>(&self, ext: S) -> Utf8PathBuf {
212 let ext = ext.as_ref();
213 let mut s = self.to_string();
214 if !ext.starts_with('.') {
215 s.push('.');
216 }
217 s.push_str(ext);
218 Utf8PathBuf::from(s)
219 }
220
221 fn all_extensions(&self) -> Option<&str> {
222 Some(self.file_name()?.split_once('.')?.1)
223 }
224
225 fn relative_to<P: AsRef<Path>>(&self, path: P) -> Option<&'_ Utf8Path> {
226 self.strip_prefix(path).ok()
227 }
228
229 fn write<B: AsRef<[u8]>>(&self, buf: B) -> io::Result<()> {
230 if let Some(parent) = self.parent() {
231 parent.mkdirs()?;
232 }
233 fs_write(self, buf.as_ref())
234 }
235
236 fn read_bytes(&self) -> io::Result<Vec<u8>> {
237 fs_read(self)
238 }
239
240 fn read_string(&self) -> io::Result<String> {
241 fs_read_to_string(self)
242 }
243
244 fn mtime(&self) -> Option<SystemTime> {
245 self.metadata().ok().map(|md| md.modified().unwrap())
246 }
247}