#[derive(Debug, Default, PartialEq)]
pub struct Front {
pub title: Option<String>,
pub name: Option<String>,
pub body: usize,
}
impl Front {
pub fn of(content: &str) -> Front {
let mut lines = content.split_inclusive('\n');
let Some(first) = lines.next().filter(|l| l.trim_end() == "---") else {
return Front::default();
};
let mut at = first.len();
let mut front = Front::default();
for line in lines {
at += line.len();
let l = line.trim_end();
if l == "---" {
front.body = at;
return front;
}
let Some((key, value)) = l.split_once(':') else {
continue;
};
let value = value.trim().trim_matches(|c| c == '"' || c == '\'').trim();
let slot = match key {
"title" => &mut front.title,
"name" => &mut front.name,
_ => continue,
};
if slot.is_none() && !value.is_empty() && !matches!(value, ">" | "|" | ">-" | "|-") {
*slot = Some(value.to_string());
}
}
Front::default()
}
}
pub fn title(content: &str) -> Option<String> {
let front = Front::of(content);
let body = &content[front.body..];
let h1 = || {
body.lines().take(40).find_map(|line| {
let h = line.trim().strip_prefix("# ")?;
let h = h.trim().trim_end_matches('#').trim();
(!h.is_empty()).then(|| h.to_string())
})
};
front.title.or_else(h1).or(front.name)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::render::{strip_leading_h1, title_for, Kind, Renderer};
const MEMORY: &str = "---\nname: account-ui-in-flight\ndescription: \"the audit\"\nmetadata:\n type: project\n---\n\nAudit doc 2dbcf2e538.\n";
#[test]
fn the_block_is_read_and_the_body_found() {
let f = Front::of(MEMORY);
assert_eq!(f.name.as_deref(), Some("account-ui-in-flight"));
assert_eq!(f.title, None);
assert!(MEMORY[f.body..].starts_with("\nAudit doc"));
let quoted = Front::of("---\r\ntitle: 'A plan'\r\n---\r\n# H1\r\n");
assert_eq!(quoted.title.as_deref(), Some("A plan"));
assert_eq!(
&"---\r\ntitle: 'A plan'\r\n---\r\n# H1\r\n"[quoted.body..],
"# H1\r\n"
);
assert_eq!(Front::of("---\ntitle: >\n folded\n---\n").title, None);
assert_eq!(Front::of("intro\n---\ntitle: x\n---\n"), Front::default());
assert_eq!(Front::of("---\ntitle: x\nno end\n"), Front::default());
}
#[test]
fn the_title_comes_from_the_frontmatter_before_the_file_name() {
let md = |c: &str| title_for(None, Kind::Markdown, Some("/p/memory/x.md"), c);
assert_eq!(md(MEMORY), "account-ui-in-flight");
assert_eq!(
md("---\ntitle: Real title\nname: slug\n---\n# H1\n"),
"Real title"
);
assert_eq!(md("---\nname: slug\n---\n# The heading\n"), "The heading");
assert_eq!(md("---\n# a comment\nlayout: post\n---\nbody\n"), "x.md");
assert_eq!(
title_for(Some("Given"), Kind::Markdown, None, MEMORY),
"Given"
);
}
#[test]
fn the_frontmatter_is_not_printed() {
let r = Renderer::new();
let html = r.render(Kind::Markdown, None, MEMORY);
assert!(!html.contains("<hr"), "{html}");
assert!(!html.contains("<h2"), "{html}");
assert!(!html.contains("account-ui-in-flight"), "{html}");
assert!(html.contains("Audit doc 2dbcf2e538."), "{html}");
let src = "---\ntitle: T\n---\n\n# Heading\n\nbody\n";
let body = strip_leading_h1(src, "T").unwrap();
assert_eq!(body, "---\ntitle: T\n---\n\nbody\n");
let html = r.render(Kind::Markdown, None, &body);
assert!(
!html.contains("<h1") && html.contains("<p>body</p>"),
"{html}"
);
}
}