Skip to main content

specfold_core/markdown/frontmatter/
split.rs

1//! Splits a Markdown file into its frontmatter block and its body.
2
3pub struct Split<'a> {
4    /// Raw frontmatter text without the fences, if the file has one.
5    pub raw: Option<&'a str>,
6    pub body: &'a str,
7    /// 1-based line where the body starts.
8    pub body_line: usize,
9}
10
11pub fn split(text: &str) -> Split<'_> {
12    let text = text.strip_prefix('\u{feff}').unwrap_or(text);
13    let Some(rest) = text.strip_prefix("---\n").or_else(|| text.strip_prefix("---\r\n")) else {
14        return Split { raw: None, body: text, body_line: 1 };
15    };
16    let mut offset = 0;
17    for line in rest.split_inclusive('\n') {
18        let bare = line.trim_end_matches(['\n', '\r']);
19        if bare == "---" || bare == "..." {
20            let raw = &rest[..offset];
21            return Split { raw: Some(raw), body: &rest[offset + line.len()..], body_line: raw.lines().count() + 3 };
22        }
23        offset += line.len();
24    }
25    Split { raw: None, body: text, body_line: 1 }
26}
27
28#[cfg(test)]
29mod tests {
30    use super::*;
31
32    #[test]
33    fn splits_block() {
34        let s = split("---\nid: x\nrel:\n  a: b\n---\n# Title\n");
35        assert_eq!(s.raw, Some("id: x\nrel:\n  a: b\n"));
36        assert_eq!(s.body_line, 6);
37        assert!(s.body.starts_with("# Title"));
38    }
39
40    #[test]
41    fn no_block() {
42        assert!(split("# Just a title\n").raw.is_none());
43        assert!(split("---\nunclosed\n").raw.is_none());
44    }
45}