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            eprintln!("  Installed {label}");
32        }
33    }
34}
35
36pub fn remove_from_file(path: &Path, start: &str, end: &str, quiet: bool, label: &str) {
37    let Ok(existing) = std::fs::read_to_string(path) else {
38        return;
39    };
40    if !existing.contains(start) {
41        return;
42    }
43    let cleaned = remove_content(&existing, start, end);
44    std::fs::write(path, cleaned.trim_end().to_owned() + "\n").ok();
45    if !quiet {
46        println!("  Removed {label}");
47    }
48}
49
50pub fn remove_content(content: &str, start: &str, end: &str) -> String {
51    let s = content.find(start);
52    let e = content.find(end);
53    match (s, e) {
54        (Some(si), Some(ei)) if ei >= si => {
55            let after_end = ei + end.len();
56            let before = content[..si].trim_end_matches('\n');
57            let after = content[after_end..].trim_start_matches('\n');
58            let mut out = before.to_string();
59            if !after.is_empty() {
60                out.push('\n');
61                out.push_str(after);
62            }
63            out
64        }
65        _ => content.to_string(),
66    }
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72
73    #[test]
74    fn remove_content_works() {
75        let content = "before\n# >>> start >>>\nhook content\n# <<< end <<<\nafter\n";
76        let cleaned = remove_content(content, "# >>> start >>>", "# <<< end <<<");
77        assert!(!cleaned.contains("hook content"));
78        assert!(cleaned.contains("before"));
79        assert!(cleaned.contains("after"));
80    }
81
82    #[test]
83    fn remove_content_preserves_when_missing() {
84        let content = "no hook here\n";
85        let cleaned = remove_content(content, "# >>> start >>>", "# <<< end <<<");
86        assert_eq!(cleaned, content);
87    }
88}