spool-memory 0.1.1

Local-first developer memory system — persistent, structured knowledge for AI coding tools
Documentation
use anyhow::Context;
use std::collections::BTreeMap;

pub fn split_frontmatter(
    raw: &str,
) -> anyhow::Result<(BTreeMap<String, serde_json::Value>, String)> {
    let mut lines = raw.lines();
    if lines.next() != Some("---") {
        return Ok((BTreeMap::new(), raw.to_string()));
    }

    let mut yaml = Vec::new();
    let mut body = Vec::new();
    let mut in_frontmatter = true;

    for line in raw.lines().skip(1) {
        if in_frontmatter && line == "---" {
            in_frontmatter = false;
            continue;
        }

        if in_frontmatter {
            yaml.push(line);
        } else {
            body.push(line);
        }
    }

    if in_frontmatter {
        anyhow::bail!("frontmatter started with --- but was not closed with ---");
    }

    let yaml_str = yaml.join("\n");
    if yaml_str.trim().is_empty() {
        return Ok((BTreeMap::new(), body.join("\n")));
    }

    let yaml_value = serde_yaml::from_str::<serde_yaml::Value>(&yaml_str)
        .with_context(|| "failed to parse frontmatter yaml")?;
    let parsed = match yaml_value {
        serde_yaml::Value::Null => BTreeMap::new(),
        other => {
            let json_value = serde_json::to_value(other)
                .with_context(|| "failed to convert frontmatter yaml to json")?;
            serde_json::from_value(json_value)
                .with_context(|| "failed to deserialize frontmatter object")?
        }
    };

    Ok((parsed, body.join("\n")))
}