Skip to main content

cc_audit/parser/
markdown.rs

1//! Markdown content parser.
2
3use super::frontmatter::FrontmatterParser;
4use super::traits::{ContentParser, ContentType, ParsedContent};
5use crate::error::Result;
6
7/// Parser for Markdown files (SKILL.md, CLAUDE.md, commands, etc.).
8pub struct MarkdownParser;
9
10impl MarkdownParser {
11    /// Create a new Markdown parser.
12    pub fn new() -> Self {
13        Self
14    }
15
16    /// Extract the body content (after frontmatter).
17    pub fn extract_body(content: &str) -> &str {
18        if let Some(fm) = FrontmatterParser::extract(content) {
19            // Skip past frontmatter + closing ---
20            let fm_end = content.find("---").unwrap_or(0) + 3 + fm.len() + 3;
21            if fm_end < content.len() {
22                return &content[fm_end..];
23            }
24        }
25        content
26    }
27}
28
29impl Default for MarkdownParser {
30    fn default() -> Self {
31        Self::new()
32    }
33}
34
35impl ContentParser for MarkdownParser {
36    fn parse(&self, content: &str, path: &str) -> Result<ParsedContent> {
37        let mut parsed =
38            ParsedContent::new(ContentType::Markdown, content.to_string(), path.to_string());
39
40        // Extract frontmatter if present
41        if let Some(fm) = FrontmatterParser::extract(content) {
42            parsed = parsed.with_frontmatter(fm.to_string());
43
44            // Try to parse as structured YAML
45            if let Some(yaml) = FrontmatterParser::parse_json(content) {
46                parsed = parsed.with_structured_data(yaml);
47            }
48        }
49
50        Ok(parsed)
51    }
52
53    fn supported_extensions(&self) -> &[&str] {
54        &[".md", ".markdown"]
55    }
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61
62    #[test]
63    fn test_parse_markdown_with_frontmatter() {
64        let parser = MarkdownParser::new();
65        let content = "---\nname: test\n---\n# Content";
66        let result = parser.parse(content, "test.md").unwrap();
67
68        assert_eq!(result.content_type, ContentType::Markdown);
69        assert!(result.frontmatter.is_some());
70        assert!(result.structured_data.is_some());
71    }
72
73    #[test]
74    fn test_parse_markdown_without_frontmatter() {
75        let parser = MarkdownParser::new();
76        let content = "# Just Content\n\nNo frontmatter here.";
77        let result = parser.parse(content, "test.md").unwrap();
78
79        assert_eq!(result.content_type, ContentType::Markdown);
80        assert!(result.frontmatter.is_none());
81        assert!(result.structured_data.is_none());
82    }
83
84    #[test]
85    fn test_extract_body() {
86        let content = "---\nname: test\n---\n# Body Content";
87        let body = MarkdownParser::extract_body(content);
88        assert!(body.contains("Body Content"));
89    }
90
91    #[test]
92    fn test_supported_extensions() {
93        let parser = MarkdownParser::new();
94        assert!(parser.can_parse("SKILL.md"));
95        assert!(parser.can_parse("README.markdown"));
96        assert!(!parser.can_parse("config.json"));
97    }
98}