#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Scanned<'a> {
pub frontmatter: Option<&'a str>,
pub body: &'a str,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct Unterminated;
pub(crate) fn scan(raw: &str) -> Result<Scanned<'_>, Unterminated> {
let raw = raw.strip_prefix('\u{feff}').unwrap_or(raw);
let Some(opening) = raw
.strip_prefix("---\r\n")
.or_else(|| raw.strip_prefix("---\n"))
else {
return Ok(Scanned {
frontmatter: None,
body: raw,
});
};
let mut cursor = 0usize;
for segment in opening.split_inclusive('\n') {
if segment.trim_end_matches(['\n', '\r']) == "---" {
return Ok(Scanned {
frontmatter: Some(&opening[..cursor]),
body: &opening[cursor + segment.len()..],
});
}
cursor += segment.len();
}
if opening[cursor..].trim_end_matches('\r') == "---" {
return Ok(Scanned {
frontmatter: Some(&opening[..cursor]),
body: "",
});
}
Err(Unterminated)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_file_without_frontmatter_is_all_body() {
let scanned = scan("just prose\n").expect("scans");
assert_eq!(scanned.frontmatter, None);
assert_eq!(scanned.body, "just prose\n");
}
#[test]
fn frontmatter_is_separated_from_the_body() {
let scanned = scan("---\nkey: value\n---\nbody\n").expect("scans");
assert_eq!(scanned.frontmatter, Some("key: value\n"));
assert_eq!(scanned.body, "body\n");
}
#[test]
fn crlf_line_endings_are_understood() {
let scanned = scan("---\r\nkey: value\r\n---\r\nbody\r\n").expect("scans");
assert_eq!(scanned.frontmatter, Some("key: value\r\n"));
assert_eq!(scanned.body, "body\r\n");
}
#[test]
fn a_leading_byte_order_mark_does_not_hide_the_frontmatter() {
let scanned = scan("\u{feff}---\nkey: value\n---\nbody\n").expect("scans");
assert_eq!(scanned.frontmatter, Some("key: value\n"));
}
#[test]
fn a_closing_delimiter_at_end_of_file_leaves_an_empty_body() {
let scanned = scan("---\nkey: value\n---").expect("scans");
assert_eq!(scanned.frontmatter, Some("key: value\n"));
assert_eq!(scanned.body, "");
}
#[test]
fn unterminated_frontmatter_is_an_error() {
assert_eq!(scan("---\nkey: value\nbody\n"), Err(Unterminated));
}
#[test]
fn empty_frontmatter_scans_as_an_empty_block_not_as_none() {
let scanned = scan("---\n---\nbody\n").expect("scans");
assert_eq!(scanned.frontmatter, Some(""));
assert_eq!(scanned.body, "body\n");
}
}