Skip to main content

fungus/sys/
path.rs

1use crate::{
2    core::*,
3    errors::*,
4    sys::{self, user},
5};
6use gory::*;
7use std::{
8    collections::HashMap,
9    ffi::OsStr,
10    fs, io,
11    os::unix::fs::{MetadataExt, PermissionsExt},
12    path::{Component, Path, PathBuf},
13};
14use walkdir::WalkDir;
15
16/// Return the path in an absolute clean form
17///
18/// ### Examples
19/// ```
20/// use fungus::prelude::*;
21///
22/// let home = user::home_dir().unwrap();
23/// assert_eq!(PathBuf::from(&home), sys::abs("~").unwrap());
24/// ```
25pub fn abs<T: AsRef<Path>>(path: T) -> FuResult<PathBuf> {
26    let path = path.as_ref();
27
28    // Check for empty string
29    if path.empty() {
30        return Err(PathError::Empty.into());
31    }
32
33    // Expand home directory
34    let mut path_buf = path.expand()?;
35
36    // Trim protocol prefix if needed
37    path_buf = path_buf.trim_protocol();
38
39    // Clean the resulting path
40    path_buf = path_buf.clean()?;
41
42    // Expand relative directories if needed
43    if !path_buf.is_absolute() {
44        let mut curr = sys::cwd()?;
45        while let Ok(path) = path_buf.first() {
46            match path {
47                Component::CurDir => {
48                    path_buf = path_buf.trim_first();
49                },
50                Component::ParentDir => {
51                    curr = curr.dir()?;
52                    path_buf = path_buf.trim_first();
53                },
54                _ => return Ok(curr.mash(path_buf)),
55            }
56        }
57        return Ok(curr);
58    }
59
60    Ok(path_buf)
61}
62
63/// Returns all directories for the given path recurisely, sorted by filename. Handles path
64/// expansion. Paths are returned as abs paths. Doesn't include the path itself. Paths are
65/// guaranteed to be distinct.
66///
67/// ### Examples
68/// ```
69/// use fungus::prelude::*;
70///
71/// let tmpdir = PathBuf::from("tests/temp").abs().unwrap().mash("path_doc_all_dirs");
72/// assert!(sys::remove_all(&tmpdir).is_ok());
73/// let dir1 = tmpdir.mash("dir1");
74/// let dir2 = dir1.mash("dir2");
75/// assert!(sys::mkdir(&dir2).is_ok());
76/// assert_iter_eq(sys::all_dirs(&tmpdir).unwrap(), vec![dir1, dir2]);
77/// assert!(sys::remove_all(&tmpdir).is_ok());
78/// ```
79pub fn all_dirs<T: AsRef<Path>>(path: T) -> FuResult<Vec<PathBuf>> {
80    let abs = path.as_ref().abs()?;
81    if abs.exists() {
82        let mut paths: Vec<PathBuf> = Vec::new();
83        let mut distinct = HashMap::<PathBuf, bool>::new();
84        if abs.is_dir() {
85            for entry in WalkDir::new(&abs).min_depth(1).follow_links(false).sort_by(|x, y| x.file_name().cmp(y.file_name())) {
86                let path = entry?.into_path();
87
88                // Ensure the path is a directory and distinct
89                if path.is_dir() && !distinct.contains_key(&path) {
90                    distinct.insert(path.clone(), true);
91                    paths.push(path);
92                }
93            }
94            return Ok(paths);
95        }
96        return Err(PathError::is_not_dir(abs).into());
97    }
98    Err(PathError::does_not_exist(abs).into())
99}
100
101/// Returns all files for the given path recursively, sorted by filename. Handles path
102/// expansion. Paths are returned as abs paths. Doesn't include the path itself. Paths are
103/// guaranteed to be distinct.
104///
105/// ### Examples
106/// ```
107/// use fungus::prelude::*;
108///
109/// let tmpdir = PathBuf::from("tests/temp").abs().unwrap().mash("path_doc_all_files");
110/// assert!(sys::remove_all(&tmpdir).is_ok());
111/// let file1 = tmpdir.mash("file1");
112/// let dir1 = tmpdir.mash("dir1");
113/// let file2 = dir1.mash("file2");
114/// assert!(sys::mkdir(&dir1).is_ok());
115/// assert!(sys::touch(&file1).is_ok());
116/// assert!(sys::touch(&file2).is_ok());
117/// assert_iter_eq(sys::all_files(&tmpdir).unwrap(), vec![file2, file1]);
118/// assert!(sys::remove_all(&tmpdir).is_ok());
119/// ```
120pub fn all_files<T: AsRef<Path>>(path: T) -> FuResult<Vec<PathBuf>> {
121    let abs = path.as_ref().abs()?;
122    if abs.exists() {
123        let mut paths: Vec<PathBuf> = Vec::new();
124        let mut distinct = HashMap::<PathBuf, bool>::new();
125        if abs.is_dir() {
126            for entry in WalkDir::new(&abs).min_depth(1).follow_links(false).sort_by(|x, y| x.file_name().cmp(y.file_name())) {
127                let path = entry?.into_path();
128
129                // Ensure the path is a directory and distinct
130                if path.is_file() && !distinct.contains_key(&path) {
131                    distinct.insert(path.clone(), true);
132                    paths.push(path);
133                }
134            }
135            return Ok(paths);
136        }
137        return Err(PathError::is_not_dir(abs).into());
138    }
139    Err(PathError::does_not_exist(abs).into())
140}
141
142/// Returns all paths for the given path recursively, sorted by filename. Handles path
143/// expansion. Paths are returned as abs paths. Doesn't include the path itself. Paths are
144/// guaranteed to be distinct.
145///
146/// ### Examples
147/// ```
148/// use fungus::prelude::*;
149///
150/// let tmpdir = PathBuf::from("tests/temp").abs().unwrap().mash("path_doc_all_paths");
151/// assert!(sys::remove_all(&tmpdir).is_ok());
152/// let file1 = tmpdir.mash("file1");
153/// let dir1 = tmpdir.mash("dir1");
154/// let file2 = dir1.mash("file2");
155/// let file3 = dir1.mash("file3");
156/// assert!(sys::mkdir(&dir1).is_ok());
157/// assert!(sys::touch(&file1).is_ok());
158/// assert!(sys::touch(&file2).is_ok());
159/// assert!(sys::touch(&file3).is_ok());
160/// assert_iter_eq(sys::all_paths(&tmpdir).unwrap(), vec![dir1, file2, file3, file1]);
161/// assert!(sys::remove_all(&tmpdir).is_ok());
162/// ```
163pub fn all_paths<T: AsRef<Path>>(path: T) -> FuResult<Vec<PathBuf>> {
164    let abs = path.as_ref().abs()?;
165    if abs.exists() {
166        let mut paths: Vec<PathBuf> = Vec::new();
167        let mut distinct = HashMap::<PathBuf, bool>::new();
168        if abs.is_dir() {
169            for entry in WalkDir::new(&abs).min_depth(1).follow_links(false).sort_by(|x, y| x.file_name().cmp(y.file_name())) {
170                let path = entry?.into_path();
171
172                // Ensure the path is a directory and distinct
173                if !distinct.contains_key(&path) {
174                    distinct.insert(path.clone(), true);
175                    paths.push(path);
176                }
177            }
178            return Ok(paths);
179        }
180        return Err(PathError::is_not_dir(abs).into());
181    }
182    Err(PathError::does_not_exist(abs).into())
183}
184
185/// Returns all directories for the given path, sorted by filename. Handles path expansion.
186/// Paths are returned as abs paths. Doesn't include the path itself only its children nor
187/// is this recursive.
188///
189/// ### Examples
190/// ```
191/// use fungus::prelude::*;
192///
193/// let tmpdir = PathBuf::from("tests/temp").abs().unwrap().mash("path_doc_dirs");
194/// assert!(sys::remove_all(&tmpdir).is_ok());
195/// let dir1 = tmpdir.mash("dir1");
196/// let dir2 = tmpdir.mash("dir2");
197/// assert!(sys::mkdir(&dir1).is_ok());
198/// assert!(sys::mkdir(&dir2).is_ok());
199/// assert_iter_eq(sys::dirs(&tmpdir).unwrap(), vec![dir1, dir2]);
200/// assert!(sys::remove_all(&tmpdir).is_ok());
201/// ```
202pub fn dirs<T: AsRef<Path>>(path: T) -> FuResult<Vec<PathBuf>> {
203    let abs = path.as_ref().abs()?;
204    if abs.exists() {
205        if abs.is_dir() {
206            let mut paths: Vec<PathBuf> = Vec::new();
207            for entry in fs::read_dir(abs)? {
208                let entry = entry?;
209                let path = entry.path();
210                if path.is_dir() {
211                    paths.push(path.abs()?);
212                }
213            }
214            paths.sort();
215            return Ok(paths);
216        }
217        return Err(PathError::is_not_dir(abs).into());
218    }
219    Err(PathError::does_not_exist(abs).into())
220}
221
222/// Returns true if the given path exists. Handles path expansion.
223///
224/// ### Examples
225/// ```
226/// use fungus::prelude::*;
227///
228/// assert_eq!(sys::exists("/etc"), true);
229/// ```
230pub fn exists<T: AsRef<Path>>(path: T) -> bool {
231    metadata(path).is_ok()
232}
233
234/// Expand all environment variables in the path as well as the home directory.
235///
236/// WARNING: Does not expand partials e.g. "/foo${BAR}ing/blah" only complete components
237/// e.g. "/foo/${BAR}/blah"
238///
239/// ### Examples
240/// ```
241/// use fungus::prelude::*;
242///
243/// let home = user::home_dir().unwrap();
244/// assert_eq!(PathBuf::from(&home).mash("foo"), PathBuf::from("~/foo").expand().unwrap());
245/// ```
246pub fn expand<T: AsRef<Path>>(path: T) -> FuResult<PathBuf> {
247    let mut path = path.as_ref().to_path_buf();
248    let pathstr = path.to_string()?;
249
250    // Expand home directory
251    match pathstr.matches('~').count() {
252        // Only home expansion at the begining of the path is allowed
253        cnt if cnt > 1 => return Err(PathError::multiple_home_symbols(path).into()),
254
255        // Invalid home expansion requested
256        cnt if cnt == 1 && !path.has_prefix("~/") && pathstr != "~" => {
257            return Err(PathError::invalid_expansion(path).into());
258        },
259
260        // Single tilda only
261        cnt if cnt == 1 && pathstr == "~" => {
262            path = user::home_dir()?;
263        },
264
265        // Replace prefix with home directory
266        1 => path = user::home_dir()?.mash(&pathstr[2..]),
267        _ => {},
268    }
269
270    // Expand other variables that may exist in the path
271    let pathstr = path.to_string()?;
272    if pathstr.matches('$').some() {
273        let mut path_buf = PathBuf::new();
274        for x in path.components() {
275            match x {
276                Component::Normal(y) => {
277                    let seg = y.to_string()?;
278                    if let Some(chunk) = seg.strip_prefix("${") {
279                        if let Some(key) = chunk.strip_suffix("}") {
280                            let var = sys::var(key)?;
281                            path_buf.push(var);
282                        } else {
283                            return Err(PathError::invalid_expansion(seg).into());
284                        }
285                    } else if let Some(key) = seg.strip_prefix('$') {
286                        let var = sys::var(key)?;
287                        path_buf.push(var);
288                    } else {
289                        path_buf.push(seg);
290                    }
291                },
292                _ => path_buf.push(x),
293            }
294        }
295        path = path_buf;
296    }
297
298    Ok(path)
299}
300
301/// Returns all files for the given path, sorted by filename. Handles path expansion.
302/// Paths are returned as abs paths. Doesn't include the path itself only its children nor
303/// is this recursive.
304///
305/// ### Examples
306/// ```
307/// use fungus::prelude::*;
308///
309/// let tmpdir = PathBuf::from("tests/temp").abs().unwrap().mash("path_doc_files");
310/// assert!(sys::remove_all(&tmpdir).is_ok());
311/// let file1 = tmpdir.mash("file1");
312/// let file2 = tmpdir.mash("file2");
313/// assert!(sys::mkdir(&tmpdir).is_ok());
314/// assert!(sys::touch(&file1).is_ok());
315/// assert!(sys::touch(&file2).is_ok());
316/// assert_iter_eq(sys::files(&tmpdir).unwrap(), vec![file1, file2]);
317/// assert!(sys::remove_all(&tmpdir).is_ok());
318/// ```
319pub fn files<T: AsRef<Path>>(path: T) -> FuResult<Vec<PathBuf>> {
320    let abs = path.as_ref().abs()?;
321    if abs.exists() {
322        if abs.is_dir() {
323            let mut paths: Vec<PathBuf> = Vec::new();
324            for entry in fs::read_dir(abs)? {
325                let entry = entry?;
326                let path = entry.path();
327                if path.is_file() {
328                    paths.push(path.abs()?);
329                }
330            }
331            paths.sort();
332            return Ok(paths);
333        }
334        return Err(PathError::is_not_dir(abs).into());
335    }
336    Err(PathError::does_not_exist(abs).into())
337}
338
339/// Returns true if the given path exists and is a directory. Handles path expansion.
340///
341/// ### Examples
342/// ```
343/// use fungus::prelude::*;
344///
345/// assert_eq!(sys::is_dir("/etc"), true);
346/// ```
347pub fn is_dir<T: AsRef<Path>>(path: T) -> bool {
348    match metadata(path) {
349        Ok(x) => x.is_dir(),
350        Err(_) => false,
351    }
352}
353
354/// Returns true if the given path exists and is an executable. Handles path expansion
355///
356/// ### Examples
357/// ```
358/// use fungus::prelude::*;
359///
360/// let tmpdir = PathBuf::from("tests/temp").abs().unwrap().mash("path_doc_is_exec");
361/// assert!(sys::remove_all(&tmpdir).is_ok());
362/// assert!(sys::mkdir(&tmpdir).is_ok());
363/// let file1 = tmpdir.mash("file1");
364/// assert!(sys::touch_p(&file1, 0o644).is_ok());
365/// assert_eq!(sys::is_exec(&file1), false);
366/// assert!(sys::chmod_p(&file1).unwrap().add_x().chmod().is_ok());
367/// assert_eq!(file1.mode().unwrap(), 0o100755);
368/// assert_eq!(file1.is_exec(), true);
369/// assert!(sys::remove_all(&tmpdir).is_ok());
370/// ```
371pub fn is_exec<T: AsRef<Path>>(path: T) -> bool {
372    match metadata(path) {
373        Ok(x) => x.permissions().mode() & 0o111 != 0,
374        Err(_) => false,
375    }
376}
377
378/// Returns true if the given path exists and is a file. Handles path expansion
379///
380/// ### Examples
381/// ```
382/// use fungus::prelude::*;
383///
384/// assert_eq!(sys::is_file("/etc/hosts"), true);
385/// ```
386pub fn is_file<T: AsRef<Path>>(path: T) -> bool {
387    match metadata(path) {
388        Ok(x) => x.is_file(),
389        Err(_) => false,
390    }
391}
392
393/// Returns true if the given path exists and is readonly. Handles path expansion
394///
395/// ### Examples
396/// ```
397/// use fungus::prelude::*;
398///
399/// let tmpdir = PathBuf::from("tests/temp").abs().unwrap().mash("path_doc_is_readonly");
400/// assert!(sys::remove_all(&tmpdir).is_ok());
401/// assert!(sys::mkdir(&tmpdir).is_ok());
402/// let file1 = tmpdir.mash("file1");
403/// assert!(sys::touch_p(&file1, 0o644).is_ok());
404/// assert_eq!(file1.is_readonly(), false);
405/// assert!(sys::chmod_p(&file1).unwrap().readonly().chmod().is_ok());
406/// assert_eq!(file1.mode().unwrap(), 0o100444);
407/// assert_eq!(sys::is_readonly(&file1), true);
408/// assert!(sys::remove_all(&tmpdir).is_ok());
409/// ```
410pub fn is_readonly<T: AsRef<Path>>(path: T) -> bool {
411    match metadata(path) {
412        Ok(x) => x.permissions().readonly(),
413        Err(_) => false,
414    }
415}
416
417/// Returns true if the given path exists and is a symlink. Handles path expansion
418///
419/// ### Examples
420/// ```
421/// use fungus::prelude::*;
422///
423/// let tmpdir = PathBuf::from("tests/temp").abs().unwrap().mash("path_doc_is_symlink");
424/// assert!(sys::remove_all(&tmpdir).is_ok());
425/// let file1 = tmpdir.mash("file1");
426/// let link1 = tmpdir.mash("link1");
427/// assert!(sys::mkdir(&tmpdir).is_ok());
428/// assert!(sys::touch(&file1).is_ok());
429/// assert!(sys::symlink(&link1, &file1).is_ok());
430/// assert_eq!(sys::is_symlink(link1), true);
431/// assert!(sys::remove_all(&tmpdir).is_ok());
432/// ```
433pub fn is_symlink<T: AsRef<Path>>(path: T) -> bool {
434    match path.as_ref().abs() {
435        Ok(abs) => readlink(abs).is_ok(),
436        Err(_) => false,
437    }
438}
439
440/// Returns true if the given path exists and is a symlinked directory. Handles path
441/// expansion
442///
443/// ### Examples
444/// ```
445/// use fungus::prelude::*;
446///
447/// let tmpdir = PathBuf::from("tests/temp").abs().unwrap().mash("path_doc_is_symlink_dir");
448/// assert!(sys::remove_all(&tmpdir).is_ok());
449/// let dir1 = tmpdir.mash("dir1");
450/// let link1 = tmpdir.mash("link1");
451/// assert!(sys::mkdir(&dir1).is_ok());
452/// assert!(sys::symlink(&link1, &dir1).is_ok());
453/// assert_eq!(sys::is_symlink_dir(link1), true);
454/// assert!(sys::remove_all(&tmpdir).is_ok());
455/// ```
456pub fn is_symlink_dir<T: AsRef<Path>>(path: T) -> bool {
457    match path.as_ref().abs() {
458        Ok(abs) => match readlink(&abs) {
459            Ok(target) => match target.abs_from(&abs) {
460                Ok(x) => x.is_dir(),
461                Err(_) => false,
462            },
463            Err(_) => false,
464        },
465        Err(_) => false,
466    }
467}
468
469/// Returns true if the given path exists and is a symlinked file. Handles path
470/// expansion
471///
472/// ### Examples
473/// ```
474/// use fungus::prelude::*;
475///
476/// let tmpdir = PathBuf::from("tests/temp").abs().unwrap().mash("path_doc_is_symlink_file");
477/// assert!(sys::remove_all(&tmpdir).is_ok());
478/// let file1 = tmpdir.mash("file1");
479/// let link1 = tmpdir.mash("link1");
480/// assert!(sys::mkdir(&tmpdir).is_ok());
481/// assert!(sys::touch(&file1).is_ok());
482/// assert!(sys::symlink(&link1, &file1).is_ok());
483/// assert_eq!(sys::is_symlink_file(link1), true);
484/// assert!(sys::remove_all(&tmpdir).is_ok());
485/// ```
486pub fn is_symlink_file<T: AsRef<Path>>(path: T) -> bool {
487    match path.as_ref().abs() {
488        Ok(abs) => match readlink(&abs) {
489            Ok(target) => match target.abs_from(&abs) {
490                Ok(x) => x.is_file(),
491                Err(_) => false,
492            },
493            Err(_) => false,
494        },
495        Err(_) => false,
496    }
497}
498
499/// Returns the group ID of the owner of this file. Handles path expansion.
500///
501/// ### Examples
502/// ```
503/// use fungus::prelude::*;
504///
505/// assert_eq!(sys::gid("/etc").unwrap(), 0);
506/// ```
507pub fn gid<T: AsRef<Path>>(path: T) -> FuResult<u32> {
508    Ok(metadata(path)?.gid())
509}
510
511/// Returns a vector of all paths from the given target glob with path expansion and sorted by
512/// name. Doesn't include the target itself only its children nor is this recursive.
513///
514/// ### Examples
515/// ```
516/// use fungus::prelude::*;
517///
518/// let tmpdir = PathBuf::from("tests/temp").abs().unwrap().mash("path_doc_glob");
519/// assert!(sys::remove_all(&tmpdir).is_ok());
520/// let dir1 = tmpdir.mash("dir1");
521/// let dir2 = tmpdir.mash("dir2");
522/// let file1 = tmpdir.mash("file1");
523/// assert!(sys::mkdir(&dir1).is_ok());
524/// assert!(sys::mkdir(&dir2).is_ok());
525/// assert!(sys::touch(&file1).is_ok());
526/// assert_iter_eq(sys::glob(tmpdir.mash("*")).unwrap(), vec![dir1, dir2, file1]);
527/// assert!(sys::remove_all(&tmpdir).is_ok());
528/// ```
529pub fn glob<T: AsRef<Path>>(src: T) -> FuResult<Vec<PathBuf>> {
530    let abs = src.as_ref().abs()?.to_string()?;
531    let mut paths: Vec<PathBuf> = Vec::new();
532    for x in glob::glob(&abs)? {
533        paths.push(x.map_err(|err| io::Error::new(io::ErrorKind::Other, format!("glob failure: {}", err.to_string())))?.abs()?);
534    }
535    Ok(paths)
536}
537
538/// Returns a new owned [`PathBuf`] from `dir` mashed together with `base`.
539/// Differs from the `join` implementation as `mash` drops root prefix of the given `path` if
540/// it exists and also drops any trailing '/' on the new resulting path. More closely aligns
541/// with the Golang implementation of join.
542///
543/// ### Examples
544/// ```
545/// use fungus::prelude::*;
546///
547/// assert_eq!(sys::mash("/foo", "/bar"), PathBuf::from("/foo/bar"));
548/// ```
549pub fn mash<T: AsRef<Path>, U: AsRef<Path>>(dir: T, base: U) -> PathBuf {
550    dir.as_ref().join(base.as_ref().trim_prefix("/")).components().collect::<PathBuf>()
551}
552
553/// Returns the Metadata object for the `Path` if it exists else an error. Handles path
554/// expansion.
555///
556/// ### Examples
557/// ```
558/// use fungus::prelude::*;
559///
560/// let meta = sys::metadata(Path::new("/etc")).unwrap();
561/// assert_eq!(meta.is_dir(), true);
562/// ```
563pub fn metadata<T: AsRef<Path>>(path: T) -> FuResult<fs::Metadata> {
564    let abs = path.as_ref().abs()?;
565    let meta = fs::metadata(abs)?;
566    Ok(meta)
567}
568
569/// Parse unix shell pathing e.g. $PATH, $XDG_DATA_DIRS or $XDG_CONFIG_DIRS.
570/// List of directories seperated by :
571///
572/// ### Examples
573/// ```
574/// use fungus::prelude::*;
575///
576/// let paths = vec![PathBuf::from("/foo1"), PathBuf::from("/foo2/bar")];
577/// assert_iter_eq(sys::parse_paths("/foo1:/foo2/bar").unwrap(), paths);
578/// ```
579pub fn parse_paths<T: AsRef<str>>(value: T) -> FuResult<Vec<PathBuf>> {
580    let mut paths: Vec<PathBuf> = Vec::new();
581    for dir in value.as_ref().split(':') {
582        // Unix shell semantics: path element "" means "."
583        let path = match dir == "" {
584            true => sys::cwd()?,
585            false => PathBuf::from(dir),
586        };
587        paths.push(path);
588    }
589    Ok(paths)
590}
591
592/// Returns all directories/files for the given path, sorted by filename. Handles path
593/// expansion. Paths are returned as abs paths. Doesn't include the path itself only
594/// its children nor is this recursive.
595///
596/// ### Examples
597/// ```
598/// use fungus::prelude::*;
599///
600/// let tmpdir = PathBuf::from("tests/temp").abs().unwrap().mash("path_doc_paths");
601/// assert!(sys::remove_all(&tmpdir).is_ok());
602/// let dir1 = tmpdir.mash("dir1");
603/// let dir2 = tmpdir.mash("dir2");
604/// let file1 = tmpdir.mash("file1");
605/// assert!(sys::mkdir(&dir1).is_ok());
606/// assert!(sys::mkdir(&dir2).is_ok());
607/// assert!(sys::touch(&file1).is_ok());
608/// assert_iter_eq(sys::paths(&tmpdir).unwrap(), vec![dir1, dir2, file1]);
609/// assert!(sys::remove_all(&tmpdir).is_ok());
610/// ```
611pub fn paths<T: AsRef<Path>>(path: T) -> FuResult<Vec<PathBuf>> {
612    let abs = path.as_ref().abs()?;
613    if abs.exists() {
614        if abs.is_dir() {
615            let mut paths: Vec<PathBuf> = Vec::new();
616            for entry in fs::read_dir(abs)? {
617                let entry = entry?;
618                let path = entry.path();
619                paths.push(path.abs()?);
620            }
621            paths.sort();
622            return Ok(paths);
623        }
624        return Err(PathError::is_not_dir(abs).into());
625    }
626    Err(PathError::does_not_exist(abs).into())
627}
628
629/// Returns the absolute path for the given link target. Handles path expansion
630///
631/// ### Examples
632/// ```
633/// use fungus::prelude::*;
634///
635/// let tmpdir = PathBuf::from("tests/temp").abs().unwrap().mash("path_doc_readlink");
636/// assert!(sys::remove_all(&tmpdir).is_ok());
637/// let file1 = tmpdir.mash("file1");
638/// let link1 = tmpdir.mash("link1");
639/// assert!(sys::mkdir(&tmpdir).is_ok());
640/// assert!(sys::touch(&file1).is_ok());
641/// assert!(sys::symlink(&link1, &file1).is_ok());
642/// assert_eq!(sys::readlink(link1).unwrap(), file1);
643/// assert!(sys::remove_all(&tmpdir).is_ok());
644/// ```
645pub fn readlink<T: AsRef<Path>>(path: T) -> FuResult<PathBuf> {
646    let abs = path.as_ref().abs()?;
647    let abs = fs::read_link(abs)?;
648    Ok(abs)
649}
650
651/// Return the current working path trimmed back to the relative dir
652///
653/// ### Examples
654/// ```
655/// use fungus::prelude::*;
656///
657/// assert_eq!(sys::rel_to("home").unwrap(), PathBuf::from("/home"));
658/// ```
659pub fn rel_to(dir: &str) -> FuResult<PathBuf> {
660    let cwd = sys::cwd()?;
661
662    // Expand path
663    let mut path = cwd.expand()?;
664
665    // Check for empty string
666    if dir.is_empty() {
667        return Ok(path);
668    }
669
670    let target = OsStr::new(dir);
671    while path.last()? != Component::Normal(&target) {
672        path = path.trim_last();
673    }
674
675    Ok(path)
676}
677
678/// Returns the user ID of the owner of this file. Handles path expansion.
679///
680/// ### Examples
681/// ```
682/// use fungus::prelude::*;
683///
684/// assert_eq!(sys::uid("/etc").unwrap(), 0);
685/// ```
686pub fn uid<T: AsRef<Path>>(path: T) -> FuResult<u32> {
687    Ok(metadata(path)?.uid())
688}
689
690// Path extensions
691// -------------------------------------------------------------------------------------------------
692pub trait PathExt {
693    /// Return the path in an absolute clean form
694    ///
695    /// ### Examples
696    /// ```
697    /// use fungus::prelude::*;
698    ///
699    /// let home = user::home_dir().unwrap();
700    /// assert_eq!(PathBuf::from(&home), sys::abs("~").unwrap());
701    /// ```
702    fn abs(&self) -> FuResult<PathBuf>;
703
704    /// Returns a new absolute [`PathBuf`] based on the given absolute `Path`. The last element of
705    /// the given path will be assumed to be a file name.
706    ///
707    /// ### Examples
708    /// ```
709    /// use fungus::prelude::*;
710    ///
711    /// let home = PathBuf::from("~").abs().unwrap();
712    /// assert_eq!(PathBuf::from("foo2").abs_from(home.mash("foo1").abs().unwrap()).unwrap(), home.mash("foo2"));
713    /// ```
714    fn abs_from<T: AsRef<Path>>(&self, path: T) -> FuResult<PathBuf>;
715
716    /// Returns the final component of the `Path`, if there is one.
717    ///
718    /// ### Examples
719    /// ```
720    /// use fungus::prelude::*;
721    ///
722    /// assert_eq!("bar", PathBuf::from("/foo/bar").base().unwrap());
723    /// ```
724    fn base(&self) -> FuResult<String>;
725
726    /// Set the given mode for the `Path` and return the `Path`
727    ///
728    /// ### Examples
729    /// ```
730    /// use fungus::prelude::*;
731    ///
732    /// let tmpdir = PathBuf::from("tests/temp").abs().unwrap().mash("pathbuf_doc_chmod");
733    /// assert!(sys::remove_all(&tmpdir).is_ok());
734    /// let file1 = tmpdir.mash("file1");
735    /// assert!(sys::mkdir(&tmpdir).is_ok());
736    /// assert!(sys::touch(&file1).is_ok());
737    /// assert!(file1.chmod(0o644).is_ok());
738    /// assert_eq!(file1.mode().unwrap(), 0o100644);
739    /// assert!(file1.chmod(0o555).is_ok());
740    /// assert_eq!(file1.mode().unwrap(), 0o100555);
741    /// assert!(sys::remove_all(&tmpdir).is_ok());
742    /// ```
743    fn chmod(&self, mode: u32) -> FuResult<()>;
744
745    /// Return the shortest path equivalent to the path by purely lexical processing and thus does
746    /// not handle links correctly in some cases, use canonicalize in those cases. It applies
747    /// the following rules interatively until no further processing can be done.
748    ///
749    /// 1. Replace multiple slashes with a single
750    /// 2. Eliminate each . path name element (the current directory)
751    /// 3. Eliminate each inner .. path name element (the parent directory)
752    ///    along with the non-.. element that precedes it.
753    /// 4. Eliminate .. elements that begin a rooted path:
754    ///    that is, replace "/.." by "/" at the beginning of a path.
755    /// 5. Leave intact ".." elements that begin a non-rooted path.
756    /// 6. Drop trailing '/' unless it is the root
757    ///
758    /// If the result of this process is an empty string, return the string `.`, representing the
759    /// current directory.
760    fn clean(&self) -> FuResult<PathBuf>;
761
762    /// Returns the `Path` with the given string concatenated on.
763    ///
764    /// ### Examples
765    /// ```
766    /// use fungus::prelude::*;
767    ///
768    /// assert_eq!(Path::new("/foo/bar").concat(".rs").unwrap(), PathBuf::from("/foo/bar.rs"));
769    /// ```
770    fn concat<T: AsRef<str>>(&self, val: T) -> FuResult<PathBuf>;
771
772    /// Returns the `Path` without its final component, if there is one.
773    ///
774    /// ### Examples
775    /// ```
776    /// use fungus::prelude::*;
777    ///
778    /// let dir = PathBuf::from("/foo/bar").dir().unwrap();
779    /// assert_eq!(PathBuf::from("/foo").as_path(), dir);
780    /// ```
781    fn dir(&self) -> FuResult<PathBuf>;
782
783    /// Returns true if the `Path` is empty.
784    ///
785    /// ### Examples
786    /// ```
787    /// use fungus::prelude::*;
788    ///
789    /// assert_eq!(PathBuf::from("").empty(), true);
790    /// ```
791    fn empty(&self) -> bool;
792
793    /// Returns true if the `Path` exists. Handles path expansion.
794    ///
795    /// ### Examples
796    /// ```
797    /// use fungus::prelude::*;
798    ///
799    /// assert_eq!(Path::new("/etc").exists(), true);
800    /// ```
801    fn exists(&self) -> bool;
802
803    /// Expand the path to include the home prefix if necessary
804    ///
805    /// ### Examples
806    /// ```
807    /// use fungus::prelude::*;
808    ///
809    /// let home = user::home_dir().unwrap();
810    /// assert_eq!(PathBuf::from(&home).mash("foo"), PathBuf::from("~/foo").expand().unwrap());
811    /// ```
812    fn expand(&self) -> FuResult<PathBuf>;
813
814    /// Returns the extension of the path or an error.
815    ///
816    /// ### Examples
817    /// ```
818    /// use fungus::prelude::*;
819    ///
820    /// assert_eq!(Path::new("foo.bar").ext().unwrap(), "bar");
821    /// ```
822    fn ext(&self) -> FuResult<String>;
823
824    /// Returns the first path component.
825    ///
826    /// ### Examples
827    /// ```
828    /// use fungus::prelude::*;
829    /// use std::path::Component;
830    ///
831    /// let first = Component::Normal(OsStr::new("foo"));
832    /// assert_eq!(PathBuf::from("foo/bar").first().unwrap(), first);
833    /// ```
834    fn first(&self) -> FuResult<Component>;
835
836    /// Returns the group ID of the owner of this file.
837    ///
838    /// ### Examples
839    /// ```
840    /// use fungus::prelude::*;
841    ///
842    /// assert_eq!(Path::new("/etc").gid().unwrap(), 0);
843    /// ```
844    fn gid(&self) -> FuResult<u32>;
845
846    /// Returns true if the `Path` contains the given path or string.
847    ///
848    /// ### Examples
849    /// ```
850    /// use fungus::prelude::*;
851    ///
852    /// let path = PathBuf::from("/foo/bar");
853    /// assert_eq!(path.has("foo"), true);
854    /// assert_eq!(path.has("/foo"), true);
855    /// ```
856    fn has<T: AsRef<Path>>(&self, path: T) -> bool;
857
858    /// Returns true if the `Path` as a String has the given prefix
859    ///
860    /// ### Examples
861    /// ```
862    /// use fungus::prelude::*;
863    ///
864    /// let path = PathBuf::from("/foo/bar");
865    /// assert_eq!(path.has_prefix("/foo"), true);
866    /// assert_eq!(path.has_prefix("foo"), false);
867    /// ```
868    fn has_prefix<T: AsRef<Path>>(&self, prefix: T) -> bool;
869
870    /// Returns true if the `Path` as a String has the given suffix
871    ///
872    /// ### Examples
873    /// ```
874    /// use fungus::prelude::*;
875    ///
876    /// let path = PathBuf::from("/foo/bar");
877    /// assert_eq!(path.has_suffix("/bar"), true);
878    /// assert_eq!(path.has_suffix("foo"), false);
879    /// ```
880    fn has_suffix<T: AsRef<Path>>(&self, suffix: T) -> bool;
881
882    /// Returns true if the `Path` exists and is a directory. Handles path expansion.
883    ///
884    /// ### Examples
885    /// ```
886    /// use fungus::prelude::*;
887    ///
888    /// assert_eq!(Path::new("/etc").is_dir(), true);
889    /// ```
890    fn is_dir(&self) -> bool;
891
892    /// Returns true if the `Path` exists and is an executable. Handles path expansion.
893    ///
894    /// ### Examples
895    /// ```
896    /// use fungus::prelude::*;
897    ///
898    /// let tmpdir = PathBuf::from("tests/temp").abs().unwrap().mash("doc_is_exec");
899    /// assert!(sys::remove_all(&tmpdir).is_ok());
900    /// assert!(sys::mkdir(&tmpdir).is_ok());
901    /// let file1 = tmpdir.mash("file1");
902    /// assert!(sys::touch_p(&file1, 0o644).is_ok());
903    /// assert_eq!(file1.is_exec(), false);
904    /// assert!(sys::chmod_p(&file1).unwrap().add_x().chmod().is_ok());
905    /// assert_eq!(file1.mode().unwrap(), 0o100755);
906    /// assert_eq!(file1.is_exec(), true);
907    /// assert!(sys::remove_all(&tmpdir).is_ok());
908    /// ```
909    fn is_exec(&self) -> bool;
910
911    /// Returns true if the `Path` exists and is a file. Handles path expansion
912    ///
913    /// ### Examples
914    /// ```
915    /// use fungus::prelude::*;
916    ///
917    /// assert_eq!(Path::new("/etc/hosts").is_file(), true);
918    /// ```
919    fn is_file(&self) -> bool;
920
921    /// Returns true if the `Path` exists and is readonly. Handles path expansion.
922    ///
923    /// ### Examples
924    /// ```
925    /// use fungus::prelude::*;
926    ///
927    /// let tmpdir = PathBuf::from("tests/temp").abs().unwrap().mash("doc_is_readonly");
928    /// assert!(sys::remove_all(&tmpdir).is_ok());
929    /// assert!(sys::mkdir(&tmpdir).is_ok());
930    /// let file1 = tmpdir.mash("file1");
931    /// assert!(sys::touch_p(&file1, 0o644).is_ok());
932    /// assert_eq!(file1.is_readonly(), false);
933    /// assert!(sys::chmod_p(&file1).unwrap().readonly().chmod().is_ok());
934    /// assert_eq!(file1.mode().unwrap(), 0o100444);
935    /// assert_eq!(file1.is_readonly(), true);
936    /// assert!(sys::remove_all(&tmpdir).is_ok());
937    /// ```
938    fn is_readonly(&self) -> bool;
939
940    /// Returns true if the `Path` exists and is a symlink. Handles path expansion
941    ///
942    /// ### Examples
943    /// ```
944    /// use fungus::prelude::*;
945    ///
946    /// let tmpdir = PathBuf::from("tests/temp").abs().unwrap().mash("pathbuf_doc_is_symlink");
947    /// assert!(sys::remove_all(&tmpdir).is_ok());
948    /// let file1 = tmpdir.mash("file1");
949    /// let link1 = tmpdir.mash("link1");
950    /// assert!(sys::mkdir(&tmpdir).is_ok());
951    /// assert!(sys::touch(&file1).is_ok());
952    /// assert!(sys::symlink(&link1, &file1).is_ok());
953    /// assert_eq!(link1.is_symlink(), true);
954    /// assert!(sys::remove_all(&tmpdir).is_ok());
955    /// ```
956    fn is_symlink(&self) -> bool;
957
958    /// Returns true if the `Path` exists and is a symlinked directory. Handles path expansion
959    ///
960    /// ### Examples
961    /// ```
962    /// use fungus::prelude::*;
963    ///
964    /// let tmpdir = PathBuf::from("tests/temp").abs().unwrap().mash("pathbuf_doc_is_symlink_dir");
965    /// assert!(sys::remove_all(&tmpdir).is_ok());
966    /// let dir1 = tmpdir.mash("dir1");
967    /// let link1 = tmpdir.mash("link1");
968    /// assert!(sys::mkdir(&dir1).is_ok());
969    /// assert!(sys::symlink(&link1, &dir1).is_ok());
970    /// assert_eq!(link1.is_symlink_dir(), true);
971    /// assert!(sys::remove_all(&tmpdir).is_ok());
972    /// ```
973    fn is_symlink_dir(&self) -> bool;
974
975    /// Returns true if the given `Path` exists and is a symlinked file. Handles path
976    /// expansion
977    ///
978    /// ### Examples
979    /// ```
980    /// use fungus::prelude::*;
981    ///
982    /// let tmpdir = PathBuf::from("tests/temp").abs().unwrap().mash("pathbuf_doc_is_symlink_file");
983    /// assert!(sys::remove_all(&tmpdir).is_ok());
984    /// let file1 = tmpdir.mash("file1");
985    /// let link1 = tmpdir.mash("link1");
986    /// assert!(sys::mkdir(&tmpdir).is_ok());
987    /// assert!(sys::touch(&file1).is_ok());
988    /// assert!(sys::symlink(&link1, &file1).is_ok());
989    /// assert_eq!(link1.is_symlink_file(), true);
990    /// assert!(sys::remove_all(&tmpdir).is_ok());
991    /// ```
992    fn is_symlink_file(&self) -> bool;
993
994    /// Returns the last path component.
995    ///
996    /// ### Examples
997    /// ```
998    /// use fungus::prelude::*;
999    /// use std::path::Component;
1000    ///
1001    /// let first = Component::Normal(OsStr::new("bar"));
1002    /// assert_eq!(PathBuf::from("foo/bar").last().unwrap(), first);
1003    /// ```
1004    fn last(&self) -> FuResult<Component>;
1005
1006    /// Returns a new owned [`PathBuf`] from `self` mashed together with `path`.
1007    /// Differs from the `mash` implementation as `mash` drops root prefix of the given `path` if
1008    /// it exists and also drops any trailing '/' on the new resulting path. More closely aligns
1009    /// with the Golang implementation of join.
1010    ///
1011    /// ### Examples
1012    /// ```
1013    /// use fungus::prelude::*;
1014    ///
1015    /// assert_eq!(Path::new("/foo").mash("/bar"), PathBuf::from("/foo/bar"));
1016    /// ```
1017    fn mash<T: AsRef<Path>>(&self, path: T) -> PathBuf;
1018
1019    /// Returns the Metadata object for the `Path` if it exists else and error
1020    ///
1021    /// ### Examples
1022    /// ```
1023    /// use fungus::prelude::*;
1024    ///
1025    /// let meta = Path::new("/etc").metadata().unwrap();
1026    /// assert_eq!(meta.is_dir(), true);
1027    /// ```
1028    fn metadata(&self) -> FuResult<fs::Metadata>;
1029
1030    /// Returns the Metadata object for the `Path` if it exists else and error
1031    ///
1032    /// ### Examples
1033    /// ```
1034    /// use fungus::prelude::*;
1035    ///
1036    /// let tmpdir = PathBuf::from("tests/temp").abs().unwrap().mash("pathbuf_doc_mode");
1037    /// assert!(sys::remove_all(&tmpdir).is_ok());
1038    /// let file1 = tmpdir.mash("file1");
1039    /// assert!(sys::mkdir(&tmpdir).is_ok());
1040    /// assert!(sys::touch(&file1).is_ok());
1041    /// assert!(file1.chmod(0o644).is_ok());
1042    /// assert_eq!(file1.mode().unwrap(), 0o100644);
1043    /// assert!(sys::remove_all(&tmpdir).is_ok());
1044    /// ```
1045    fn mode(&self) -> FuResult<u32>;
1046
1047    /// Returns the final component of the `Path` without an extension if there is one
1048    ///
1049    /// ### Examples
1050    /// ```
1051    /// use fungus::prelude::*;
1052    ///
1053    /// assert_eq!(PathBuf::from("/foo/bar.foo").name().unwrap(), "bar");
1054    /// ```
1055    fn name(&self) -> FuResult<String>;
1056
1057    /// Return the permissions for the `Path`
1058    ///
1059    /// ### Examples
1060    /// ```
1061    /// use fungus::prelude::*;
1062    ///
1063    /// let tmpdir = PathBuf::from("tests/temp").abs().unwrap().mash("pathbuf_doc_perms");
1064    /// assert!(sys::remove_all(&tmpdir).is_ok());
1065    /// let file1 = tmpdir.mash("file1");
1066    /// assert!(sys::mkdir(&tmpdir).is_ok());
1067    /// assert!(sys::touch(&file1).is_ok());
1068    /// assert!(file1.chmod(0o644).is_ok());
1069    /// assert_eq!(file1.perms().unwrap().mode(), 0o100644);
1070    /// assert!(sys::remove_all(&tmpdir).is_ok());
1071    /// ```
1072    fn perms(&self) -> FuResult<fs::Permissions>;
1073
1074    /// Returns the absolute path for the link target. Handles path expansion
1075    ///
1076    /// ### Examples
1077    /// ```
1078    /// use fungus::prelude::*;
1079    ///
1080    /// let tmpdir = PathBuf::from("tests/temp").abs().unwrap().mash("pathbuf_doc_readlink");
1081    /// assert!(sys::remove_all(&tmpdir).is_ok());
1082    /// let file1 = tmpdir.mash("file1");
1083    /// let link1 = tmpdir.mash("link1");
1084    /// assert!(sys::mkdir(&tmpdir).is_ok());
1085    /// assert!(sys::touch(&file1).is_ok());
1086    /// assert!(sys::symlink(&link1, &file1).is_ok());
1087    /// assert_eq!(link1.readlink().unwrap(), file1);
1088    /// assert!(sys::remove_all(&tmpdir).is_ok());
1089    /// ```
1090    fn readlink(&self) -> FuResult<PathBuf>;
1091
1092    /// Returns the `Path` relative to the given `Path`
1093    ///
1094    /// ### Examples
1095    /// ```
1096    /// use fungus::prelude::*;
1097    ///
1098    /// assert_eq!(PathBuf::from("foo/bar1").relative_from("foo/bar2").unwrap(), PathBuf::from("bar1"));
1099    /// ```
1100    fn relative_from<T: AsRef<Path>>(&self, path: T) -> FuResult<PathBuf>;
1101
1102    /// Set the given [`Permissions`] on the `Path` and return the `Path`
1103    ///
1104    /// ### Examples
1105    /// ```
1106    /// use fungus::prelude::*;
1107    ///
1108    /// let tmpdir = PathBuf::from("tests/temp").abs().unwrap().mash("pathbuf_doc_setperms");
1109    /// assert!(sys::remove_all(&tmpdir).is_ok());
1110    /// let file1 = tmpdir.mash("file1");
1111    /// assert!(sys::mkdir(&tmpdir).is_ok());
1112    /// assert!(sys::touch(&file1).is_ok());
1113    /// assert!(file1.chmod(0o644).is_ok());
1114    /// assert_eq!(file1.perms().unwrap().mode(), 0o100644);
1115    /// assert!(file1.setperms(fs::Permissions::from_mode(0o555)).is_ok());
1116    /// assert_eq!(file1.perms().unwrap().mode(), 0o100555);
1117    /// assert!(sys::remove_all(&tmpdir).is_ok());
1118    /// ```
1119    fn setperms(&self, perms: fs::Permissions) -> FuResult<PathBuf>;
1120
1121    /// Returns a new [`PathBuf`] with the file extension trimmed off.
1122    ///
1123    /// ### Examples
1124    /// ```
1125    /// use fungus::prelude::*;
1126    ///
1127    /// assert_eq!(Path::new("foo.exe").trim_ext().unwrap(), PathBuf::from("foo"));
1128    /// ```
1129    fn trim_ext(&self) -> FuResult<PathBuf>;
1130
1131    /// Returns a new [`PathBuf`] with first [`Component`] trimmed off.
1132    ///
1133    /// ### Examples
1134    /// ```
1135    /// use fungus::prelude::*;
1136    ///
1137    /// assert_eq!(PathBuf::from("/foo").trim_first(), PathBuf::from("foo"));
1138    /// ```
1139    fn trim_first(&self) -> PathBuf;
1140
1141    /// Returns a new [`PathBuf`] with last [`Component`] trimmed off.
1142    ///
1143    /// ### Examples
1144    /// ```
1145    /// use fungus::prelude::*;
1146    ///
1147    /// assert_eq!(PathBuf::from("/foo").trim_last(), PathBuf::from("/"));
1148    /// ```
1149    fn trim_last(&self) -> PathBuf;
1150
1151    /// Returns a new [`PathBuf`] with the given prefix trimmed off else the original `path`.
1152    ///
1153    /// ### Examples
1154    /// ```
1155    /// use fungus::prelude::*;
1156    ///
1157    /// assert_eq!(Path::new("/foo/bar").trim_prefix("/foo"), PathBuf::from("/bar"));
1158    /// ```
1159    fn trim_prefix<T: AsRef<Path>>(&self, prefix: T) -> PathBuf;
1160
1161    /// Returns a new [`PathBuf`] with well known protocol prefixes trimmed off else the original
1162    /// `path`.
1163    ///
1164    /// ### Examples
1165    /// ```
1166    /// use fungus::prelude::*;
1167    ///
1168    /// assert_eq!(PathBuf::from("ftp://foo").trim_protocol(), PathBuf::from("foo"));
1169    /// ```
1170    fn trim_protocol(&self) -> PathBuf;
1171
1172    /// Returns a new [`PathBuf`] with the given `suffix` trimmed off else the original `path`.
1173    ///
1174    /// ### Examples
1175    /// ```
1176    /// use fungus::prelude::*;
1177    ///
1178    /// assert_eq!(PathBuf::from("/foo/bar").trim_suffix("/bar"), PathBuf::from("/foo"));
1179    /// ```
1180    fn trim_suffix<T: AsRef<Path>>(&self, suffix: T) -> PathBuf;
1181
1182    /// Returns the user ID of the owner of this file.
1183    ///
1184    /// ### Examples
1185    /// ```
1186    /// use fungus::prelude::*;
1187    ///
1188    /// assert_eq!(Path::new("/etc").uid().unwrap(), 0);
1189    /// ```
1190    fn uid(&self) -> FuResult<u32>;
1191}
1192
1193impl PathExt for Path {
1194    fn abs(&self) -> FuResult<PathBuf> {
1195        abs(self)
1196    }
1197
1198    fn abs_from<T: AsRef<Path>>(&self, base: T) -> FuResult<PathBuf> {
1199        let base = base.as_ref().abs()?;
1200        if !self.is_absolute() && self != base {
1201            let mut path = base.trim_last();
1202            let mut components = self.components();
1203            loop {
1204                match components.next() {
1205                    Some(component) => match component {
1206                        Component::ParentDir => path = path.trim_last(),
1207                        Component::Normal(x) => return Ok(path.mash(x).mash(components.collect::<PathBuf>()).clean()?),
1208                        _ => {},
1209                    },
1210                    None => return Err(PathError::Empty.into()),
1211                }
1212            }
1213        }
1214        Ok(self.to_path_buf())
1215    }
1216
1217    fn base(&self) -> FuResult<String> {
1218        self.file_name().ok_or_else(|| PathError::filename_not_found(self))?.to_string()
1219    }
1220
1221    fn chmod(&self, mode: u32) -> FuResult<()> {
1222        sys::chmod(self, mode)?;
1223        Ok(())
1224    }
1225
1226    fn clean(&self) -> FuResult<PathBuf> {
1227        // Components already handles the following cases:
1228        // 1. Repeated separators are ignored, so a/b and a//b both have a and b as components.
1229        // 2. Occurrences of . are normalized away, except if they are at the beginning of the path.
1230        //    e.g. a/./b, a/b/, a/b/. and a/b all have a and b as components, but ./a/b starts with an
1231        // additional CurDir component.
1232        // 6. A trailing slash is normalized away, /a/b and /a/b/ are equivalent.
1233        let mut cnt = 0;
1234        let mut prev = None;
1235        let mut path_buf = PathBuf::new();
1236        for component in self.components() {
1237            match component {
1238                // 2. Eliminate . path name at begining of path for simplicity
1239                x if x == Component::CurDir && cnt == 0 => continue,
1240
1241                // 5. Leave .. begining non rooted path
1242                x if x == Component::ParentDir && cnt > 0 && !prev.has(Component::ParentDir) => {
1243                    match prev.unwrap() {
1244                        // 4. Eliminate .. elements that begin a root path
1245                        Component::RootDir => {},
1246
1247                        // 3. Eliminate inner .. path name elements
1248                        Component::Normal(_) => {
1249                            cnt -= 1;
1250                            path_buf.pop();
1251                            prev = path_buf.components().last();
1252                        },
1253                        _ => {},
1254                    }
1255                    continue;
1256                },
1257
1258                // Normal
1259                _ => {
1260                    cnt += 1;
1261                    path_buf.push(component);
1262                    prev = Some(component);
1263                },
1264            };
1265        }
1266
1267        // Ensure if empty the current dir is returned
1268        if path_buf.empty() {
1269            path_buf.push(".");
1270        }
1271        Ok(path_buf)
1272    }
1273
1274    fn concat<T: AsRef<str>>(&self, val: T) -> FuResult<PathBuf> {
1275        Ok(PathBuf::from(format!("{}{}", self.to_string()?, val.as_ref())))
1276    }
1277
1278    fn dir(&self) -> FuResult<PathBuf> {
1279        let dir = self.parent().ok_or_else(|| PathError::parent_not_found(self))?;
1280        Ok(dir.to_path_buf())
1281    }
1282
1283    fn empty(&self) -> bool {
1284        self == PathBuf::new()
1285    }
1286
1287    fn exists(&self) -> bool {
1288        exists(&self)
1289    }
1290
1291    fn expand(&self) -> FuResult<PathBuf> {
1292        expand(&self)
1293    }
1294
1295    fn ext(&self) -> FuResult<String> {
1296        match self.extension() {
1297            Some(val) => val.to_string(),
1298            None => Err(PathError::extension_not_found(self).into()),
1299        }
1300    }
1301
1302    fn first(&self) -> FuResult<Component> {
1303        self.components().first_result()
1304    }
1305
1306    fn gid(&self) -> FuResult<u32> {
1307        gid(&self)
1308    }
1309
1310    fn has<T: AsRef<Path>>(&self, path: T) -> bool {
1311        match (self.to_string(), path.as_ref().to_string()) {
1312            (Ok(base), Ok(path)) => base.contains(&path),
1313            _ => false,
1314        }
1315    }
1316
1317    fn has_prefix<T: AsRef<Path>>(&self, prefix: T) -> bool {
1318        match (self.to_string(), prefix.as_ref().to_string()) {
1319            (Ok(base), Ok(prefix)) => base.starts_with(&prefix),
1320            _ => false,
1321        }
1322    }
1323
1324    fn has_suffix<T: AsRef<Path>>(&self, suffix: T) -> bool {
1325        match (self.to_string(), suffix.as_ref().to_string()) {
1326            (Ok(base), Ok(suffix)) => base.ends_with(&suffix),
1327            _ => false,
1328        }
1329    }
1330
1331    fn is_dir(&self) -> bool {
1332        is_dir(self)
1333    }
1334
1335    fn is_exec(&self) -> bool {
1336        is_exec(self)
1337    }
1338
1339    fn is_file(&self) -> bool {
1340        is_file(self)
1341    }
1342
1343    fn is_readonly(&self) -> bool {
1344        is_readonly(self)
1345    }
1346
1347    fn is_symlink(&self) -> bool {
1348        is_symlink(self)
1349    }
1350
1351    fn is_symlink_dir(&self) -> bool {
1352        is_symlink_dir(self)
1353    }
1354
1355    fn is_symlink_file(&self) -> bool {
1356        is_symlink_file(self)
1357    }
1358
1359    fn last(&self) -> FuResult<Component> {
1360        self.components().last_result()
1361    }
1362
1363    fn mash<T: AsRef<Path>>(&self, path: T) -> PathBuf {
1364        mash(self, path)
1365    }
1366
1367    fn metadata(&self) -> FuResult<fs::Metadata> {
1368        let meta = fs::metadata(self)?;
1369        Ok(meta)
1370    }
1371
1372    fn mode(&self) -> FuResult<u32> {
1373        let perms = self.perms()?;
1374        Ok(perms.mode())
1375    }
1376
1377    fn name(&self) -> FuResult<String> {
1378        self.trim_ext()?.base()
1379    }
1380
1381    fn perms(&self) -> FuResult<fs::Permissions> {
1382        Ok(self.metadata()?.permissions())
1383    }
1384
1385    fn readlink(&self) -> FuResult<PathBuf> {
1386        readlink(self)
1387    }
1388
1389    fn relative_from<T: AsRef<Path>>(&self, base: T) -> FuResult<PathBuf> {
1390        let path = self.abs()?;
1391        let base = base.as_ref().abs()?;
1392        if path != base {
1393            let mut x = path.components();
1394            let mut y = base.components();
1395            let mut comps: Vec<Component> = vec![];
1396            loop {
1397                match (x.next(), y.next()) {
1398                    (None, None) => break,
1399                    (Some(a), None) => {
1400                        comps.push(a);
1401                        comps.extend(x.by_ref());
1402                        break;
1403                    },
1404                    (None, _) => comps.push(Component::ParentDir),
1405                    (Some(a), Some(b)) if comps.is_empty() && a == b => {},
1406                    (Some(a), Some(b)) if b == Component::CurDir => comps.push(a),
1407                    (Some(_), Some(b)) if b == Component::ParentDir => return Ok(path),
1408                    (Some(a), Some(_)) => {
1409                        for _ in y {
1410                            comps.push(Component::ParentDir);
1411                        }
1412                        comps.push(a);
1413                        comps.extend(x.by_ref());
1414                        break;
1415                    },
1416                }
1417            }
1418            return Ok(comps.iter().collect::<PathBuf>());
1419        }
1420        Ok(path)
1421    }
1422
1423    fn setperms(&self, perms: fs::Permissions) -> FuResult<PathBuf> {
1424        fs::set_permissions(&self, perms)?;
1425        Ok(self.to_path_buf())
1426    }
1427
1428    fn trim_ext(&self) -> FuResult<PathBuf> {
1429        Ok(match self.extension() {
1430            Some(val) => self.trim_suffix(format!(".{}", val.to_string()?)),
1431            None => self.to_path_buf(),
1432        })
1433    }
1434
1435    fn trim_first(&self) -> PathBuf {
1436        self.components().drop(1).as_path().to_path_buf()
1437    }
1438
1439    fn trim_last(&self) -> PathBuf {
1440        self.components().drop(-1).as_path().to_path_buf()
1441    }
1442
1443    fn trim_prefix<T: AsRef<Path>>(&self, prefix: T) -> PathBuf {
1444        match (self.to_string(), prefix.as_ref().to_string()) {
1445            (Ok(base), Ok(prefix)) if base.starts_with(&prefix) => PathBuf::from(&base[prefix.size()..]),
1446            _ => self.to_path_buf(),
1447        }
1448    }
1449
1450    fn trim_protocol(&self) -> PathBuf {
1451        match self.to_string() {
1452            Ok(base) => match base.find("//") {
1453                Some(i) => {
1454                    let (prefix, suffix) = base.split_at(i + 2);
1455                    let lower = prefix.to_lowercase();
1456                    let lower = lower.trim_start_matches("file://");
1457                    let lower = lower.trim_start_matches("ftp://");
1458                    let lower = lower.trim_start_matches("http://");
1459                    let lower = lower.trim_start_matches("https://");
1460                    if lower != "" {
1461                        PathBuf::from(format!("{}{}", prefix, suffix))
1462                    } else {
1463                        PathBuf::from(suffix)
1464                    }
1465                },
1466                _ => PathBuf::from(base),
1467            },
1468            _ => self.to_path_buf(),
1469        }
1470    }
1471
1472    fn trim_suffix<T: AsRef<Path>>(&self, suffix: T) -> PathBuf {
1473        match (self.to_string(), suffix.as_ref().to_string()) {
1474            (Ok(base), Ok(suffix)) if base.ends_with(&suffix) => PathBuf::from(&base[..base.size() - suffix.size()]),
1475            _ => self.to_path_buf(),
1476        }
1477    }
1478
1479    fn uid(&self) -> FuResult<u32> {
1480        uid(&self)
1481    }
1482}
1483
1484pub trait PathColorExt {
1485    fn black(&self) -> ColorString;
1486    fn red(&self) -> ColorString;
1487    fn green(&self) -> ColorString;
1488    fn yellow(&self) -> ColorString;
1489    fn blue(&self) -> ColorString;
1490    fn magenta(&self) -> ColorString;
1491    fn cyan(&self) -> ColorString;
1492    fn white(&self) -> ColorString;
1493}
1494impl PathColorExt for Path {
1495    fn black(&self) -> ColorString {
1496        self.display().to_string().black()
1497    }
1498
1499    fn red(&self) -> ColorString {
1500        self.display().to_string().red()
1501    }
1502
1503    fn green(&self) -> ColorString {
1504        self.display().to_string().green()
1505    }
1506
1507    fn yellow(&self) -> ColorString {
1508        self.display().to_string().yellow()
1509    }
1510
1511    fn blue(&self) -> ColorString {
1512        self.display().to_string().blue()
1513    }
1514
1515    fn magenta(&self) -> ColorString {
1516        self.display().to_string().magenta()
1517    }
1518
1519    fn cyan(&self) -> ColorString {
1520        self.display().to_string().cyan()
1521    }
1522
1523    fn white(&self) -> ColorString {
1524        self.display().to_string().white()
1525    }
1526}
1527
1528// Unit tests
1529// -------------------------------------------------------------------------------------------------
1530#[cfg(test)]
1531mod tests {
1532    use crate::prelude::*;
1533    use std::path::Component;
1534
1535    // Test setup
1536    fn setup() -> PathBuf {
1537        let temp = PathBuf::from("tests/temp").abs().unwrap();
1538        sys::mkdir(&temp).unwrap();
1539        temp
1540    }
1541
1542    #[test]
1543    fn test_abs() {
1544        let cwd = sys::cwd().unwrap();
1545        let prev = cwd.dir().unwrap();
1546
1547        // expand relative directory
1548        assert_eq!(sys::abs("foo").unwrap(), cwd.mash("foo"));
1549
1550        // expand previous directory and drop trailing slashes
1551        assert_eq!(sys::abs("..//").unwrap(), prev);
1552        assert_eq!(sys::abs("../").unwrap(), prev);
1553        assert_eq!(sys::abs("..").unwrap(), prev);
1554
1555        // expand current directory and drop trailing slashes
1556        assert_eq!(sys::abs(".//").unwrap(), cwd);
1557        assert_eq!(sys::abs("./").unwrap(), cwd);
1558        assert_eq!(sys::abs(".").unwrap(), cwd);
1559
1560        // home dir
1561        let home = PathBuf::from(user::home_dir().unwrap());
1562        assert_eq!(sys::abs("~").unwrap(), home);
1563        assert_eq!(sys::abs("~/").unwrap(), home);
1564
1565        // expand home path
1566        assert_eq!(sys::abs("~/foo").unwrap(), home.mash("foo"));
1567
1568        // More complicated
1569        assert_eq!(sys::abs("~/foo/bar/../.").unwrap(), home.mash("foo"));
1570        assert_eq!(sys::abs("~/foo/bar/../").unwrap(), home.mash("foo"));
1571        assert_eq!(sys::abs("~/foo/bar/../blah").unwrap(), home.mash("foo/blah"));
1572
1573        // // Move up the path multiple levels
1574        // assert_eq!(sys::abs("./../../foo").unwrap(), home.mash("foo"));
1575        // assert_eq!(sys::abs("../../foo").unwrap(), home.mash("foo"));
1576
1577        // // Move up until invalid
1578        // assert!(sys::abs("../../../../../foo").is_err());
1579    }
1580
1581    #[test]
1582    fn test_all_dirs() {
1583        let tmpdir = setup().mash("path_all_dirs");
1584        let tmpdir1 = tmpdir.mash("dir1");
1585        let tmpdir2 = tmpdir1.mash("dir2");
1586        let tmpfile1 = tmpdir.mash("file1");
1587        let tmpfile2 = tmpdir.mash("file2");
1588
1589        // invalid target
1590        assert!(sys::all_dirs("").is_err());
1591        assert!(sys::all_dirs("foobar").is_err());
1592
1593        // Create the dirs and files
1594        assert!(sys::mkdir(&tmpdir1).is_ok());
1595        assert!(sys::mkdir(&tmpdir2).is_ok());
1596        assert_eq!(tmpdir.is_dir(), true);
1597        assert_eq!(tmpdir.is_file(), false);
1598        assert_eq!(tmpdir1.is_dir(), true);
1599        assert_eq!(tmpdir2.is_dir(), true);
1600        assert!(sys::touch(&tmpfile1).is_ok());
1601        assert_eq!(tmpfile1.is_dir(), false);
1602        assert_eq!(tmpfile1.is_file(), true);
1603        assert!(sys::touch(&tmpfile2).is_ok());
1604        assert_eq!(tmpfile2.is_dir(), false);
1605        assert_eq!(tmpfile2.is_file(), true);
1606
1607        // invalid target
1608        assert!(sys::all_dirs(&tmpfile1).is_err());
1609
1610        // Validate the the all_dirs function gives me the correct dirs in order
1611        let dirs = sys::all_dirs(&tmpdir).unwrap();
1612        assert_iter_eq(dirs, vec![tmpdir1, tmpdir2]);
1613
1614        // Clean up
1615        assert!(sys::remove_all(&tmpdir).is_ok());
1616        assert_eq!(tmpdir.exists(), false);
1617    }
1618
1619    #[test]
1620    fn test_all_files() {
1621        let tmpdir = setup().mash("path_all_files");
1622        let tmpdir1 = tmpdir.mash("dir1");
1623        let tmpdir2 = tmpdir1.mash("dir2");
1624        let tmpfile1 = tmpdir1.mash("file1");
1625        let tmpfile2 = tmpdir2.mash("file2");
1626
1627        // invalid target
1628        assert!(sys::all_files("").is_err());
1629        assert!(sys::all_files("foobar").is_err());
1630
1631        // Create the dirs and files
1632        assert!(sys::mkdir(&tmpdir1).is_ok());
1633        assert!(sys::mkdir(&tmpdir2).is_ok());
1634        assert_eq!(tmpdir.is_dir(), true);
1635        assert_eq!(tmpdir.is_file(), false);
1636        assert_eq!(tmpdir1.is_dir(), true);
1637        assert_eq!(tmpdir2.is_dir(), true);
1638        assert!(sys::touch(&tmpfile1).is_ok());
1639        assert_eq!(tmpfile1.is_dir(), false);
1640        assert_eq!(tmpfile1.is_file(), true);
1641        assert!(sys::touch(&tmpfile2).is_ok());
1642        assert_eq!(tmpfile2.is_dir(), false);
1643        assert_eq!(tmpfile2.is_file(), true);
1644
1645        // invalid target
1646        assert!(sys::all_files(&tmpfile1).is_err());
1647
1648        // Validate the the all_files function gives me the correct files in order
1649        let files = sys::all_files(&tmpdir).unwrap();
1650        assert_iter_eq(files, vec![tmpfile2, tmpfile1]);
1651
1652        // Clean up
1653        assert!(sys::remove_all(&tmpdir).is_ok());
1654        assert_eq!(tmpdir.exists(), false);
1655    }
1656
1657    #[test]
1658    fn test_all_paths() {
1659        let tmpdir = setup().mash("path_all_paths");
1660        let tmpdir1 = tmpdir.mash("dir1");
1661        let tmpdir2 = tmpdir1.mash("dir2");
1662        let tmpfile1 = tmpdir1.mash("file1");
1663        let tmpfile2 = tmpdir2.mash("file2");
1664
1665        // invalid target
1666        assert!(sys::all_paths("").is_err());
1667        assert!(sys::all_paths("foobar").is_err());
1668
1669        // Create the dirs and files
1670        assert!(sys::mkdir(&tmpdir1).is_ok());
1671        assert!(sys::mkdir(&tmpdir2).is_ok());
1672        assert_eq!(tmpdir.is_dir(), true);
1673        assert_eq!(tmpdir.is_file(), false);
1674        assert_eq!(tmpdir1.is_dir(), true);
1675        assert_eq!(tmpdir2.is_dir(), true);
1676        assert!(sys::touch(&tmpfile1).is_ok());
1677        assert_eq!(tmpfile1.is_dir(), false);
1678        assert_eq!(tmpfile1.is_file(), true);
1679        assert!(sys::touch(&tmpfile2).is_ok());
1680        assert_eq!(tmpfile2.is_dir(), false);
1681        assert_eq!(tmpfile2.is_file(), true);
1682
1683        // invalid target
1684        assert!(sys::all_paths(&tmpfile1).is_err());
1685
1686        // Validate the the all_paths function gives me the correct paths in order
1687        let paths = sys::all_paths(&tmpdir).unwrap();
1688        assert_iter_eq(paths, vec![tmpdir1, tmpdir2, tmpfile2, tmpfile1]);
1689
1690        // Clean up
1691        assert!(sys::remove_all(&tmpdir).is_ok());
1692        assert_eq!(tmpdir.exists(), false);
1693    }
1694
1695    #[test]
1696    fn test_dirs() {
1697        let tmpdir = setup().mash("path_dirs");
1698        let tmpdir1 = tmpdir.mash("dir1");
1699        let tmpdir2 = tmpdir.mash("dir2");
1700        let tmpfile1 = tmpdir.mash("file1");
1701        let tmpfile2 = tmpdir.mash("file2");
1702
1703        // invalid target
1704        assert!(sys::dirs("").is_err());
1705        assert!(sys::dirs("foobar").is_err());
1706
1707        // Create the dirs and files
1708        assert!(sys::mkdir(&tmpdir1).is_ok());
1709        assert!(sys::mkdir(&tmpdir2).is_ok());
1710        assert_eq!(tmpdir.is_dir(), true);
1711        assert_eq!(tmpdir.is_file(), false);
1712        assert_eq!(tmpdir1.is_dir(), true);
1713        assert_eq!(tmpdir2.is_dir(), true);
1714        assert!(sys::touch(&tmpfile1).is_ok());
1715        assert_eq!(tmpfile1.is_dir(), false);
1716        assert_eq!(tmpfile1.is_file(), true);
1717        assert!(sys::touch(&tmpfile2).is_ok());
1718        assert_eq!(tmpfile2.is_dir(), false);
1719        assert_eq!(tmpfile2.is_file(), true);
1720
1721        // invalid target
1722        assert!(sys::dirs(&tmpfile1).is_err());
1723
1724        // Validate the the dirs function gives me the correct dirs without the files and in order
1725        let dirs = sys::dirs(&tmpdir).unwrap();
1726        assert_iter_eq(dirs, vec![tmpdir1, tmpdir2]);
1727
1728        // Clean up
1729        assert!(sys::remove_all(&tmpdir).is_ok());
1730        assert_eq!(tmpdir.exists(), false);
1731    }
1732
1733    #[test]
1734    fn test_exists() {
1735        let tmpdir = setup().mash("path_exists");
1736        let tmpfile = tmpdir.mash("file");
1737        assert!(sys::remove_all(&tmpdir).is_ok());
1738        assert!(sys::mkdir(&tmpdir).is_ok());
1739        assert_eq!(sys::exists(&tmpfile), false);
1740        assert!(sys::touch(&tmpfile).is_ok());
1741        assert_eq!(sys::exists(&tmpfile), true);
1742        assert!(sys::remove_all(&tmpdir).is_ok());
1743    }
1744
1745    #[test]
1746    fn test_files() {
1747        let tmpdir = setup().mash("path_files");
1748        let tmpdir1 = tmpdir.mash("dir1");
1749        let tmpdir2 = tmpdir.mash("dir2");
1750        let tmpfile1 = tmpdir.mash("file1");
1751        let tmpfile2 = tmpdir.mash("file2");
1752
1753        // invalid target
1754        assert!(sys::files("").is_err());
1755        assert!(sys::files("foobar").is_err());
1756
1757        // Create the dirs and files
1758        assert!(sys::mkdir(&tmpdir1).is_ok());
1759        assert!(sys::mkdir(&tmpdir2).is_ok());
1760        assert_eq!(tmpdir.is_dir(), true);
1761        assert_eq!(tmpdir.is_file(), false);
1762        assert_eq!(tmpdir1.is_dir(), true);
1763        assert_eq!(tmpdir2.is_dir(), true);
1764        assert!(sys::touch(&tmpfile1).is_ok());
1765        assert_eq!(tmpfile1.is_dir(), false);
1766        assert_eq!(tmpfile1.is_file(), true);
1767        assert!(sys::touch(&tmpfile2).is_ok());
1768        assert_eq!(tmpfile2.is_dir(), false);
1769        assert_eq!(tmpfile2.is_file(), true);
1770
1771        // invalid target
1772        assert!(sys::files(&tmpfile1).is_err());
1773
1774        // Validate the the files function gives me the correct files without the dirs and in order
1775        let files = sys::files(&tmpdir).unwrap();
1776        assert_iter_eq(files, vec![tmpfile1, tmpfile2]);
1777
1778        // Clean up
1779        assert!(sys::remove_all(&tmpdir).is_ok());
1780        assert_eq!(tmpdir.exists(), false);
1781    }
1782
1783    #[test]
1784    fn test_rel_to() {
1785        assert_eq!(sys::rel_to("home").unwrap(), PathBuf::from("/home"));
1786    }
1787
1788    #[test]
1789    fn test_uid() {
1790        assert!(sys::uid(".").is_ok());
1791        assert!(Path::new(".").uid().is_ok());
1792    }
1793
1794    #[test]
1795    fn test_gid() {
1796        assert!(sys::gid(".").is_ok());
1797        assert!(Path::new(".").gid().is_ok());
1798    }
1799
1800    #[test]
1801    fn test_is_dir() {
1802        assert_eq!(sys::is_dir("."), true);
1803        assert_eq!(sys::is_dir(setup()), true);
1804        assert_eq!(sys::is_dir("/foobar"), false);
1805    }
1806
1807    #[test]
1808    fn test_is_exec() {
1809        let tmpdir = setup().mash("path_is_exec");
1810        let file1 = tmpdir.mash("file1");
1811
1812        // setup
1813        assert!(sys::remove_all(&tmpdir).is_ok());
1814        assert!(sys::mkdir(&tmpdir).is_ok());
1815        assert_eq!(sys::is_exec(&file1), false);
1816        assert!(sys::touch_p(&file1, 0o644).is_ok());
1817        assert_eq!(file1.mode().unwrap(), 0o100644);
1818        assert_eq!(file1.is_exec(), false);
1819
1820        // add_x
1821        assert!(sys::chmod_p(&file1).unwrap().add_x().chmod().is_ok());
1822        assert_eq!(file1.mode().unwrap(), 0o100755);
1823        assert_eq!(file1.is_exec(), true);
1824
1825        // cleanup
1826        assert!(sys::remove_all(&tmpdir).is_ok());
1827    }
1828
1829    #[test]
1830    fn test_is_file() {
1831        let tmpdir = setup().mash("path_is_file");
1832        let tmpfile = tmpdir.mash("file1");
1833
1834        assert!(sys::remove_all(&tmpdir).is_ok());
1835        assert!(sys::mkdir(&tmpdir).is_ok());
1836        assert_eq!(sys::is_file(&tmpfile), false);
1837        assert!(sys::touch(&tmpfile).is_ok());
1838        assert_eq!(sys::is_file(tmpfile), true);
1839
1840        // Clean up
1841        assert!(sys::remove_all(&tmpdir).is_ok());
1842    }
1843
1844    #[test]
1845    fn test_is_readonly() {
1846        let tmpdir = setup().mash("path_is_readonly");
1847        let file1 = tmpdir.mash("file1");
1848        assert!(sys::remove_all(&tmpdir).is_ok());
1849        assert!(sys::mkdir(&tmpdir).is_ok());
1850
1851        assert_eq!(sys::is_readonly(&file1), false);
1852        assert!(sys::touch_p(&file1, 0o644).is_ok());
1853        assert_eq!(file1.is_readonly(), false);
1854        assert!(sys::chmod_p(&file1).unwrap().readonly().chmod().is_ok());
1855        assert_eq!(file1.mode().unwrap(), 0o100444);
1856        assert_eq!(sys::is_readonly(&file1), true);
1857        assert!(sys::remove_all(&tmpdir).is_ok());
1858    }
1859
1860    #[test]
1861    fn test_is_symlink() {
1862        let tmpdir = setup().mash("path_is_symlink");
1863        let file1 = tmpdir.mash("file1");
1864        let link1 = tmpdir.mash("link1");
1865
1866        assert!(sys::remove_all(&tmpdir).is_ok());
1867        assert!(sys::mkdir(&tmpdir).is_ok());
1868        assert_eq!(sys::is_symlink(&link1), false);
1869        assert!(sys::touch(&file1).is_ok());
1870        assert!(sys::symlink(&link1, &file1).is_ok());
1871        assert_eq!(sys::is_symlink(link1), true);
1872
1873        // cleanup
1874        assert!(sys::remove_all(&tmpdir).is_ok());
1875    }
1876
1877    #[test]
1878    fn test_is_symlink_dir() {
1879        let tmpdir = setup().mash("path_is_symlink_dir");
1880        let dir1 = tmpdir.mash("dir1");
1881        let link1 = tmpdir.mash("link1");
1882        let link2 = tmpdir.mash("link2");
1883
1884        // setup
1885        assert!(sys::remove_all(&tmpdir).is_ok());
1886        assert!(sys::mkdir(&dir1).is_ok());
1887
1888        // test absolute
1889        assert!(sys::symlink(&link1, &dir1).is_ok());
1890        assert_eq!(sys::is_symlink_dir(&link1), true);
1891        assert_eq!(sys::is_symlink_file(&link1), false);
1892
1893        // test relative
1894        assert!(sys::symlink(&link2, "dir1").is_ok());
1895        assert_eq!(sys::is_symlink_dir(&link2), true);
1896        assert_eq!(sys::is_symlink_file(&link2), false);
1897
1898        // cleanup
1899        assert!(sys::remove_all(&tmpdir).is_ok());
1900    }
1901
1902    #[test]
1903    fn test_is_symlink_file() {
1904        let tmpdir = setup().mash("path_is_symlink_file");
1905        let file1 = tmpdir.mash("file1");
1906        let link1 = tmpdir.mash("link1");
1907        let link2 = tmpdir.mash("link2");
1908
1909        // invalid
1910        assert_eq!(sys::is_symlink_file(""), false);
1911
1912        // setup
1913        assert!(sys::remove_all(&tmpdir).is_ok());
1914        assert!(sys::mkdir(&tmpdir).is_ok());
1915        assert!(sys::touch(&file1).is_ok());
1916
1917        // test absolute
1918        assert!(sys::symlink(&link1, &file1).is_ok());
1919        assert_eq!(sys::is_symlink_file(&link1), true);
1920        assert_eq!(sys::is_symlink_dir(&link1), false);
1921
1922        // test relative
1923        assert!(sys::symlink(&link2, "file1").is_ok());
1924        assert_eq!(sys::is_symlink_file(&link2), true);
1925        assert_eq!(sys::is_symlink_dir(&link2), false);
1926
1927        // cleanup
1928        assert!(sys::remove_all(&tmpdir).is_ok());
1929    }
1930
1931    #[test]
1932    fn test_glob() {
1933        let tmpdir = setup().mash("path_glob");
1934        let tmpdir1 = tmpdir.mash("dir1");
1935        let tmpdir2 = tmpdir.mash("dir2");
1936        let tmpfile1 = tmpdir.mash("file1");
1937        let tmpfile2 = tmpdir.mash("file2");
1938
1939        // Create the dirs and files
1940        assert!(sys::mkdir(&tmpdir1).is_ok());
1941        assert!(sys::mkdir(&tmpdir2).is_ok());
1942        assert_eq!(tmpdir.is_dir(), true);
1943        assert_eq!(tmpdir.is_file(), false);
1944        assert_eq!(tmpdir1.is_dir(), true);
1945        assert_eq!(tmpdir2.is_dir(), true);
1946        assert!(sys::touch(&tmpfile1).is_ok());
1947        assert_eq!(tmpfile1.is_dir(), false);
1948        assert_eq!(tmpfile1.is_file(), true);
1949        assert!(sys::touch(&tmpfile2).is_ok());
1950        assert_eq!(tmpfile2.is_dir(), false);
1951        assert_eq!(tmpfile2.is_file(), true);
1952
1953        // Validate the the files function gives me the correct files without the dirs and in order
1954        let paths = sys::glob(tmpdir.mash("*")).unwrap();
1955        assert_iter_eq(paths, vec![tmpdir1, tmpdir2, tmpfile1, tmpfile2]);
1956
1957        // Clean up
1958        assert!(sys::remove_all(&tmpdir).is_ok());
1959        assert_eq!(tmpdir.exists(), false);
1960    }
1961
1962    #[test]
1963    fn test_metadata() {
1964        let meta = sys::metadata(setup()).unwrap();
1965        assert_eq!(meta.is_dir(), true);
1966    }
1967
1968    #[test]
1969    fn test_paths() {
1970        let tmpdir = setup().mash("path_paths");
1971        let tmpdir1 = tmpdir.mash("dir1");
1972        let tmpdir2 = tmpdir.mash("dir2");
1973        let tmpfile1 = tmpdir.mash("file1");
1974        let tmpfile2 = tmpdir.mash("file2");
1975
1976        // invalid target
1977        assert!(sys::paths("").is_err());
1978        assert!(sys::paths("foobar").is_err());
1979
1980        // Create the dirs and files
1981        assert!(sys::mkdir(&tmpdir1).is_ok());
1982        assert!(sys::mkdir(&tmpdir2).is_ok());
1983        assert_eq!(tmpdir.is_dir(), true);
1984        assert_eq!(tmpdir.is_file(), false);
1985        assert_eq!(tmpdir1.is_dir(), true);
1986        assert_eq!(tmpdir2.is_dir(), true);
1987        assert!(sys::touch(&tmpfile1).is_ok());
1988        assert_eq!(tmpfile1.is_dir(), false);
1989        assert_eq!(tmpfile1.is_file(), true);
1990        assert!(sys::touch(&tmpfile2).is_ok());
1991        assert_eq!(tmpfile2.is_dir(), false);
1992        assert_eq!(tmpfile2.is_file(), true);
1993
1994        // invalid target
1995        assert!(sys::paths(&tmpfile1).is_err());
1996
1997        // Validate the the paths function gives me all the dirs/files in order
1998        let paths = sys::paths(&tmpdir).unwrap();
1999        assert_iter_eq(paths, vec![tmpdir1, tmpdir2, tmpfile1, tmpfile2]);
2000
2001        // Clean up
2002        assert!(sys::remove_all(&tmpdir).is_ok());
2003        assert_eq!(tmpdir.exists(), false);
2004    }
2005
2006    #[test]
2007    fn test_parse_paths() {
2008        let paths = vec![PathBuf::from("/foo1"), PathBuf::from("/foo2/bar")];
2009        assert_iter_eq(sys::parse_paths("/foo1:/foo2/bar").unwrap(), paths);
2010
2011        let paths = vec![
2012            sys::cwd().unwrap(),
2013            PathBuf::from("/foo1"),
2014            PathBuf::from("/foo2/bar"),
2015        ];
2016        assert_iter_eq(sys::parse_paths(":/foo1:/foo2/bar").unwrap(), paths);
2017    }
2018
2019    #[test]
2020    fn test_readlink() {
2021        let tmpdir = setup().mash("path_readlink");
2022        let file1 = tmpdir.mash("file1");
2023        let link1 = tmpdir.mash("link1");
2024
2025        assert!(sys::remove_all(&tmpdir).is_ok());
2026        assert!(sys::mkdir(&tmpdir).is_ok());
2027        assert!(sys::touch(&file1).is_ok());
2028        assert!(sys::symlink(&link1, &file1).is_ok());
2029        assert_eq!(sys::is_symlink_file(&link1), true);
2030        assert_eq!(sys::is_symlink_dir(&link1), false);
2031        assert_eq!(sys::readlink(&link1).unwrap(), file1);
2032
2033        // cleanup
2034        assert!(sys::remove_all(&tmpdir).is_ok());
2035    }
2036
2037    // Path tests
2038    // ---------------------------------------------------------------------------------------------
2039
2040    #[test]
2041    fn test_pathext_abs_from() {
2042        let home = PathBuf::from("~").abs().unwrap();
2043
2044        // invalid
2045        assert!(PathBuf::from("foo").abs_from("").is_err());
2046
2047        // already absolute
2048        assert_eq!(PathBuf::from("/foo").abs_from("foo1").unwrap(), PathBuf::from("/foo"));
2049
2050        // share the same directory
2051        assert_eq!(PathBuf::from("foo2").abs_from(home.mash("foo1").abs().unwrap()).unwrap(), home.mash("foo2"));
2052        assert_eq!(PathBuf::from("./foo2").abs_from(home.mash("foo1").abs().unwrap()).unwrap(), home.mash("foo2"));
2053
2054        // share parent directory
2055        assert_eq!(PathBuf::from("../foo2").abs_from(home.mash("bar1/foo1").abs().unwrap()).unwrap(), home.mash("foo2"));
2056        assert_eq!(PathBuf::from("bar2/foo2").abs_from(home.mash("bar1/foo1").abs().unwrap()).unwrap(), home.mash("bar1/bar2/foo2"));
2057        assert_eq!(PathBuf::from("../../foo2").abs_from(home.mash("bar1/foo1").abs().unwrap()).unwrap(), home.trim_last().mash("foo2"));
2058
2059        // share grandparent directory
2060        assert_eq!(PathBuf::from("blah1/bar2/foo2").abs_from(home.mash("bar1/foo1").abs().unwrap()).unwrap(), home.mash("bar1/blah1/bar2/foo2"));
2061    }
2062
2063    #[test]
2064    fn test_pathext_base() {
2065        assert_eq!("bar", PathBuf::from("/foo/bar").base().unwrap());
2066    }
2067
2068    #[test]
2069    fn test_pathext_chmod() {
2070        let tmpdir = setup().mash("path_pathbuf_chmod");
2071        let file1 = tmpdir.mash("file1");
2072
2073        assert!(sys::remove_all(&tmpdir).is_ok());
2074        assert!(sys::mkdir(&tmpdir).is_ok());
2075        assert!(sys::touch(&file1).is_ok());
2076        assert!(file1.chmod(0o644).is_ok());
2077        assert_eq!(file1.mode().unwrap(), 0o100644);
2078        assert!(file1.chmod(0o555).is_ok());
2079        assert_eq!(file1.mode().unwrap(), 0o100555);
2080        assert!(sys::remove_all(&tmpdir).is_ok());
2081    }
2082
2083    #[test]
2084    fn test_pathext_clean() {
2085        let tests = vec![
2086            // Root
2087            ("/", "/"),
2088            // Remove trailing slashes
2089            ("/", "//"),
2090            ("/", "///"),
2091            (".", ".//"),
2092            // Remove duplicates and handle rooted parent ref
2093            ("/", "//.."),
2094            ("..", "..//"),
2095            ("/", "/..//"),
2096            ("foo/bar/blah", "foo//bar///blah"),
2097            ("/foo/bar/blah", "/foo//bar///blah"),
2098            // Unneeded current dirs and duplicates
2099            ("/", "/.//./"),
2100            (".", "././/./"),
2101            (".", "./"),
2102            ("/", "/./"),
2103            ("foo", "./foo"),
2104            ("foo/bar", "./foo/./bar"),
2105            ("/foo/bar", "/foo/./bar"),
2106            ("foo/bar", "foo/bar/."),
2107            // Handle parent references
2108            ("/", "/.."),
2109            ("/foo", "/../foo"),
2110            (".", "foo/.."),
2111            ("../foo", "../foo"),
2112            ("/bar", "/foo/../bar"),
2113            ("foo", "foo/bar/.."),
2114            ("bar", "foo/../bar"),
2115            ("/bar", "/foo/../bar"),
2116            (".", "foo/bar/../../"),
2117            ("..", "foo/bar/../../.."),
2118            ("/", "/foo/bar/../../.."),
2119            ("/", "/foo/bar/../../../.."),
2120            ("../..", "foo/bar/../../../.."),
2121            ("blah/bar", "foo/bar/../../blah/bar"),
2122            ("blah", "foo/bar/../../blah/bar/.."),
2123            ("../foo", "../foo"),
2124            ("../foo", "../foo/"),
2125            ("../foo/bar", "../foo/bar"),
2126            ("..", "../foo/.."),
2127            ("~/foo", "~/foo"),
2128        ];
2129        for test in tests {
2130            assert_eq!(PathBuf::from(test.0), PathBuf::from(test.1).clean().unwrap());
2131        }
2132    }
2133
2134    #[test]
2135    fn test_pathext_concat() {
2136        assert_eq!(Path::new("").concat(".rs").unwrap(), PathBuf::from(".rs"));
2137        assert_eq!(Path::new("foo").concat(".rs").unwrap(), PathBuf::from("foo.rs"));
2138        assert_eq!(Path::new("foo.exe").concat(".rs").unwrap(), PathBuf::from("foo.exe.rs"));
2139        assert_eq!(Path::new("/foo/bar").concat(".rs").unwrap(), PathBuf::from("/foo/bar.rs"));
2140    }
2141
2142    #[test]
2143    fn test_pathext_dirname() {
2144        assert_eq!(PathBuf::from("/").as_path(), PathBuf::from("/foo/").dir().unwrap());
2145        assert_eq!(PathBuf::from("/foo").as_path(), PathBuf::from("/foo/bar").dir().unwrap());
2146    }
2147
2148    #[test]
2149    fn test_pathext_empty() {
2150        // empty string
2151        assert_eq!(PathBuf::from("").empty(), true);
2152
2153        // false
2154        assert_eq!(PathBuf::from("/foo").empty(), false);
2155    }
2156
2157    #[test]
2158    fn test_pathext_exists() {
2159        assert_eq!(setup().exists(), true);
2160    }
2161
2162    #[test]
2163    fn test_pathext_expand() {
2164        let home = PathBuf::from(user::home_dir().unwrap());
2165
2166        // happy path
2167        assert_eq!(PathBuf::from("~/").expand().unwrap(), home);
2168        assert_eq!(PathBuf::from("~").expand().unwrap(), home);
2169
2170        // More than one ~
2171        assert!(PathBuf::from("~/foo~").expand().is_err());
2172
2173        // invalid path
2174        assert!(PathBuf::from("~foo").expand().is_err());
2175
2176        // empty path - nothing to do but no error
2177        assert_eq!(PathBuf::from(""), PathBuf::from("").expand().unwrap());
2178
2179        // Commented out these two as XDB paths are not set in github's test environment apparently
2180        if !sys::flag("GITHUB_ACTIONS") {
2181            assert_eq!(PathBuf::from("$XDG_CONFIG_HOME").expand().unwrap(), home.mash(".config"));
2182            assert_eq!(PathBuf::from("${XDG_CONFIG_HOME}").expand().unwrap(), home.mash(".config"));
2183        }
2184
2185        // Expand other variables in the path
2186        sys::set_var("PATHEXT_EXPAND", "bar");
2187        assert_eq!(PathBuf::from("~/foo/$PATHEXT_EXPAND").expand().unwrap(), home.mash("foo/bar"));
2188        assert_eq!(PathBuf::from("~/foo/${PATHEXT_EXPAND}").expand().unwrap(), home.mash("foo/bar"));
2189        assert_eq!(PathBuf::from("~/foo/$PATHEXT_EXPAND/blah").expand().unwrap(), home.mash("foo/bar/blah"));
2190    }
2191
2192    #[test]
2193    fn test_pathext_ext() {
2194        assert!(PathBuf::from("").ext().is_err());
2195        assert!(PathBuf::from("foo").ext().is_err());
2196        assert_eq!(PathBuf::from("foo.exe").ext().unwrap(), "exe");
2197        assert_eq!(PathBuf::from("/foo/bar.exe").ext().unwrap(), "exe");
2198    }
2199
2200    #[test]
2201    fn test_pathext_first() {
2202        assert_eq!(Component::RootDir, PathBuf::from("/").first().unwrap());
2203        assert_eq!(Component::CurDir, PathBuf::from(".").first().unwrap());
2204        assert_eq!(Component::ParentDir, PathBuf::from("..").first().unwrap());
2205        assert_eq!(Component::Normal(OsStr::new("foo")), PathBuf::from("foo").first().unwrap());
2206        assert_eq!(Component::Normal(OsStr::new("foo")), PathBuf::from("foo/bar").first().unwrap());
2207    }
2208
2209    #[test]
2210    fn test_pathext_has() {
2211        let path = PathBuf::from("/foo/bar");
2212        assert_eq!(path.has("foo"), true);
2213        assert_eq!(path.has("/foo"), true);
2214        assert_eq!(path.has("/"), true);
2215        assert_eq!(path.has("/ba"), true);
2216        assert_eq!(path.has("bob"), false);
2217    }
2218
2219    #[test]
2220    fn test_pathext_has_prefix() {
2221        let path = PathBuf::from("/foo/bar");
2222        assert_eq!(path.has_prefix("/foo"), true);
2223        assert_eq!(path.has_prefix("foo"), false);
2224    }
2225
2226    #[test]
2227    fn test_pathext_has_suffix() {
2228        let path = PathBuf::from("/foo/bar");
2229        assert_eq!(path.has_suffix("/foo"), false);
2230        assert_eq!(path.has_suffix("/bar"), true);
2231    }
2232
2233    #[test]
2234    fn test_pathext_is_dir() {
2235        let tmpdir = setup().mash("path_pathext_is_dir");
2236
2237        assert!(sys::remove_all(&tmpdir).is_ok());
2238        assert_eq!(tmpdir.is_dir(), false);
2239        assert!(sys::mkdir(&tmpdir).is_ok());
2240        assert_eq!(tmpdir.is_dir(), true);
2241
2242        // Clean up
2243        assert!(sys::remove_all(&tmpdir).is_ok());
2244    }
2245
2246    #[test]
2247    fn test_pathext_is_file() {
2248        let tmpdir = setup().mash("path_pathext_is_file");
2249        let tmpfile = tmpdir.mash("file1");
2250
2251        assert!(sys::remove_all(&tmpdir).is_ok());
2252        assert!(sys::mkdir(&tmpdir).is_ok());
2253        assert!(sys::touch(&tmpfile).is_ok());
2254        assert_eq!(tmpfile.is_file(), true);
2255
2256        // Clean up
2257        assert!(sys::remove_all(&tmpdir).is_ok());
2258    }
2259
2260    #[test]
2261    fn test_pathext_is_symlink_file() {
2262        let tmpdir = setup().mash("path_pathext_is_symlink_file");
2263        let file1 = tmpdir.mash("file1");
2264        let link1 = tmpdir.mash("link1");
2265
2266        assert!(sys::remove_all(&tmpdir).is_ok());
2267        assert!(sys::mkdir(&tmpdir).is_ok());
2268        assert_eq!(link1.is_symlink_file(), false);
2269        assert!(sys::touch(&file1).is_ok());
2270        assert!(sys::symlink(&link1, &file1).is_ok());
2271        assert_eq!(link1.is_symlink_file(), true);
2272
2273        // Clean up
2274        assert!(sys::remove_all(&tmpdir).is_ok());
2275    }
2276
2277    #[test]
2278    fn test_pathext_last() {
2279        assert_eq!(Component::RootDir, PathBuf::from("/").last().unwrap());
2280        assert_eq!(Component::CurDir, PathBuf::from(".").last().unwrap());
2281        assert_eq!(Component::ParentDir, PathBuf::from("..").last().unwrap());
2282        assert_eq!(Component::Normal(OsStr::new("foo")), PathBuf::from("foo").last().unwrap());
2283        assert_eq!(Component::Normal(OsStr::new("bar")), PathBuf::from("/foo/bar").last().unwrap());
2284    }
2285
2286    #[test]
2287    fn test_pathext_metadata() {
2288        let tmpdir = setup().mash("path_pathext_metadata");
2289
2290        assert!(sys::remove_all(&tmpdir).is_ok());
2291        assert!(tmpdir.metadata().is_err());
2292        assert!(sys::mkdir(&tmpdir).is_ok());
2293        assert!(tmpdir.metadata().is_ok());
2294
2295        // Clean up
2296        assert!(sys::remove_all(&tmpdir).is_ok());
2297    }
2298
2299    #[test]
2300    fn test_pathext_mash() {
2301        // strips off root on path
2302        assert_eq!(Path::new("/foo").mash("/bar"), PathBuf::from("/foo/bar"));
2303
2304        // strips off trailing slashes
2305        assert_eq!(Path::new("/foo").mash("bar/"), PathBuf::from("/foo/bar"));
2306    }
2307
2308    #[test]
2309    fn test_pathext_meta() {
2310        let meta = setup().metadata().unwrap();
2311        assert_eq!(meta.is_dir(), true);
2312    }
2313
2314    #[test]
2315    fn test_pathext_mode() {
2316        let tmpdir = setup().mash("path_pathbuf_mode");
2317        let file1 = tmpdir.mash("file1");
2318
2319        assert!(sys::remove_all(&tmpdir).is_ok());
2320        assert!(sys::mkdir(&tmpdir).is_ok());
2321        assert!(sys::touch(&file1).is_ok());
2322        assert!(file1.chmod(0o644).is_ok());
2323        assert_eq!(file1.mode().unwrap(), 0o100644);
2324        assert!(sys::remove_all(&tmpdir).is_ok());
2325    }
2326
2327    #[test]
2328    fn test_pathext_name() {
2329        assert!(PathBuf::from("").name().is_err());
2330        assert_eq!(PathBuf::from("foo").name().unwrap(), "foo");
2331        assert_eq!(PathBuf::from("foo.exe").name().unwrap(), "foo");
2332        assert_eq!(PathBuf::from("/foo/bar.exe").name().unwrap(), "bar");
2333    }
2334
2335    #[test]
2336    fn test_pathext_perms() {
2337        let tmpdir = setup().mash("path_pathbuf_perms");
2338        let file1 = tmpdir.mash("file1");
2339
2340        assert!(sys::remove_all(&tmpdir).is_ok());
2341        assert!(sys::mkdir(&tmpdir).is_ok());
2342        assert!(sys::touch(&file1).is_ok());
2343        assert!(file1.chmod(0o644).is_ok());
2344        assert_eq!(file1.perms().unwrap().mode(), 0o100644);
2345        assert!(sys::remove_all(&tmpdir).is_ok());
2346    }
2347
2348    #[test]
2349    fn test_pathext_setperms() {
2350        let tmpdir = setup().mash("path_pathbuf_setperms");
2351        let file1 = tmpdir.mash("file1");
2352
2353        assert!(sys::remove_all(&tmpdir).is_ok());
2354        assert!(sys::mkdir(&tmpdir).is_ok());
2355        assert!(sys::touch(&file1).is_ok());
2356        assert!(file1.chmod(0o644).is_ok());
2357        let mut perms = file1.perms().unwrap();
2358        assert_eq!(perms.mode(), 0o100644);
2359        perms.set_mode(0o555);
2360        assert!(file1.setperms(perms).is_ok());
2361        assert_eq!(file1.mode().unwrap(), 0o100555);
2362        assert!(sys::remove_all(&tmpdir).is_ok());
2363    }
2364
2365    #[test]
2366    fn test_pathext_relative_from() {
2367        let cwd = sys::cwd().unwrap();
2368
2369        // same directory
2370        assert_eq!(PathBuf::from("bar1").relative_from("bar1").unwrap(), cwd.mash("bar1"));
2371
2372        // share same directory
2373        assert_eq!(PathBuf::from("bar1").relative_from("bar2").unwrap(), PathBuf::from("bar1"));
2374        assert_eq!(PathBuf::from("foo/bar1").relative_from("foo/bar2").unwrap(), PathBuf::from("bar1"));
2375        assert_eq!(PathBuf::from("~/foo/bar1").relative_from("~/foo/bar2").unwrap(), PathBuf::from("bar1"));
2376        assert_eq!(PathBuf::from("../foo/bar1").relative_from("../foo/bar2").unwrap(), PathBuf::from("bar1"));
2377
2378        // share parent directory
2379        assert_eq!(PathBuf::from("foo1/bar1").relative_from("foo2/bar2").unwrap(), PathBuf::from("../foo1/bar1"));
2380
2381        // share grandparent directory
2382        assert_eq!(PathBuf::from("blah1/foo1/bar1").relative_from("blah2/foo2/bar2").unwrap(), PathBuf::from("../../blah1/foo1/bar1"));
2383    }
2384
2385    #[test]
2386    fn test_pathext_trim_ext() {
2387        assert_eq!(PathBuf::from("").trim_ext().unwrap(), PathBuf::new());
2388        assert_eq!(PathBuf::from("foo").trim_ext().unwrap(), PathBuf::from("foo"));
2389        assert_eq!(PathBuf::from("foo.exe").trim_ext().unwrap(), PathBuf::from("foo"));
2390        assert_eq!(PathBuf::from("/foo/bar.exe").trim_ext().unwrap(), PathBuf::from("/foo/bar"));
2391    }
2392
2393    #[test]
2394    fn test_pathext_trim_last() {
2395        assert_eq!(PathBuf::new(), PathBuf::from("/").trim_last());
2396        assert_eq!(PathBuf::from("/"), PathBuf::from("/foo").trim_last());
2397    }
2398
2399    #[test]
2400    fn test_pathext_trim_first() {
2401        assert_eq!(PathBuf::new(), PathBuf::from("/").trim_first());
2402        assert_eq!(PathBuf::from("foo"), PathBuf::from("/foo").trim_first());
2403    }
2404
2405    #[test]
2406    fn test_pathext_trim_prefix() {
2407        // drop root
2408        assert_eq!(PathBuf::from("/").trim_prefix("/"), PathBuf::new());
2409
2410        // drop start
2411        assert_eq!(Path::new("/foo/bar").trim_prefix("/foo"), PathBuf::from("/bar"));
2412
2413        // no change
2414        assert_eq!(PathBuf::from("/").trim_prefix(""), PathBuf::from("/"));
2415        assert_eq!(PathBuf::from("/foo").trim_prefix("blah"), PathBuf::from("/foo"));
2416    }
2417
2418    #[test]
2419    fn test_pathext_trim_protocol() {
2420        // no change
2421        assert_eq!(PathBuf::from("/foo"), PathBuf::from("/foo").trim_protocol());
2422
2423        // file://
2424        assert_eq!(PathBuf::from("/foo"), PathBuf::from("file:///foo").trim_protocol());
2425
2426        // ftp://
2427        assert_eq!(PathBuf::from("foo"), PathBuf::from("ftp://foo").trim_protocol());
2428
2429        // http://
2430        assert_eq!(PathBuf::from("foo"), PathBuf::from("http://foo").trim_protocol());
2431
2432        // https://
2433        assert_eq!(PathBuf::from("foo"), PathBuf::from("https://foo").trim_protocol());
2434
2435        // Check case is being considered
2436        assert_eq!(PathBuf::from("Foo"), PathBuf::from("HTTPS://Foo").trim_protocol());
2437        assert_eq!(PathBuf::from("Foo"), PathBuf::from("Https://Foo").trim_protocol());
2438        assert_eq!(PathBuf::from("FoO"), PathBuf::from("HttpS://FoO").trim_protocol());
2439
2440        // Check non protocol matches are ignored
2441        assert_eq!(PathBuf::from("foo"), PathBuf::from("foo").trim_protocol());
2442        assert_eq!(PathBuf::from("foo/bar"), PathBuf::from("foo/bar").trim_protocol());
2443        assert_eq!(PathBuf::from("foo//bar"), PathBuf::from("foo//bar").trim_protocol());
2444        assert_eq!(PathBuf::from("ntp:://foo"), PathBuf::from("ntp:://foo").trim_protocol());
2445    }
2446
2447    #[test]
2448    fn test_pathext_trim_suffix() {
2449        // drop root
2450        assert_eq!(PathBuf::new(), PathBuf::from("/").trim_suffix("/"));
2451
2452        // drop end
2453        assert_eq!(PathBuf::from("/foo"), PathBuf::from("/foo/").trim_suffix("/"));
2454
2455        // no change
2456        assert_eq!(PathBuf::from("/foo"), PathBuf::from("/foo").trim_suffix("/"));
2457    }
2458
2459    #[test]
2460    fn test_pathcolorext() {
2461        assert_eq!("foo".black(), PathBuf::from("foo").black());
2462        assert_eq!("foo".red(), PathBuf::from("foo").red());
2463        assert_eq!("foo".green(), PathBuf::from("foo").green());
2464        assert_eq!("foo".yellow(), PathBuf::from("foo").yellow());
2465        assert_eq!("foo".blue(), PathBuf::from("foo").blue());
2466        assert_eq!("foo".magenta(), PathBuf::from("foo").magenta());
2467        assert_eq!("foo".cyan(), PathBuf::from("foo").cyan());
2468        assert_eq!("foo".white(), PathBuf::from("foo").white());
2469    }
2470}