tytanic_utils/path.rs
1//! Helper functions and types for paths.
2
3use std::path::Path;
4
5/// Returns the lexical common ancestor of two paths if there is any. This means
6/// it will not canonicalize paths.
7///
8/// # Example
9/// ```no_run
10/// # use std::path::Path;
11/// # use tytanic_utils::path::common_ancestor;
12/// assert_eq!(
13/// common_ancestor(Path::new("foo/bar"), Path::new("foo/baz")),
14/// Some(Path::new("foo")),
15/// );
16/// assert_eq!(
17/// common_ancestor(Path::new("foo/bar"), Path::new("/foo/baz")),
18/// None,
19/// );
20/// # Ok::<_, Box<dyn std::error::Error>>(())
21/// ```
22pub fn common_ancestor<'a>(p: &'a Path, q: &'a Path) -> Option<&'a Path> {
23 let mut paths = [p, q];
24 paths.sort_by_key(|p| p.as_os_str().len());
25 let [short, long] = paths;
26
27 // Find the longest match where long starts with short.
28 short.ancestors().find(|a| long.starts_with(a))
29}
30
31/// Returns whether `base` is a lexical ancestor of `path`. This means it will
32/// not canonicalize paths and may not always be correct (e.g. in the presence
33/// of symlinks or filesystem mounts).
34///
35/// # Example
36/// ```no_run
37/// # use tytanic_utils::path::is_ancestor_of;
38/// assert_eq!(is_ancestor_of("foo/", "foo/baz"), true);
39/// assert_eq!(is_ancestor_of("foo/", "/foo/baz"), false);
40/// # Ok::<_, Box<dyn std::error::Error>>(())
41/// ```
42pub fn is_ancestor_of<P: AsRef<Path>, Q: AsRef<Path>>(base: P, path: Q) -> bool {
43 fn inner(base: &Path, path: &Path) -> bool {
44 common_ancestor(base, path).is_some_and(|common| common == base)
45 }
46
47 inner(base.as_ref(), path.as_ref())
48}