1use std::path::Path;
2
3pub fn upsert(path: &Path, start: &str, end: &str, block: &str, quiet: bool, label: &str) {
4 let existing = std::fs::read_to_string(path).unwrap_or_default();
5
6 if existing.contains(start) {
7 let cleaned = remove_content(&existing, start, end);
8 let mut out = cleaned.trim_end().to_string();
9 if !out.is_empty() {
10 out.push('\n');
11 }
12 out.push('\n');
13 out.push_str(block);
14 out.push('\n');
15 std::fs::write(path, &out).ok();
16 if !quiet {
17 println!(" Updated {label}");
18 }
19 } else {
20 let mut out = existing;
21 if !out.is_empty() && !out.ends_with('\n') {
22 out.push('\n');
23 }
24 if !out.is_empty() {
25 out.push('\n');
26 }
27 out.push_str(block);
28 out.push('\n');
29 std::fs::write(path, &out).ok();
30 if !quiet {
31 println!(" Installed {label}");
32 }
33 }
34}
35
36pub fn remove_from_file(path: &Path, start: &str, end: &str, quiet: bool, label: &str) {
37 let existing = match std::fs::read_to_string(path) {
38 Ok(c) => c,
39 Err(_) => return,
40 };
41 if !existing.contains(start) {
42 return;
43 }
44 let cleaned = remove_content(&existing, start, end);
45 std::fs::write(path, cleaned.trim_end().to_owned() + "\n").ok();
46 if !quiet {
47 println!(" Removed {label}");
48 }
49}
50
51pub fn remove_content(content: &str, start: &str, end: &str) -> String {
52 let s = content.find(start);
53 let e = content.find(end);
54 match (s, e) {
55 (Some(si), Some(ei)) if ei >= si => {
56 let after_end = ei + end.len();
57 let before = content[..si].trim_end_matches('\n');
58 let after = content[after_end..].trim_start_matches('\n');
59 let mut out = before.to_string();
60 if !after.is_empty() {
61 out.push('\n');
62 out.push_str(after);
63 }
64 out
65 }
66 _ => content.to_string(),
67 }
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73
74 #[test]
75 fn remove_content_works() {
76 let content = "before\n# >>> start >>>\nhook content\n# <<< end <<<\nafter\n";
77 let cleaned = remove_content(content, "# >>> start >>>", "# <<< end <<<");
78 assert!(!cleaned.contains("hook content"));
79 assert!(cleaned.contains("before"));
80 assert!(cleaned.contains("after"));
81 }
82
83 #[test]
84 fn remove_content_preserves_when_missing() {
85 let content = "no hook here\n";
86 let cleaned = remove_content(content, "# >>> start >>>", "# <<< end <<<");
87 assert_eq!(cleaned, content);
88 }
89}