use_markdown/
frontmatter.rs1pub fn extract_frontmatter(markdown: &str) -> Option<&str> {
3 let bounds = detect_frontmatter_bounds(markdown)?;
4 Some(&markdown[bounds.content_start..bounds.content_end])
5}
6
7pub fn has_frontmatter(markdown: &str) -> bool {
9 detect_frontmatter_bounds(markdown).is_some()
10}
11
12pub fn strip_frontmatter(markdown: &str) -> &str {
14 detect_frontmatter_bounds(markdown)
15 .map(|bounds| &markdown[bounds.full_end..])
16 .unwrap_or(markdown)
17}
18
19#[derive(Clone, Copy, Debug, Eq, PartialEq)]
20pub(crate) struct FrontmatterBounds {
21 pub content_start: usize,
22 pub content_end: usize,
23 pub full_end: usize,
24 pub line_count: usize,
25}
26
27pub(crate) fn frontmatter_line_count(markdown: &str) -> usize {
28 detect_frontmatter_bounds(markdown)
29 .map(|bounds| bounds.line_count)
30 .unwrap_or(0)
31}
32
33pub(crate) fn detect_frontmatter_bounds(markdown: &str) -> Option<FrontmatterBounds> {
34 let bom_length = if markdown.starts_with('\u{feff}') {
35 '\u{feff}'.len_utf8()
36 } else {
37 0
38 };
39
40 let (_, first_line_end, first_line) = next_line(markdown, bom_length)?;
41 let fence = match first_line.trim() {
42 "---" => "---",
43 "+++" => "+++",
44 _ => return None,
45 };
46
47 let content_start = first_line_end;
48 let mut line_count = 1usize;
49 let mut offset = first_line_end;
50
51 while let Some((line_start, line_end, line)) = next_line(markdown, offset) {
52 line_count += 1;
53 if line.trim() == fence {
54 let content_end = trim_trailing_line_breaks(&markdown[content_start..line_start]).len()
55 + content_start;
56 return Some(FrontmatterBounds {
57 content_start,
58 content_end,
59 full_end: line_end,
60 line_count,
61 });
62 }
63
64 offset = line_end;
65 }
66
67 None
68}
69
70fn trim_trailing_line_breaks(input: &str) -> &str {
71 input.trim_end_matches(['\r', '\n'])
72}
73
74fn next_line(markdown: &str, start: usize) -> Option<(usize, usize, &str)> {
75 if start >= markdown.len() {
76 return None;
77 }
78
79 let remainder = &markdown[start..];
80 if let Some(relative_end) = remainder.find('\n') {
81 let line_end = start + relative_end + 1;
82 let mut content_end = start + relative_end;
83 if relative_end > 0 && remainder.as_bytes()[relative_end - 1] == b'\r' {
84 content_end -= 1;
85 }
86
87 return Some((start, line_end, &markdown[start..content_end]));
88 }
89
90 Some((start, markdown.len(), &markdown[start..]))
91}