1#![forbid(unsafe_code)]
4
5use std::path::Path;
6
7use tokmd_path::normalize_rel_path;
8
9#[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#[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
34pub 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}