use super::error::LoadError;
pub fn split(raw: &str) -> Result<(serde_json::Map<String, serde_json::Value>, String), LoadError> {
let trimmed = raw.trim_start();
if !trimmed.starts_with("---") && !trimmed.starts_with("+++") {
return Ok((serde_json::Map::new(), raw.to_string()));
}
let opener = &trimmed[..3];
let after_opener = match trimmed[3..].find('\n') {
Some(pos) => 3 + pos + 1,
None => {
return Ok((serde_json::Map::new(), String::new()));
}
};
let rest = &trimmed[after_opener..];
let close_pos = find_closing_delimiter(rest, opener);
match close_pos {
Some(pos) => {
let yaml_str = &rest[..pos];
let after_close = &rest[pos..];
let body = match after_close.find('\n') {
Some(nl) => &after_close[nl + 1..],
None => "",
};
let frontmatter = parse_yaml(yaml_str)?;
Ok((frontmatter, body.to_string()))
}
None => Err(LoadError::InvalidFrontmatter(
"Opening delimiter without closing match".to_string(),
)),
}
}
fn find_closing_delimiter(text: &str, _opener: &str) -> Option<usize> {
for (i, line) in text.split('\n').scan(0usize, |pos, line| {
let start = *pos;
*pos += line.len() + 1; Some((start, line))
}) {
let trimmed_line = line.trim();
if trimmed_line == "---" || trimmed_line == "+++" {
return Some(i);
}
}
None
}
fn parse_yaml(yaml: &str) -> Result<serde_json::Map<String, serde_json::Value>, LoadError> {
let trimmed = yaml.trim();
if trimmed.is_empty() {
return Ok(serde_json::Map::new());
}
let value: serde_json::Value =
serde_yaml::from_str(trimmed).map_err(|e| LoadError::InvalidFrontmatter(e.to_string()))?;
match value {
serde_json::Value::Object(map) => Ok(map),
_ => Err(LoadError::InvalidFrontmatter(
"Frontmatter must be a YAML mapping".to_string(),
)),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_split() {
let raw = "---\nname: test\n---\nHello world.";
let (fm, body) = split(raw).unwrap();
assert_eq!(fm["name"], "test");
assert_eq!(body, "Hello world.");
}
#[test]
fn test_no_frontmatter() {
let raw = "Just a body with no frontmatter.";
let (fm, body) = split(raw).unwrap();
assert!(fm.is_empty());
assert_eq!(body, raw);
}
#[test]
fn test_empty_frontmatter() {
let raw = "---\n---\nBody here.";
let (fm, body) = split(raw).unwrap();
assert!(fm.is_empty());
assert_eq!(body, "Body here.");
}
#[test]
fn test_missing_closing_delimiter() {
let raw = "---\nname: test\nNo closing.";
let result = split(raw);
assert!(result.is_err());
}
#[test]
fn test_multiline_body() {
let raw = "---\nname: test\n---\nline1\nline2\nline3";
let (fm, body) = split(raw).unwrap();
assert_eq!(fm["name"], "test");
assert_eq!(body, "line1\nline2\nline3");
}
}