1use crate::bait::ResultExt as _;
3use crate::bog::BogOkExt;
4use crate::{ebog, ibog, unwrap};
5use std::cmp::Ordering;
6use std::io;
7use std::path::{Component, PathBuf};
8use std::{
9 fs::{self, DirEntry},
10 path::Path,
11};
12
13pub fn is_executable(path: impl AsRef<Path>) -> bool {
18 let path = path.as_ref();
19 let error_prefix = format!("Failed to check executability of {path:?}");
20
21 #[cfg(unix)]
22 {
23 let metadata = unwrap!(std::fs::metadata(path).prefix(&error_prefix)._ebog());
24 use std::os::unix::fs::PermissionsExt;
25 metadata.permissions().mode() & 0o111 != 0
26 }
27
28 #[cfg(windows)]
29 {
30 let ext = path
31 .extension()
32 .and_then(|e| e.to_str())
33 .unwrap_or_default()
34 .to_ascii_lowercase();
35 matches!(ext.as_str(), "exe" | "bat" | "cmd" | "com")
36 }
37
38 #[cfg(not(any(unix, windows)))]
39 {
40 ebog!("{error_prefix}: unsupported platform.");
41 false
42 }
43}
44
45pub fn set_executable(path: &Path) -> Result<(), std::io::Error> {
58 #[cfg(windows)]
59 {
60 Ok(())
63 }
64 #[cfg(unix)]
65 {
66 use std::os::unix::fs::PermissionsExt;
67 let metadata = std::fs::metadata(path)?;
68
69 let mut perms = metadata.permissions();
70 perms.set_mode(perms.mode() | 0o111); fs::set_permissions(path, perms)
72 }
73 #[cfg(not(any(unix, windows)))]
74 {
75 Ok(())
76 }
77}
78
79pub fn permissions(path: &Path) -> [bool; 3] {
81 #[cfg(unix)]
82 {
83 use std::os::unix::fs::PermissionsExt;
84 let metadata = match std::fs::metadata(path) {
85 Ok(m) => m,
86 Err(_) => return [false; 3],
87 };
88 let mode = metadata.permissions().mode();
89 [
90 mode & 0o400 != 0, mode & 0o200 != 0, mode & 0o100 != 0, ]
94 }
95 #[cfg(windows)]
96 {
97 let metadata = match std::fs::metadata(path) {
98 Ok(m) => m,
99 Err(_) => return [false; 3],
100 };
101 let readonly = metadata.permissions().readonly();
102 let ext = path
103 .extension()
104 .and_then(|e| e.to_str())
105 .unwrap_or_default()
106 .to_ascii_lowercase();
107 let executable = matches!(ext.as_str(), "exe" | "bat" | "cmd" | "com");
108 [true, !readonly, executable]
109 }
110 #[cfg(not(any(unix, windows)))]
111 {
112 [false; 3]
113 }
114}
115
116pub fn is_symlink(path: impl AsRef<Path>) -> bool {
120 let path = path.as_ref();
121 let error_prefix = format!("Failed to check metadata of {path:?}");
122
123 let meta = unwrap!(fs::symlink_metadata(path).prefix(&error_prefix)._ebog());
124 meta.file_type().is_symlink()
125}
126
127pub fn symlink(
140 src: impl AsRef<Path>,
141 dst: impl AsRef<Path>,
142 relative: bool,
143) -> Result<(), std::io::Error> {
144 let src = src.as_ref();
145 let dst = dst.as_ref();
146
147 if let Some(parent) = dst.parent() {
148 std::fs::create_dir_all(parent)?;
149 }
150
151 let target = if relative {
152 unwrap!(
153 diff_paths(src, dst),
154 std::io::Error::other("Unable to determine relative path")
155 )
156 } else {
157 src.to_path_buf()
158 };
159
160 #[cfg(unix)]
161 {
162 std::os::unix::fs::symlink(&target, dst)?;
163 }
164
165 #[cfg(windows)]
166 {
167 use std::os::windows::fs::{symlink_dir, symlink_file};
168
169 let meta = src.metadata()?;
170 if meta.is_dir() {
171 symlink_dir(&target, dst)?;
172 } else {
173 symlink_file(&target, dst)?;
174 }
175 }
176
177 Ok(())
178}
179
180pub fn diff_paths<P, B>(path: P, base: B) -> Option<PathBuf>
207where
208 P: AsRef<Path>,
209 B: AsRef<Path>,
210{
211 let path = path.as_ref();
212 let base = base.as_ref();
213
214 if path.is_absolute() != base.is_absolute() {
215 if path.is_absolute() {
216 Some(PathBuf::from(path))
217 } else {
218 None
219 }
220 } else {
221 let mut ita = path.components();
222 let mut itb = base.components();
223 let mut comps: Vec<Component> = vec![];
224 loop {
225 match (ita.next(), itb.next()) {
226 (None, None) => break,
227 (Some(a), None) => {
228 comps.push(a);
229 comps.extend(ita.by_ref());
230 break;
231 }
232 (None, _) => comps.push(Component::ParentDir),
233 (Some(a), Some(b)) if comps.is_empty() && a == b => (),
234 (Some(a), Some(b)) if b == Component::CurDir => comps.push(a),
235 (Some(_), Some(b)) if b == Component::ParentDir => return None,
236 (Some(a), Some(_)) => {
237 comps.push(Component::ParentDir);
238 for _ in itb {
239 comps.push(Component::ParentDir);
240 }
241 comps.push(a);
242 comps.extend(ita.by_ref());
243 break;
244 }
245 }
246 }
247 Some(comps.iter().map(|c| c.as_os_str()).collect())
248 }
249}
250
251pub fn create_dir(dir: impl AsRef<Path>) -> bool {
258 let dir = dir.as_ref();
259 if dir.as_os_str().is_empty() {
260 ebog!("Failed to determine directory to create."); return false;
262 }
263
264 if !dir.exists() {
265 match std::fs::create_dir_all(dir) {
266 Ok(_) => {
267 ibog!("Created directory: {}", dir.display());
268 true
269 }
270 Err(e) => {
271 ebog!("Failed to create {:?}: {e}", dir);
272 false
273 }
274 }
275 } else {
276 true
277 }
278}
279
280pub fn clear_dir(
293 dir: impl AsRef<Path>,
294 filter: impl Fn(&DirEntry) -> bool,
295) -> Result<(), io::Error> {
296 let path = dir.as_ref();
297
298 if !path.exists() {
299 return Ok(());
300 }
301
302 let entries = fs::read_dir(path)?;
303
304 for entry in entries {
305 let entry = entry?;
306 if !filter(&entry) {
307 continue;
308 }
309 let path = entry.path();
310
311 if path.is_dir() {
312 fs::remove_dir_all(&path)?;
313 } else {
314 fs::remove_file(&path)?;
315 }
316 }
317 Ok(())
318}
319
320#[easy_ext::ext(FsPathExt)]
321pub impl<T: AsRef<Path>> T {
322 fn is_empty_dir(&self) -> bool {
324 let path = self.as_ref();
325 fs::read_dir(path)
326 .map(|mut entries| entries.next().is_none())
327 .unwrap_or(false)
328 }
329}
330
331pub fn sort_by_mtime(paths: &mut Vec<PathBuf>) {
333 paths.sort_by(|a, b| {
334 let ma = fs::metadata(a).and_then(|m| m.modified());
335 let mb = fs::metadata(b).and_then(|m| m.modified());
336 match (ma, mb) {
337 (Ok(a), Ok(b)) => b.cmp(&a),
338 (Ok(_), Err(_)) => Ordering::Less,
339 (Err(_), Ok(_)) => Ordering::Greater,
340 (Err(_), Err(_)) => Ordering::Equal,
341 }
342 });
343}