use serde::de::DeserializeOwned;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Frontmatter {
Toml,
Yaml,
}
pub(crate) fn split_frontmatter(contents: &str) -> Option<(Frontmatter, &str, &str)> {
let rest = contents.trim_start_matches(['\u{feff}']).trim_start(); let (kind, fence) = if rest.starts_with("+++") {
(Frontmatter::Toml, "+++")
} else if rest.starts_with("---") {
(Frontmatter::Yaml, "---")
} else {
return None;
};
let rest = &rest[fence.len()..];
let rest = rest
.strip_prefix('\n')
.or_else(|| rest.strip_prefix("\r\n"))?;
let close = find_closing_fence(rest, fence)?;
let frontmatter = &rest[..close.start];
let body = rest[close.end..].trim();
Some((kind, frontmatter, body))
}
pub(crate) fn parse_frontmatter<T: DeserializeOwned>(
kind: Frontmatter,
text: &str,
) -> Result<T, String> {
match kind {
Frontmatter::Toml => toml::from_str(text).map_err(|e| e.to_string()),
#[cfg(feature = "yaml")]
Frontmatter::Yaml => serde_yaml::from_str(text).map_err(|e| e.to_string()),
#[cfg(not(feature = "yaml"))]
Frontmatter::Yaml => Err("YAML frontmatter (`---`) requires the `yaml` feature; \
rebuild with `--features yaml`, or use `+++` TOML frontmatter"
.to_string()),
}
}
struct Fence {
start: usize,
end: usize,
}
fn find_closing_fence(s: &str, fence: &str) -> Option<Fence> {
let mut offset = 0;
for line in s.split_inclusive('\n') {
let trimmed = line.trim_end_matches(['\n', '\r']);
if trimmed.trim() == fence {
return Some(Fence {
start: offset,
end: offset + line.len(),
});
}
offset += line.len();
}
None
}