cc_audit/parser/
frontmatter.rs1pub struct FrontmatterParser;
5
6impl FrontmatterParser {
7 pub fn extract(content: &str) -> Option<&str> {
20 let after_open = content.strip_prefix("---")?;
24 if !after_open.starts_with('\n') && !after_open.starts_with("\r\n") {
25 return None;
26 }
27
28 let mut offset = 0;
34 for line in after_open.split_inclusive('\n') {
35 if line.trim_end_matches(['\r', '\n']).trim_end() == "---" {
36 return Some(&after_open[..offset]);
37 }
38 offset += line.len();
39 }
40
41 None
42 }
43
44 pub fn parse_yaml(content: &str) -> Option<serde_norway::Value> {
48 Self::extract(content).and_then(|fm| serde_norway::from_str(fm.trim()).ok())
49 }
50
51 pub fn parse_json(content: &str) -> Option<serde_json::Value> {
55 Self::extract(content)
56 .and_then(|fm| serde_norway::from_str::<serde_json::Value>(fm.trim()).ok())
57 }
58}
59
60#[cfg(test)]
61mod tests {
62 use super::*;
63
64 #[test]
65 fn test_valid_frontmatter() {
66 let content = "---\nname: test\ndescription: A test\n---\n# Content";
67 let result = FrontmatterParser::extract(content);
68 assert_eq!(result, Some("\nname: test\ndescription: A test\n"));
69 }
70
71 #[test]
72 fn test_frontmatter_with_allowed_tools() {
73 let content = "---\nname: skill\nallowed-tools: Read, Write\n---\n# Skill";
74 let result = FrontmatterParser::extract(content);
75 assert!(result.is_some());
76 assert!(result.unwrap().contains("allowed-tools"));
77 }
78
79 #[test]
80 fn test_no_frontmatter() {
81 let content = "# Just Markdown\nNo frontmatter here.";
82 assert!(FrontmatterParser::extract(content).is_none());
83 }
84
85 #[test]
86 fn test_incomplete_frontmatter() {
87 let content = "---\nname: test\nNo closing dashes";
88 assert!(FrontmatterParser::extract(content).is_none());
89 }
90
91 #[test]
92 fn test_thematic_break_is_not_empty_frontmatter() {
93 let content = "------\n# Content";
96 assert!(FrontmatterParser::extract(content).is_none());
97 }
98
99 #[test]
100 fn test_inline_dashes_in_value_do_not_truncate() {
101 let content = "---\ndescription: \"harmless a---b\"\nallowed-tools: *\n---\n# Body";
104 let result = FrontmatterParser::extract(content);
105 assert_eq!(
106 result,
107 Some("\ndescription: \"harmless a---b\"\nallowed-tools: *\n")
108 );
109 }
110
111 #[test]
112 fn test_closing_delimiter_must_be_own_line() {
113 let content = "---\nname: test\nvalue: a---b\nno closing line";
116 assert!(FrontmatterParser::extract(content).is_none());
117 }
118
119 #[test]
120 fn test_content_not_starting_with_dashes() {
121 let content = "# Title\n---\nname: test\n---";
122 assert!(FrontmatterParser::extract(content).is_none());
123 }
124
125 #[test]
126 fn test_parse_yaml() {
127 let content = "---\nname: test\nversion: 1.0\n---\n# Content";
128 let yaml = FrontmatterParser::parse_yaml(content);
129 assert!(yaml.is_some());
130 let yaml = yaml.unwrap();
131 assert_eq!(yaml["name"].as_str(), Some("test"));
132 }
133
134 #[test]
135 fn test_parse_json() {
136 let content = "---\nname: test\nversion: 1.0\n---\n# Content";
137 let json = FrontmatterParser::parse_json(content);
138 assert!(json.is_some());
139 let json = json.unwrap();
140 assert_eq!(json["name"].as_str(), Some("test"));
141 }
142}