spool/vault/
frontmatter.rs1use anyhow::Context;
2use std::collections::BTreeMap;
3
4pub fn split_frontmatter(
5 raw: &str,
6) -> anyhow::Result<(BTreeMap<String, serde_json::Value>, String)> {
7 let mut lines = raw.lines();
8 if lines.next() != Some("---") {
9 return Ok((BTreeMap::new(), raw.to_string()));
10 }
11
12 let mut yaml = Vec::new();
13 let mut body = Vec::new();
14 let mut in_frontmatter = true;
15
16 for line in raw.lines().skip(1) {
17 if in_frontmatter && line == "---" {
18 in_frontmatter = false;
19 continue;
20 }
21
22 if in_frontmatter {
23 yaml.push(line);
24 } else {
25 body.push(line);
26 }
27 }
28
29 if in_frontmatter {
30 anyhow::bail!("frontmatter started with --- but was not closed with ---");
31 }
32
33 let yaml_str = yaml.join("\n");
34 if yaml_str.trim().is_empty() {
35 return Ok((BTreeMap::new(), body.join("\n")));
36 }
37
38 let yaml_value = serde_yaml::from_str::<serde_yaml::Value>(&yaml_str)
39 .with_context(|| "failed to parse frontmatter yaml")?;
40 let parsed = match yaml_value {
41 serde_yaml::Value::Null => BTreeMap::new(),
42 other => {
43 let json_value = serde_json::to_value(other)
44 .with_context(|| "failed to convert frontmatter yaml to json")?;
45 serde_json::from_value(json_value)
46 .with_context(|| "failed to deserialize frontmatter object")?
47 }
48 };
49
50 Ok((parsed, body.join("\n")))
51}