1pub use error::Error;
6pub use located::Located;
7pub use state::Delims;
8use state::State;
9use types::Root;
10
11pub(crate) type Input<'i> = winnow::Stateful<Located<'i>, State>;
12
13mod error;
14mod located;
15mod parse;
16mod state;
17pub mod types;
18
19pub fn parse_template(input: &str, delims: Delims) -> Result<Root<'_>, Error<'_>> {
25 use winnow::Parser;
26 let input = winnow::Stateful {
27 input: Located::new(input),
28 state: State::default(),
29 };
30 parse::root(&delims)
31 .parse(input)
32 .map_err(|err| err.into_inner())
33}
34
35#[cfg(test)]
36mod test {
37 use crate::{parse_template, types::{Expr, IfBranch, Item, ItemBlock, ItemFor, ItemIf, ItemMacro, ItemMatch, MatchArm, Root}, Delims};
38 use pretty_assertions::assert_eq;
39 use syn::{parse::Parser as _, punctuated::Punctuated};
40
41 const TEMPLATE: &str = r###"
42{% extends "base.html" %}
43
44{% fn my_func(s: &str) -> String {
45 let mut out = "OOF".to_string();
46 out.push_str(s);
47 out
48} %}
49
50{% macro my_mac(time: std::time::Duration) %}
51 INSIDE MY MAC
52{% end %}
53
54{% block head %}
55 {% a %}
56 {% super() %}
57 overwrites
58{% end %}
59
60{% block header %}
61 {% for i in 0..10 %}
62 {% match i %}
63 {% when 2 if i != 0 %}
64 {% i.json() %}
65 {% when 3 | 4 %}
66 {% a %}
67 {% when _ %}
68 {% end %}
69 {% end %}
70 {% if true %}
71 {% a %}
72 {% else %}
73 {% a %}
74 {% end %}
75{% end %}
76
77{% block main %}
78 {% "Hello Word" %}
79 {% include "other.html" %}
80 {% a %}
81{% end %}
82
83{% block footer %}
84 {% call my_mac(std::time::Duration::from_secs(50)) %}
85 {% my_func(s) %}
86{% end %}
87"###;
88
89 #[test]
90 pub fn parse_example_template() {
91 let res = parse_template(TEMPLATE, Delims::default()).unwrap();
92 let expects = Root {
93 content: vec![
94 Item::Expr(Expr::Extends("base.html".into())),
95 Item::Content("\n\n".into()),
96 Item::Expr(Expr::Stmt(syn::parse_str(r#"fn my_func(s: &str) -> String {
97 let mut out = "OOF".to_string();
98 out.push_str(s);
99 out
100 }"#).unwrap())),
101 Item::Content("\n\n".into()),
102 Item::Macro(ItemMacro {
103 name: syn::parse_str("my_mac").unwrap(),
104 args: Punctuated::parse_terminated.parse_str("time: std::time::Duration").unwrap(),
105 content: vec![Item::Content("\n INSIDE MY MAC\n".into())],
106 }),
107 Item::Content("\n\n".into()),
108 Item::Block(ItemBlock {
109 name: "head".into(),
110 content: vec![
111 Item::Content("\n ".into()),
112 Item::Expr(Expr::Expr(syn::parse_str("a").unwrap())),
113 Item::Content("\n ".into()),
114 Item::Expr(Expr::SuperCall),
115 Item::Content("\n overwrites\n".into()),
116 ]
117 }),
118 Item::Content("\n\n".into()),
119 Item::Block(ItemBlock {
120 name: "header".into(),
121 content: vec![
122 Item::Content("\n ".into()),
123 Item::For(ItemFor {
124 label: None,
125 pat: syn::Pat::parse_single.parse_str("i").unwrap(),
126 expr: syn::parse_str("0..10").unwrap(),
127 content: vec![
128 Item::Content("\n ".into()),
129 Item::Match(ItemMatch {
130 expr: syn::parse_str("i").unwrap(),
131 arms: vec![
132 MatchArm {
133 pat: syn::Pat::parse_multi.parse_str("2").unwrap(),
134 guard: Some(syn::parse_str("i != 0").unwrap()),
135 content: vec![
136 Item::Content("\n ".into()),
137 Item::Expr(Expr::Expr(syn::parse_str("i.json()").unwrap())),
138 Item::Content("\n ".into()),
139 ],
140 },
141 MatchArm {
142 pat: syn::Pat::parse_multi.parse_str("3 | 4").unwrap(),
143 guard: None,
144 content: vec![
145 Item::Content("\n ".into()),
146 Item::Expr(Expr::Expr(syn::parse_str("a").unwrap())),
147 Item::Content("\n ".into()),
148 ],
149 },
150 MatchArm {
151 pat: syn::Pat::parse_multi.parse_str("_").unwrap(),
152 guard: None,
153 content: vec![Item::Content("\n ".into())],
154 }
155 ]
156 }),
157 Item::Content("\n ".into()),
158 ],
159 }),
160 Item::Content("\n ".into()),
161 Item::If(ItemIf {
162 cond: syn::parse_str("true").unwrap(),
163 content: vec![
164 Item::Content("\n ".into()),
165 Item::Expr(Expr::Expr(syn::parse_str("a").unwrap())),
166 Item::Content("\n ".into()),
167 ],
168 branch: IfBranch::Else {
169 content: vec![
170 Item::Content("\n ".into()),
171 Item::Expr(Expr::Expr(syn::parse_str("a").unwrap())),
172 Item::Content("\n ".into()),
173 ],
174 },
175 }),
176 Item::Content("\n".into()),
177 ]
178 }),
179 Item::Content("\n\n".into()),
180 Item::Block(ItemBlock {
181 name: "main".into(),
182 content: vec![
183 Item::Content("\n ".into()),
184 Item::Expr(Expr::Expr(syn::parse_str(r#""Hello Word""#).unwrap())),
185 Item::Content("\n ".into()),
186 Item::Expr(Expr::Include {
187 reference: "other.html".into(),
188 args: Punctuated::parse_terminated.parse_str("").unwrap()
189 }),
190 Item::Content("\n ".into()),
191 Item::Expr(Expr::Expr(syn::parse_str("a").unwrap())),
192 Item::Content("\n".into())
193 ],
194 }),
195 Item::Content("\n\n".into()),
196 Item::Block(ItemBlock {
197 name: "footer".into(),
198 content: vec![
199 Item::Content("\n ".into()),
200 Item::Expr(Expr::MacroCall {
201 name: syn::parse_str("my_mac").unwrap(),
202 args: Punctuated::parse_terminated.parse_str("std::time::Duration::from_secs(50)").unwrap(),
203 }),
204 Item::Content("\n ".into()),
205 Item::Expr(Expr::Expr(syn::parse_str("my_func(s)").unwrap())),
206 Item::Content("\n".into()),
207 ],
208 }),
209 Item::Content("\n".into()),
210 ]
211 };
212 assert_eq!(res, expects);
213 }
214}