1use crate::template_parser::to_sql::{SqlSegment, ToSqlSegment};
2use nom::{
3 branch::alt,
4 bytes::complete::{tag, take_until},
5 character::complete::multispace0,
6 combinator::{map, opt},
7 sequence::delimited,
8 IResult,
9};
10use std::fmt::Display;
11
12#[derive(PartialEq, Debug, Clone, Copy)]
13pub enum Modifier {
14 Plus,
15 Minus,
16 Tilde,
17}
18impl Display for Modifier {
19 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20 match self {
21 Modifier::Plus => write!(f, "+"),
22 Modifier::Minus => write!(f, "-"),
23 Modifier::Tilde => write!(f, "~"),
24 }
25 }
26}
27
28#[derive(Debug, PartialEq, Clone)]
29pub struct EndBlock {
30 pub name: String,
31 pub start_modifier: Option<Modifier>,
32 pub end_modifier: Option<Modifier>,
33}
34
35impl Display for EndBlock {
36 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37 let start_modifier_str = if let Some(start_modifier) = &self.start_modifier {
38 start_modifier.to_string()
39 } else {
40 "".to_string()
41 };
42 let end_modifier_str = if let Some(end_modifier) = &self.end_modifier {
43 end_modifier.to_string()
44 } else {
45 "".to_string()
46 };
47 write!(
48 f,
49 "{{%{} {} {}%}}",
50 start_modifier_str, self.name, end_modifier_str
51 )
52 }
53}
54
55#[derive(Debug, PartialEq, Clone)]
56pub struct StartBlock {
57 pub name: String,
58 pub start_modifier: Option<Modifier>,
59 pub end_modifier: Option<Modifier>,
60 pub expr: String,
61}
62
63impl Display for StartBlock {
64 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65 let start_modifier_str = if let Some(start_modifier) = &self.start_modifier {
66 start_modifier.to_string()
67 } else {
68 "".to_string()
69 };
70 let end_modifier_str = if let Some(end_modifier) = &self.end_modifier {
71 end_modifier.to_string()
72 } else {
73 "".to_string()
74 };
75 write!(
76 f,
77 "{{%{} {} {} {}%}}",
78 start_modifier_str, self.name, self.expr, end_modifier_str
79 )
80 }
81}
82
83#[derive(Debug, PartialEq, Clone)]
84pub enum TemplatePart {
85 Expression(String), ControlBlock {
87 start_block: StartBlock,
88 content: String,
89 end_block: EndBlock,
90 }, Call(String), Comment(String), }
94
95impl Display for TemplatePart {
96 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
97 match self {
98 TemplatePart::ControlBlock {
99 start_block,
100 content,
101 end_block,
102 } => {
103 write!(f, "{}{}{}", start_block, content, end_block)
104 }
105 TemplatePart::Expression(expression) => {
106 write!(f, "{{ {} }}", expression)
107 }
108 TemplatePart::Call(call_expr) => {
109 write!(f, "{{% call {} %}}", call_expr)
110 }
111 TemplatePart::Comment(comment) => write!(f, ""),
112 }
113 }
114}
115
116impl TemplatePart {
117 pub fn parse(input: &str) -> IResult<&str, TemplatePart> {
118 let (input, part) = delimited(
119 multispace0,
120 alt((
121 map(parse_call, |part| part),
122 map(parse_comment, |part| part),
123 map(parse_expression, |part| part),
124 map(parse_control_block, |part| part),
125 )),
126 multispace0,
127 )(input)?;
128 Ok((input, part))
129 }
130}
131
132fn parse_expression(input: &str) -> IResult<&str, TemplatePart> {
133 let (input, _) = tag("{{")(input)?;
134 let (input, expr) = take_until("}}")(input)?;
135 let (input, _) = tag("}}")(input)?;
136 Ok((input, TemplatePart::Expression(expr.trim().to_string())))
137}
138
139fn take_until_and_consume<F, R>(parser: F) -> impl Fn(&str) -> IResult<&str, String>
141where
142 F: Fn(&str) -> IResult<&str, R>,
143{
144 move |input: &str| {
145 let mut buffer = String::new();
146 let mut remaining = input;
147
148 loop {
149 match parser(remaining) {
150 Ok((_, _)) => {
151 break;
153 }
154 Err(_) => {
155 if remaining.is_empty() {
156 return Err(nom::Err::Error(nom::error::Error::new(
158 input,
159 nom::error::ErrorKind::TakeUntil,
160 )));
161 }
162
163 buffer.push(remaining.chars().next().unwrap());
165 remaining = &remaining[1..];
166 }
167 }
168 }
169 Ok((remaining, buffer))
170 }
171}
172fn parse_modifier(input: &str) -> IResult<&str, Modifier> {
173 let (input, modifier_str) = alt((tag("+"), tag("-"), tag("~")))(input)?;
174 let modifier = match modifier_str {
175 "+" => Modifier::Plus,
176 "-" => Modifier::Minus,
177 "~" => Modifier::Tilde,
178 _ => unreachable!(),
179 };
180 Ok((input, modifier))
181}
182
183fn parse_start_brace(input: &str) -> IResult<&str, Option<Modifier>> {
184 let (input, _) = tag("{%")(input)?;
185 let (input, _) = multispace0(input)?; let (input, modifier) = opt(parse_modifier)(input)?;
187 Ok((input, modifier))
188}
189
190fn parse_end_brace(input: &str) -> IResult<&str, Option<Modifier>> {
191 let (input, modifier) = opt(parse_modifier)(input)?;
192 let (input, _) = multispace0(input)?; let (input, _) = tag("%}")(input)?;
194 Ok((input, modifier))
195}
196
197fn parse_start_block_with_name<'a>(
198 input: &'a str,
199 block_name: &'static str,
200) -> IResult<&'a str, StartBlock> {
201 let (input, start_modifier) = parse_start_brace(input)?;
202 let (input, _) = multispace0(input)?; let (input, parsed_block_name) = tag(block_name)(input)?;
204 let (input, _) = multispace0(input)?; let (input, expr) = take_until_and_consume(parse_end_brace)(input)?; let (input, _) = multispace0(input)?; let (input, end_modifier) = parse_end_brace(input)?; Ok((
210 input,
211 StartBlock {
212 name: parsed_block_name.to_string(),
213 start_modifier,
214 end_modifier,
215 expr: expr.trim().to_string(),
216 },
217 ))
218}
219
220fn parse_start_block(input: &str) -> IResult<&str, StartBlock> {
221 alt((
222 |i| parse_start_block_with_name(i, "if"),
223 |i| parse_start_block_with_name(i, "match"),
224 |i| parse_start_block_with_name(i, "for"),
225 |i| parse_start_block_with_name(i, "macro"),
226 |i| parse_start_block_with_name(i, "filter"),
227 |i| parse_start_block_with_name(i, "block"),
228 ))(input)
229}
230
231fn parse_end_block_with_name<'a>(
232 input: &'a str,
233 block_name: &'static str,
234) -> IResult<&'a str, EndBlock> {
235 let (input, start_modifier) = parse_start_brace(input)?;
236 let (input, _) = multispace0(input)?; let (input, _) = tag(block_name)(input)?;
238 let (input, _) = multispace0(input)?; let (input, end_modifier) = parse_end_brace(input)?; Ok((
241 input,
242 EndBlock {
243 name: block_name.to_string(),
244 start_modifier,
245 end_modifier,
246 },
247 ))
248}
249
250fn parse_end_block(input: &str) -> IResult<&str, EndBlock> {
251 alt((
252 |i| parse_end_block_with_name(i, "endif"),
253 |i| parse_end_block_with_name(i, "endmatch"),
254 |i| parse_end_block_with_name(i, "endfor"),
255 |i| parse_end_block_with_name(i, "endmacro"),
256 |i| parse_end_block_with_name(i, "endfilter"),
257 |i| parse_end_block_with_name(i, "endblock"),
258 ))(input)
259}
260
261fn parse_control_block(input: &str) -> IResult<&str, TemplatePart> {
262 let (input, start_block) = parse_start_block(input)?;
264
265 let (input, control_content) = take_until_and_consume(parse_end_block)(input)?;
266
267 let (input, end_block) = parse_end_block(input)?;
268
269 Ok((
270 input,
271 TemplatePart::ControlBlock {
272 start_block,
273 content: control_content.trim().to_string(),
274 end_block,
275 },
276 ))
277}
278
279fn parse_call(input: &str) -> IResult<&str, TemplatePart> {
280 let (input, _) = tag("{% call ")(input)?;
281 let (input, expr) = take_until("%}")(input)?;
282 let (input, _) = tag("%}")(input)?;
283 Ok((input, TemplatePart::Call(expr.trim().to_string())))
284}
285
286fn parse_comment(input: &str) -> IResult<&str, TemplatePart> {
287 let (input, _) = tag("{#")(input)?;
288 let (input, comment) = take_until("#}")(input)?;
289 let (input, _) = tag("#}")(input)?;
290 Ok((input, TemplatePart::Comment(comment.to_string())))
291}
292
293#[cfg(test)]
312mod tests {
313 use super::*;
314
315 #[test]
316 fn test_parse_end_brace() {
317 let template = "%}";
318 let (remaining, end_block) = parse_end_brace(template).unwrap();
319 assert_eq!(end_block, None);
320
321 let template = "- %}";
322 let (remaining, end_block) = parse_end_brace(template).unwrap();
323 assert_eq!(end_block, Some(Modifier::Minus));
324
325 let template = "~ %}";
326 let (remaining, end_block) = parse_end_brace(template).unwrap();
327 assert_eq!(end_block, Some(Modifier::Tilde));
328 }
329
330 #[test]
331 fn test_parse_start_block() {
332 let template = r#"{% if active %}"#;
333 let (remaining, parsed) = parse_start_block(template).unwrap();
334 let expected = StartBlock {
335 name: "if".to_string(),
336 start_modifier: None,
337 end_modifier: None,
338 expr: "active".to_string(),
339 };
340 assert_eq!(expected, parsed);
341 let template = r#"{%~ for active set as test -%}"#;
342 let (remaining, parsed) = parse_start_block(template).unwrap();
343 let expected = StartBlock {
344 name: "for".to_string(),
345 start_modifier: Some(Modifier::Tilde),
346 end_modifier: Some(Modifier::Minus),
347 expr: "active set as test".to_string(),
348 };
349 assert_eq!(expected, parsed);
350 }
351
352 #[test]
353 fn test_parse_end_block() {
354 let template = r#"{% endif %}"#;
355 let (remaining, parsed) = parse_end_block(template).unwrap();
356 assert_eq!(
357 parsed,
358 EndBlock {
359 name: String::from("endif"),
360 start_modifier: None,
361 end_modifier: None
362 }
363 );
364
365 let template = r#"{% if active %}
366 You are active.
367 {% endif -%}"#;
368 let (remaining, _) = parse_start_block(template).unwrap();
369 assert_eq!(
370 remaining,
371 "\n You are active.\n {% endif -%}"
372 );
373 let (remaining, parsed) = take_until_and_consume(parse_end_block)(remaining).unwrap();
374 assert_eq!(parsed, "\n You are active.\n ");
375 }
376
377 #[test]
378 fn test_parse_template_part() {
379 let template = r#"{{ name | capitalize }}"#;
380 let (remaining, parsed_template_parts) = TemplatePart::parse(template).unwrap();
381 let expected = TemplatePart::Expression("name | capitalize".to_string());
382 assert_eq!(parsed_template_parts, expected);
383
384 let template = r#"{% if active %}
385 You are active.
386 {% endif -%}"#;
387 let (remaining, parsed_template_parts) = TemplatePart::parse(template).unwrap();
388 let expected = TemplatePart::ControlBlock {
389 start_block: StartBlock {
390 name: "if".to_string(),
391 start_modifier: None,
392 end_modifier: None,
393 expr: "active".to_string(),
394 },
395 content: "You are active.".to_string(),
396 end_block: EndBlock {
397 name: "endif".to_string(),
398 start_modifier: None,
399 end_modifier: Some(Modifier::Minus),
400 },
401 };
402 assert_eq!(parsed_template_parts, expected);
403
404 let template = r#"
405 {% for item in items %}
406 Item: {{ item | upper }}
407 {% endfor %}"#;
408 let (remaining, parsed_template_parts) = TemplatePart::parse(template).unwrap();
409 let expected = TemplatePart::ControlBlock {
410 start_block: StartBlock {
411 name: "for".to_string(),
412 start_modifier: None,
413 end_modifier: None,
414 expr: "item in items".to_string(),
415 },
416 content: "Item: {{ item | upper }}".to_string(),
417 end_block: EndBlock {
418 name: "endfor".to_string(),
419 start_modifier: None,
420 end_modifier: None,
421 },
422 };
423 assert_eq!(parsed_template_parts, expected);
424
425 let template = r#"{% call macro %}"#;
426 let (remaining, parsed_template_parts) = TemplatePart::parse(template).unwrap();
427 let expected = TemplatePart::Call("macro".to_string());
428 assert_eq!(parsed_template_parts, expected);
429
430 let template = r#"
431 {# This is a comment #}"#;
432 let (remaining, parsed_template_parts) = TemplatePart::parse(template).unwrap();
433 let expected = TemplatePart::Comment(" This is a comment ".to_string());
434 assert_eq!(parsed_template_parts, expected);
435 }
436}