rebecca-core 0.2.0

Core planning, safety, scanning, and history models for Rebecca.
Documentation
use std::path::{Component, Path};

pub(crate) fn paths_overlap(left: &Path, right: &Path) -> bool {
    same_or_child_path(left, right) || same_or_child_path(right, left)
}

fn same_or_child_path(parent: &Path, child: &Path) -> bool {
    let parent = comparable_components(parent);
    let child = comparable_components(child);
    !parent.is_empty() && child.len() >= parent.len() && child.starts_with(&parent)
}

fn comparable_components(path: &Path) -> Vec<String> {
    path.components()
        .filter_map(|component| match component {
            Component::Prefix(prefix) => Some(prefix.as_os_str().to_string_lossy().into_owned()),
            Component::RootDir => Some(std::path::MAIN_SEPARATOR.to_string()),
            Component::Normal(value) => Some(value.to_string_lossy().into_owned()),
            Component::ParentDir => Some("..".to_string()),
            Component::CurDir => None,
        })
        .map(|component| {
            if cfg!(windows) {
                component.to_ascii_lowercase()
            } else {
                component
            }
        })
        .collect()
}

#[cfg(test)]
mod tests {
    use std::path::Path;

    use super::paths_overlap;

    #[test]
    fn detects_same_child_and_parent_paths() {
        let temp = tempfile::tempdir().unwrap();
        let root = temp.path().join("Rebecca");
        let child = root.join("cache").join("scan");

        assert!(paths_overlap(&root, &root));
        assert!(paths_overlap(&root, &child));
        assert!(paths_overlap(&child, &root));
        assert!(!paths_overlap(&root, &temp.path().join("Other")));
    }

    #[cfg(windows)]
    #[test]
    fn compares_windows_paths_case_insensitively() {
        assert!(paths_overlap(
            Path::new(r"C:\Users\Alice\AppData\Local\Rebecca"),
            Path::new(r"c:\users\alice\appdata\local\rebecca\cache"),
        ));
    }
}