kardo_core/parser/
frontmatter.rs1use std::collections::HashMap;
6
7pub fn parse_frontmatter(content: &str) -> Option<HashMap<String, String>> {
13 if !content.starts_with("---\n") && !content.starts_with("---\r\n") {
15 return None;
16 }
17
18 let after_first = if let Some(stripped) = content.strip_prefix("---\r\n") {
20 stripped
21 } else {
22 &content[4..]
23 };
24
25 let end_pos = after_first.find("\n---")?;
27 let frontmatter_str = &after_first[..end_pos];
28
29 let mut map = HashMap::new();
30
31 for line in frontmatter_str.lines() {
32 let line = line.trim();
33 if line.is_empty() || line.starts_with('#') {
34 continue;
35 }
36
37 if let Some((key, value)) = line.split_once(':') {
38 let key = key.trim().to_string();
39 let value = value.trim();
40
41 let value = if (value.starts_with('"') && value.ends_with('"'))
43 || (value.starts_with('\'') && value.ends_with('\''))
44 {
45 value[1..value.len() - 1].to_string()
46 } else {
47 value.to_string()
48 };
49
50 if !key.is_empty() {
51 map.insert(key, value);
52 }
53 }
54 }
55
56 if map.is_empty() {
57 None
58 } else {
59 Some(map)
60 }
61}
62
63#[cfg(test)]
64mod tests {
65 use super::*;
66
67 #[test]
68 fn test_simple_frontmatter() {
69 let content = "---\ntitle: Hello World\nauthor: Alice\n---\n\n# Content\n";
70 let fm = parse_frontmatter(content).unwrap();
71 assert_eq!(fm.get("title").unwrap(), "Hello World");
72 assert_eq!(fm.get("author").unwrap(), "Alice");
73 }
74
75 #[test]
76 fn test_quoted_values() {
77 let content = "---\ntitle: \"My Doc\"\nstatus: 'draft'\n---\n\nBody\n";
78 let fm = parse_frontmatter(content).unwrap();
79 assert_eq!(fm.get("title").unwrap(), "My Doc");
80 assert_eq!(fm.get("status").unwrap(), "draft");
81 }
82
83 #[test]
84 fn test_no_frontmatter() {
85 let content = "# Just a heading\n\nSome text.\n";
86 assert!(parse_frontmatter(content).is_none());
87 }
88
89 #[test]
90 fn test_empty_frontmatter() {
91 let content = "---\n---\n\nContent.\n";
92 assert!(parse_frontmatter(content).is_none());
93 }
94
95 #[test]
96 fn test_frontmatter_with_comments() {
97 let content = "---\ntitle: Test\n# This is a comment\nauthor: Bob\n---\n\n";
98 let fm = parse_frontmatter(content).unwrap();
99 assert_eq!(fm.len(), 2);
100 assert_eq!(fm.get("title").unwrap(), "Test");
101 assert_eq!(fm.get("author").unwrap(), "Bob");
102 }
103
104 #[test]
105 fn test_no_closing_marker() {
106 let content = "---\ntitle: Missing close\n";
107 assert!(parse_frontmatter(content).is_none());
108 }
109
110 #[test]
111 fn test_value_with_colon() {
112 let content = "---\nurl: https://example.com\n---\n\nBody\n";
113 let fm = parse_frontmatter(content).unwrap();
114 assert_eq!(fm.get("url").unwrap(), "https://example.com");
115 }
116}