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