mdlint/markdown/
front_matter.rs1#[derive(Debug, Clone, PartialEq, Eq)]
2pub enum FrontMatterType {
3 Yaml,
4 Toml,
5}
6
7const YAML_FRONT_MATTER: &str = "---";
8const TOML_FRONT_MATTER: &str = "+++";
9
10#[derive(Debug, Clone)]
11pub struct FrontMatter {
12 pub matter_type: FrontMatterType,
13 pub content: String,
14 pub end_line: usize,
15}
16
17pub fn detect_front_matter(content: &str) -> Option<FrontMatter> {
18 let lines: Vec<&str> = content.lines().collect();
19 if lines.is_empty() {
20 return None;
21 }
22
23 detect_filetype_front_matter(&lines, FrontMatterType::Yaml)
24 .or_else(|| detect_filetype_front_matter(&lines, FrontMatterType::Toml))
25}
26
27fn detect_filetype_front_matter(
28 lines: &[&str],
29 matter_type: FrontMatterType,
30) -> Option<FrontMatter> {
31 if lines.is_empty() {
32 return None;
33 }
34
35 let front_matter = match matter_type {
36 FrontMatterType::Yaml => YAML_FRONT_MATTER,
37 FrontMatterType::Toml => TOML_FRONT_MATTER,
38 };
39 if lines[0] != front_matter {
40 return None;
41 }
42
43 for (i, line) in lines.iter().enumerate().skip(1) {
44 if *line != front_matter {
45 continue;
46 }
47 let content = lines[1..i].join("\n");
48 return Some(FrontMatter {
49 matter_type,
50 content,
51 end_line: i + 1,
52 });
53 }
54
55 None
56}
57
58#[allow(dead_code)]
59pub fn strip_front_matter(content: &str) -> String {
60 if let Some(front_matter) = detect_front_matter(content) {
61 let lines: Vec<&str> = content.lines().collect();
62 lines[front_matter.end_line..].join("\n")
63 } else {
64 content.to_string()
65 }
66}
67
68#[cfg(test)]
69mod tests {
70 use super::*;
71
72 #[test]
73 fn test_detect_yaml_front_matter() {
74 let content = "---\ntitle: Test\nauthor: John\n---\n# Heading";
75 let fm = detect_front_matter(content).unwrap();
76
77 assert_eq!(fm.matter_type, FrontMatterType::Yaml);
78 assert_eq!(fm.content, "title: Test\nauthor: John");
79 assert_eq!(fm.end_line, 4);
80 }
81
82 #[test]
83 fn test_detect_toml_front_matter() {
84 let content = "+++\ntitle = \"Test\"\nauthor = \"John\"\n+++\n# Heading";
85 let fm = detect_front_matter(content).unwrap();
86
87 assert_eq!(fm.matter_type, FrontMatterType::Toml);
88 assert_eq!(fm.content, "title = \"Test\"\nauthor = \"John\"");
89 assert_eq!(fm.end_line, 4);
90 }
91
92 #[test]
93 fn test_no_front_matter() {
94 let content = "# Heading\nSome content";
95 assert!(detect_front_matter(content).is_none());
96 }
97
98 #[test]
99 fn test_incomplete_front_matter() {
100 let content = "---\ntitle: Test\n# Heading";
101 assert!(detect_front_matter(content).is_none());
102 }
103
104 #[test]
105 fn test_strip_front_matter() {
106 let content = "---\ntitle: Test\n---\n# Heading\nContent";
107 let stripped = strip_front_matter(content);
108
109 assert_eq!(stripped, "# Heading\nContent");
110 }
111
112 #[test]
113 fn test_strip_no_front_matter() {
114 let content = "# Heading\nContent";
115 let stripped = strip_front_matter(content);
116
117 assert_eq!(stripped, content);
118 }
119}