Skip to main content

lean_ctx/
marked_block.rs

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}