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 an ancestor of `path` lexically. This means it
32/// will not canonicalize paths and may not always be correct (e.g. in the
33/// presence 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}