section.rs 63 lignes · 2251 octets
//! Remplacement idempotent d'une section délimitée par des marqueurs HTML.
//! Permet de régénérer les blocs de sécurité lors d'un re-run du wizard sans
//! écraser le reste du document (charte, tableau, prose ajoutée par le dev).

/// Bloc encadré : `<!-- id -->\n{body}\n<!-- /id -->`.
pub fn block(id: &str, body: &str) -> String {
    format!("<!-- {id} -->\n{}\n<!-- /{id} -->", body.trim_end())
}

/// Insère (à la fin) ou remplace en place la section `id` par `body`.
pub fn upsert(content: &str, id: &str, body: &str) -> String {
    let block = block(id, body);
    let open = format!("<!-- {id} -->");
    let close = format!("<!-- /{id} -->");
    if let (Some(start), Some(close_at)) = (content.find(&open), content.find(&close)) {
        if start < close_at {
            let end = close_at + close.len();
            let mut out = String::with_capacity(content.len() + block.len());
            out.push_str(&content[..start]);
            out.push_str(&block);
            out.push_str(&content[end..]);
            return out;
        }
    }
    let mut out = content.to_string();
    if !out.ends_with('\n') {
        out.push('\n');
    }
    out.push('\n');
    out.push_str(&block);
    out.push('\n');
    out
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn block_encadre_le_corps() {
        let b = block("demo:x", "corps\n");
        assert_eq!(b, "<!-- demo:x -->\ncorps\n<!-- /demo:x -->");
    }

    #[test]
    fn upsert_ajoute_a_la_fin_si_absent() {
        let out = upsert("# Titre\n", "demo:x", "corps");
        assert!(out.starts_with("# Titre\n"));
        assert!(out.contains("<!-- demo:x -->\ncorps\n<!-- /demo:x -->"));
    }

    #[test]
    fn upsert_remplace_en_place_et_est_idempotent() {
        let base = format!("A\n\n{}\n\nB\n", block("demo:x", "v1"));
        let once = upsert(&base, "demo:x", "v2");
        assert!(once.contains("A\n"), "préfixe préservé");
        assert!(once.contains("B\n"), "suffixe préservé");
        assert!(once.contains("v2"));
        assert!(!once.contains("v1"), "ancien corps remplacé");
        // idempotence : ré-appliquer avec le même corps ne change rien.
        assert_eq!(upsert(&once, "demo:x", "v2"), once);
    }
}