1pub mod markdown;
6pub mod syntax;
7pub mod typst_parser;
8
9use std::path::Path;
10
11pub use markdown::MarkdownParser;
12pub use syntax::SyntaxHighlighter;
13use thiserror::Error;
14pub use typst_parser::TypstParser;
15use typstify_core::content::{ContentType, ParsedContent};
16
17#[derive(Debug, Error)]
19pub enum ParserError {
20 #[error("markdown error: {0}")]
22 Markdown(#[from] markdown::MarkdownError),
23
24 #[error("typst error: {0}")]
26 Typst(#[from] typst_parser::TypstError),
27
28 #[error("unsupported content type: {0:?}")]
30 UnsupportedType(ContentType),
31
32 #[error("unknown file extension: {0}")]
34 UnknownExtension(String),
35}
36
37pub type Result<T> = std::result::Result<T, ParserError>;
39
40pub trait ContentParser {
42 fn parse(&self, content: &str, path: &Path) -> Result<ParsedContent>;
44}
45
46impl ContentParser for MarkdownParser {
47 fn parse(&self, content: &str, path: &Path) -> Result<ParsedContent> {
48 Ok(self.parse(content, path)?)
49 }
50}
51
52impl ContentParser for TypstParser {
53 fn parse(&self, content: &str, path: &Path) -> Result<ParsedContent> {
54 Ok(self.parse(content, path)?)
55 }
56}
57
58#[derive(Debug)]
60pub struct ParserRegistry {
61 markdown: MarkdownParser,
62 typst: TypstParser,
63}
64
65impl Default for ParserRegistry {
66 fn default() -> Self {
67 Self::new()
68 }
69}
70
71impl ParserRegistry {
72 pub fn new() -> Self {
74 Self {
75 markdown: MarkdownParser::new(),
76 typst: TypstParser::new(),
77 }
78 }
79
80 pub fn with_theme(theme: &str) -> Self {
82 Self {
83 markdown: MarkdownParser::with_theme(theme),
84 typst: TypstParser::new(),
85 }
86 }
87
88 pub fn parse(&self, content: &str, path: &Path) -> Result<ParsedContent> {
90 let ext = path
91 .extension()
92 .and_then(|e| e.to_str())
93 .ok_or_else(|| ParserError::UnknownExtension("(none)".to_string()))?;
94
95 match ContentType::from_extension(ext) {
96 Some(ContentType::Markdown) => Ok(self.markdown.parse(content, path)?),
97 Some(ContentType::Typst) => Ok(self.typst.parse(content, path)?),
98 None => Err(ParserError::UnknownExtension(ext.to_string())),
99 }
100 }
101
102 pub fn markdown(&self) -> &MarkdownParser {
104 &self.markdown
105 }
106
107 pub fn typst(&self) -> &TypstParser {
109 &self.typst
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116
117 #[test]
118 fn test_registry_markdown() {
119 let registry = ParserRegistry::new();
120 let content = r#"---
121title: "Test"
122---
123
124# Hello"#;
125
126 let result = registry.parse(content, Path::new("test.md")).unwrap();
127 assert_eq!(result.frontmatter.title, "Test");
128 }
129
130 #[test]
131 fn test_registry_unknown_extension() {
132 let registry = ParserRegistry::new();
133 let result = registry.parse("content", Path::new("test.xyz"));
134
135 assert!(matches!(result, Err(ParserError::UnknownExtension(_))));
136 }
137
138 #[test]
139 fn test_content_parser_trait() {
140 let parser = MarkdownParser::new();
141 let content = r#"---
142title: "Trait Test"
143---
144
145Content"#;
146
147 let result: Result<ParsedContent> =
148 ContentParser::parse(&parser, content, Path::new("test.md"));
149 assert!(result.is_ok());
150 }
151}