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