Skip to main content

ailint_core/parser/
mod.rs

1//! Parsers for each supported guidance format.
2
3pub mod json;
4pub mod markdown;
5pub mod source_comments;
6pub mod yaml;
7
8use std::path::Path;
9
10use anyhow::Result;
11
12use crate::file_type::FileType;
13
14/// A parsed guidance document — the input to the rule engine.
15#[derive(Debug, Clone)]
16pub struct ParsedDocument {
17    /// Path the document was read from.
18    pub path: std::path::PathBuf,
19    /// Detected guidance file type.
20    pub file_type: FileType,
21    /// Full original file contents.
22    pub raw: String,
23    /// Format-specific parsed representation.
24    pub content: DocumentContent,
25}
26
27/// Format-specific parsed representation for a document.
28#[derive(Debug, Clone)]
29pub enum DocumentContent {
30    /// Parsed Markdown structure.
31    Markdown(markdown::MarkdownDoc),
32    /// Parsed YAML value.
33    Yaml(serde_yaml::Value),
34    /// Parsed JSON value.
35    Json(serde_json::Value),
36    /// The file was recognized as YAML or JSON but failed to parse. The
37    /// stored string is the parser error message so rules like
38    /// `malformed-yaml` (AIL041) can surface it.
39    ParseError(String),
40    /// Plain text with no recognized structure.
41    Text,
42    /// A zero-length or whitespace-only file.
43    Empty,
44}
45
46/// Read and parse a guidance file according to its detected type.
47pub fn parse(path: &Path, file_type: FileType) -> Result<ParsedDocument> {
48    let raw = std::fs::read_to_string(path)?;
49    let content = dispatch(&raw, file_type, path);
50    Ok(ParsedDocument {
51        path: path.to_path_buf(),
52        file_type,
53        raw,
54        content,
55    })
56}
57
58fn dispatch(raw: &str, file_type: FileType, path: &Path) -> DocumentContent {
59    if raw.is_empty() {
60        return DocumentContent::Empty;
61    }
62    let ext = path
63        .extension()
64        .and_then(|s| s.to_str())
65        .map(|s| s.to_ascii_lowercase());
66    let ext_str = ext.as_deref();
67
68    match file_type {
69        FileType::ClaudeMd
70        | FileType::AgentsMd
71        | FileType::CopilotCustomization
72        | FileType::CopilotInstructions
73        | FileType::JunieGuidelines
74        | FileType::AiderConventions
75        | FileType::GitHubSkill
76        | FileType::CustomProjectRules
77        | FileType::GenericMarkdown => DocumentContent::Markdown(markdown::parse(raw)),
78
79        FileType::CursorRules
80        | FileType::WindsurfRules
81        | FileType::ClineRules
82        | FileType::ContinueRules => match ext_str {
83            Some("md") | Some("markdown") | Some("mdc") => {
84                DocumentContent::Markdown(markdown::parse(raw))
85            }
86            Some("yaml") | Some("yml") => match yaml::parse(raw) {
87                Ok(v) => DocumentContent::Yaml(v),
88                Err(e) => DocumentContent::ParseError(e.to_string()),
89            },
90            Some("json") => match json::parse(raw) {
91                Ok(v) => DocumentContent::Json(v),
92                Err(e) => DocumentContent::ParseError(e.to_string()),
93            },
94            _ => DocumentContent::Markdown(markdown::parse(raw)),
95        },
96
97        FileType::GenericSystemPrompt => match ext_str {
98            Some("json") => match json::parse(raw) {
99                Ok(v) => DocumentContent::Json(v),
100                Err(e) => DocumentContent::ParseError(e.to_string()),
101            },
102            Some("yaml") | Some("yml") => match yaml::parse(raw) {
103                Ok(v) => DocumentContent::Yaml(v),
104                Err(e) => DocumentContent::ParseError(e.to_string()),
105            },
106            Some("md") | Some("markdown") => DocumentContent::Markdown(markdown::parse(raw)),
107            _ => DocumentContent::Markdown(markdown::parse(raw)),
108        },
109
110        FileType::GenericYaml => match yaml::parse(raw) {
111            Ok(v) => DocumentContent::Yaml(v),
112            Err(e) => DocumentContent::ParseError(e.to_string()),
113        },
114
115        FileType::McpConfig => match json::parse(raw) {
116            Ok(v) => DocumentContent::Json(v),
117            Err(e) => DocumentContent::ParseError(e.to_string()),
118        },
119
120        FileType::SourceCode(lang) => {
121            DocumentContent::Markdown(source_comments::synthesize(raw, lang))
122        }
123
124        FileType::Unknown => DocumentContent::Text,
125    }
126}