Skip to main content

tokmd_exclude/
lib.rs

1//! Deterministic exclude-pattern normalization and dedupe helpers.
2
3#![forbid(unsafe_code)]
4
5use std::path::Path;
6
7use tokmd_path::normalize_rel_path;
8
9/// Normalize an exclude path into a deterministic pattern.
10///
11/// Rules:
12/// - if `path` is absolute and under `root`, strip the `root` prefix
13/// - convert backslashes to `/`
14/// - strip one leading `./`
15#[must_use]
16pub fn normalize_exclude_pattern(root: &Path, path: &Path) -> String {
17    let rel = if path.is_absolute() {
18        path.strip_prefix(root).unwrap_or(path)
19    } else {
20        path
21    };
22    normalize_rel_path(&rel.to_string_lossy())
23}
24
25/// Return `true` when `existing` already contains `pattern` after normalization.
26#[must_use]
27pub fn has_exclude_pattern(existing: &[String], pattern: &str) -> bool {
28    let normalized = normalize_rel_path(pattern);
29    existing
30        .iter()
31        .any(|candidate| normalize_rel_path(candidate) == normalized)
32}
33
34/// Add a pattern only when non-empty and not already present (after normalization).
35///
36/// Returns `true` when the pattern was inserted.
37pub fn add_exclude_pattern(existing: &mut Vec<String>, pattern: String) -> bool {
38    if pattern.is_empty() || has_exclude_pattern(existing, &pattern) {
39        return false;
40    }
41    existing.push(pattern);
42    true
43}
44
45#[cfg(test)]
46mod tests {
47    use super::*;
48
49    #[test]
50    fn normalize_exclude_pattern_strips_root_for_absolute_paths() {
51        let root = std::env::temp_dir().join("tokmd-exclude-lib-root");
52        let path = root.join(".handoff").join("manifest.json");
53        let got = normalize_exclude_pattern(&root, &path);
54        assert_eq!(got, ".handoff/manifest.json");
55    }
56
57    #[test]
58    fn normalize_exclude_pattern_keeps_outside_absolute_paths() {
59        let root = std::env::temp_dir().join("tokmd-exclude-lib-root");
60        let outside = std::env::temp_dir()
61            .join("tokmd-exclude-lib-outside")
62            .join("bundle.txt");
63        let got = normalize_exclude_pattern(&root, &outside);
64        let expected = tokmd_path::normalize_rel_path(&outside.to_string_lossy());
65        assert_eq!(got, expected);
66    }
67
68    #[test]
69    fn add_exclude_pattern_dedupes_after_normalization() {
70        let mut existing = vec!["./out\\bundle".to_string()];
71        assert!(!add_exclude_pattern(
72            &mut existing,
73            "out/bundle".to_string()
74        ));
75        assert_eq!(existing.len(), 1);
76    }
77
78    #[test]
79    fn add_exclude_pattern_rejects_empty_patterns() {
80        let mut existing = vec![];
81        assert!(!add_exclude_pattern(&mut existing, String::new()));
82        assert!(existing.is_empty());
83    }
84}