Skip to main content

panache_parser/
syntax.rs

1//! Syntax tree types and AST node wrappers for Quarto/Pandoc documents.
2//!
3//! This module provides a typed API over the raw concrete syntax tree (CST)
4//! produced by the parser. The CST is based on the `rowan` library and uses
5//! the red-green tree pattern for efficient incremental parsing.
6
7pub mod alerts;
8pub mod ast;
9pub mod attributes;
10pub mod block_quotes;
11pub mod blocks;
12pub mod chunk_options;
13pub mod citations;
14pub mod code_blocks;
15pub mod crossrefs;
16pub mod definitions;
17pub mod fenced_divs;
18pub mod headings;
19pub mod inlines;
20pub mod kind;
21pub mod links;
22pub mod lists;
23pub mod math;
24pub mod raw_tex;
25pub mod references;
26pub mod shortcodes;
27pub mod tables;
28pub mod yaml;
29
30pub use alerts::*;
31pub use ast::*;
32pub use attributes::*;
33pub use block_quotes::*;
34pub use blocks::*;
35pub use chunk_options::*;
36pub use citations::*;
37pub use code_blocks::*;
38pub use crossrefs::*;
39pub use definitions::*;
40pub use fenced_divs::*;
41pub use headings::*;
42pub use inlines::*;
43pub use kind::*;
44pub use links::*;
45pub use lists::*;
46pub use math::*;
47pub use raw_tex::*;
48pub use references::*;
49pub use shortcodes::*;
50pub use tables::*;
51pub use yaml::*;
52
53pub type SyntaxNode = rowan::SyntaxNode<PanacheLanguage>;
54pub type SyntaxToken = rowan::SyntaxToken<PanacheLanguage>;
55pub type SyntaxElement = rowan::SyntaxElement<PanacheLanguage>;
56
57#[cfg(test)]
58mod tests {
59    use super::*;
60
61    #[test]
62    fn test_heading_wrapper() {
63        use crate::ParserOptions;
64        use crate::parser::parse;
65
66        let input = "# Hello World\n\nParagraph.";
67        let tree = parse(input, Some(ParserOptions::default()));
68
69        let heading = tree
70            .children()
71            .find_map(Heading::cast)
72            .expect("should find heading");
73
74        assert_eq!(heading.level(), 1);
75        assert_eq!(heading.text(), "Hello World");
76    }
77
78    #[test]
79    fn test_link_wrapper() {
80        use crate::ParserOptions;
81        use crate::parser::parse;
82
83        let input = "Click [here](https://example.com).";
84        let tree = parse(input, Some(ParserOptions::default()));
85
86        // Find link using typed wrapper
87        let link = tree
88            .descendants()
89            .find_map(Link::cast)
90            .expect("should find link");
91
92        assert_eq!(
93            link.text().map(|t| t.text_content()),
94            Some("here".to_string())
95        );
96        assert_eq!(
97            link.dest().map(|d| d.url_content()),
98            Some("https://example.com".to_string())
99        );
100    }
101
102    #[test]
103    fn test_image_wrapper() {
104        use crate::ParserOptions;
105        use crate::parser::parse;
106
107        let input = "![Alt text](image.png)";
108        let tree = parse(input, Some(ParserOptions::default()));
109
110        let image = tree
111            .descendants()
112            .find_map(ImageLink::cast)
113            .expect("should find image");
114
115        assert_eq!(image.alt().map(|a| a.text()), Some("Alt text".to_string()));
116    }
117
118    #[test]
119    fn test_autolink_wrapper() {
120        use crate::ParserOptions;
121        use crate::parser::parse;
122
123        let input = "<https://example.com>";
124        let tree = parse(input, Some(ParserOptions::default()));
125
126        let autolink = tree
127            .descendants()
128            .find_map(AutoLink::cast)
129            .expect("should find autolink");
130
131        assert_eq!(autolink.target(), "https://example.com");
132    }
133
134    #[test]
135    fn test_shortcode_wrapper() {
136        use crate::ParserOptions;
137        use crate::parser::parse;
138
139        let input = "{{< include \"chapters/part 1.qmd\" >}}";
140        let tree = parse(input, Some(ParserOptions::default()));
141
142        let shortcode = tree
143            .descendants()
144            .find_map(Shortcode::cast)
145            .expect("should find shortcode");
146
147        assert_eq!(shortcode.name().as_deref(), Some("include"));
148        assert_eq!(
149            shortcode.args(),
150            vec!["include".to_string(), "chapters/part 1.qmd".to_string()]
151        );
152    }
153
154    #[test]
155    fn test_table_wrapper() {
156        use crate::ParserOptions;
157        use crate::parser::parse;
158
159        let input = r#"| A | B |
160|---|---|
161| 1 | 2 |
162
163Table: My caption
164"#;
165        let tree = parse(input, Some(ParserOptions::default()));
166
167        let table = tree
168            .descendants()
169            .find_map(PipeTable::cast)
170            .expect("should find table");
171
172        assert_eq!(
173            table.caption().map(|c| c.text()),
174            Some("My caption".to_string())
175        );
176        assert!(table.rows().count() > 0);
177    }
178}