1use std::fmt;
2use std::io::Write;
3
4use liquid_core::error::ResultLiquidExt;
5use liquid_core::model::{ValueView, ValueViewCmp};
6use liquid_core::parser::BlockElement;
7use liquid_core::parser::TagToken;
8use liquid_core::Expression;
9use liquid_core::Language;
10use liquid_core::Renderable;
11use liquid_core::Runtime;
12use liquid_core::Template;
13use liquid_core::{BlockReflection, ParseBlock, TagBlock, TagTokenIter};
14use liquid_core::{Error, Result};
15
16#[derive(Copy, Clone, Debug, Default)]
17pub struct IfBlock;
18
19impl IfBlock {
20 pub fn new() -> Self {
21 Self::default()
22 }
23}
24
25impl BlockReflection for IfBlock {
26 fn start_tag(&self) -> &str {
27 "if"
28 }
29
30 fn end_tag(&self) -> &str {
31 "endif"
32 }
33
34 fn description(&self) -> &str {
35 ""
36 }
37}
38
39impl ParseBlock for IfBlock {
40 fn parse(
41 &self,
42 arguments: TagTokenIter<'_>,
43 mut tokens: TagBlock<'_, '_>,
44 options: &Language,
45 ) -> Result<Box<dyn Renderable>> {
46 let conditional = parse_if(arguments, &mut tokens, options)?;
47
48 tokens.assert_empty();
49 Ok(conditional)
50 }
51
52 fn reflection(&self) -> &dyn BlockReflection {
53 self
54 }
55}
56
57fn parse_if(
58 arguments: TagTokenIter<'_>,
59 tokens: &mut TagBlock<'_, '_>,
60 options: &Language,
61) -> Result<Box<dyn Renderable>> {
62 let condition = parse_condition(arguments)?;
63
64 let mut if_true = Vec::new();
65 let mut if_false = None;
66
67 while let Some(element) = tokens.next()? {
68 match element {
69 BlockElement::Tag(tag) => match tag.name() {
70 "else" => {
71 if_false = Some(tokens.parse_all(options)?);
72 break;
73 }
74 "elsif" => {
75 if_false = Some(vec![parse_if(tag.into_tokens(), tokens, options)?]);
76 break;
77 }
78 _ => if_true.push(tag.parse(tokens, options)?),
79 },
80 element => if_true.push(element.parse(tokens, options)?),
81 }
82 }
83
84 let if_true = Template::new(if_true);
85 let if_false = if_false.map(Template::new);
86
87 Ok(Box::new(Conditional {
88 condition,
89 mode: true,
90 if_true,
91 if_false,
92 }))
93}
94
95#[derive(Copy, Clone, Debug, Default)]
96pub struct UnlessBlock;
97
98impl UnlessBlock {
99 pub fn new() -> Self {
100 Self::default()
101 }
102}
103
104impl BlockReflection for UnlessBlock {
105 fn start_tag(&self) -> &str {
106 "unless"
107 }
108
109 fn end_tag(&self) -> &str {
110 "endunless"
111 }
112
113 fn description(&self) -> &str {
114 ""
115 }
116}
117
118impl ParseBlock for UnlessBlock {
119 fn parse(
120 &self,
121 arguments: TagTokenIter<'_>,
122 mut tokens: TagBlock<'_, '_>,
123 options: &Language,
124 ) -> Result<Box<dyn Renderable>> {
125 let condition = parse_condition(arguments)?;
126
127 let mut if_true = Vec::new();
128 let mut if_false = None;
129
130 while let Some(element) = tokens.next()? {
131 match element {
132 BlockElement::Tag(tag) => match tag.name() {
133 "else" => {
134 if_false = Some(tokens.parse_all(options)?);
135 break;
136 }
137 _ => if_true.push(tag.parse(&mut tokens, options)?),
138 },
139 element => if_true.push(element.parse(&mut tokens, options)?),
140 }
141 }
142
143 let if_true = Template::new(if_true);
144 let if_false = if_false.map(Template::new);
145
146 tokens.assert_empty();
147 Ok(Box::new(Conditional {
148 condition,
149 mode: false,
150 if_true,
151 if_false,
152 }))
153 }
154
155 fn reflection(&self) -> &dyn BlockReflection {
156 self
157 }
158}
159
160#[derive(Debug)]
161struct Conditional {
162 condition: Condition,
163 mode: bool,
164 if_true: Template,
165 if_false: Option<Template>,
166}
167
168impl Conditional {
169 fn compare(&self, runtime: &dyn Runtime) -> Result<bool> {
170 let result = self.condition.evaluate(runtime)?;
171
172 Ok(result == self.mode)
173 }
174
175 fn trace(&self) -> String {
176 format!("{{% if {} %}}", self.condition)
177 }
178}
179
180impl Renderable for Conditional {
181 fn render_to(&self, writer: &mut dyn Write, runtime: &dyn Runtime) -> Result<()> {
182 let condition = self.compare(runtime).trace_with(|| self.trace().into())?;
183 if condition {
184 self.if_true
185 .render_to(writer, runtime)
186 .trace_with(|| self.trace().into())?;
187 } else if let Some(ref template) = self.if_false {
188 template
189 .render_to(writer, runtime)
190 .trace("{{% else %}}")
191 .trace_with(|| self.trace().into())?;
192 }
193
194 Ok(())
195 }
196}
197
198#[derive(Clone, Debug)]
199enum Condition {
200 Binary(BinaryCondition),
201 Existence(ExistenceCondition),
202 Conjunction(Box<Condition>, Box<Condition>),
203 Disjunction(Box<Condition>, Box<Condition>),
204}
205
206impl Condition {
207 pub fn evaluate(&self, runtime: &dyn Runtime) -> Result<bool> {
208 match *self {
209 Condition::Binary(ref c) => c.evaluate(runtime),
210 Condition::Existence(ref c) => c.evaluate(runtime),
211 Condition::Conjunction(ref left, ref right) => {
212 Ok(left.evaluate(runtime)? && right.evaluate(runtime)?)
213 }
214 Condition::Disjunction(ref left, ref right) => {
215 Ok(left.evaluate(runtime)? || right.evaluate(runtime)?)
216 }
217 }
218 }
219}
220
221impl fmt::Display for Condition {
222 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
223 match *self {
224 Condition::Binary(ref c) => write!(f, "{}", c),
225 Condition::Existence(ref c) => write!(f, "{}", c),
226 Condition::Conjunction(ref left, ref right) => write!(f, "{} and {}", left, right),
227 Condition::Disjunction(ref left, ref right) => write!(f, "{} or {}", left, right),
228 }
229 }
230}
231
232#[derive(Clone, Debug)]
233struct BinaryCondition {
234 lh: Expression,
235 comparison: ComparisonOperator,
236 rh: Expression,
237}
238
239impl BinaryCondition {
240 pub fn evaluate(&self, runtime: &dyn Runtime) -> Result<bool> {
241 let a = self.lh.evaluate(runtime)?;
242 let ca = ValueViewCmp::new(a.as_view());
243 let b = self.rh.evaluate(runtime)?;
244 let cb = ValueViewCmp::new(b.as_view());
245
246 let result = match self.comparison {
247 ComparisonOperator::Equals => ca == cb,
248 ComparisonOperator::NotEquals => ca != cb,
249 ComparisonOperator::LessThan => ca < cb,
250 ComparisonOperator::GreaterThan => ca > cb,
251 ComparisonOperator::LessThanEquals => ca <= cb,
252 ComparisonOperator::GreaterThanEquals => ca >= cb,
253 ComparisonOperator::Contains => contains_check(a.as_view(), b.as_view())?,
254 };
255
256 Ok(result)
257 }
258}
259
260impl fmt::Display for BinaryCondition {
261 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
262 write!(f, "{} {} {}", self.lh, self.comparison, self.rh)
263 }
264}
265
266fn contains_check(a: &dyn ValueView, b: &dyn ValueView) -> Result<bool> {
267 if let Some(a) = a.as_scalar() {
268 let b = b.to_kstr();
269 Ok(a.to_kstr().contains(b.as_str()))
270 } else if let Some(a) = a.as_object() {
271 let b = b.as_scalar();
272 let check = b
273 .map(|b| a.contains_key(b.to_kstr().as_str()))
274 .unwrap_or(false);
275 Ok(check)
276 } else if let Some(a) = a.as_array() {
277 for elem in a.values() {
278 if ValueViewCmp::new(elem) == ValueViewCmp::new(b) {
279 return Ok(true);
280 }
281 }
282 Ok(false)
283 } else {
284 Err(unexpected_value_error(
285 "string | array | object",
286 Some(a.type_name()),
287 ))
288 }
289}
290
291#[derive(Clone, Debug)]
292enum ComparisonOperator {
293 Equals,
294 NotEquals,
295 LessThan,
296 GreaterThan,
297 LessThanEquals,
298 GreaterThanEquals,
299 Contains,
300}
301
302impl fmt::Display for ComparisonOperator {
303 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
304 let out = match *self {
305 ComparisonOperator::Equals => "==",
306 ComparisonOperator::NotEquals => "!=",
307 ComparisonOperator::LessThanEquals => "<=",
308 ComparisonOperator::GreaterThanEquals => ">=",
309 ComparisonOperator::LessThan => "<",
310 ComparisonOperator::GreaterThan => ">",
311 ComparisonOperator::Contains => "contains",
312 };
313 write!(f, "{}", out)
314 }
315}
316
317impl ComparisonOperator {
318 fn from_str(s: &str) -> ::std::result::Result<Self, ()> {
319 match s {
320 "==" => Ok(ComparisonOperator::Equals),
321 "!=" | "<>" => Ok(ComparisonOperator::NotEquals),
322 "<" => Ok(ComparisonOperator::LessThan),
323 ">" => Ok(ComparisonOperator::GreaterThan),
324 "<=" => Ok(ComparisonOperator::LessThanEquals),
325 ">=" => Ok(ComparisonOperator::GreaterThanEquals),
326 "contains" => Ok(ComparisonOperator::Contains),
327 _ => Err(()),
328 }
329 }
330}
331
332#[derive(Clone, Debug)]
333struct ExistenceCondition {
334 lh: Expression,
335}
336
337impl ExistenceCondition {
338 pub fn evaluate(&self, runtime: &dyn Runtime) -> Result<bool> {
339 let a = self.lh.try_evaluate(runtime);
340 let a = a.unwrap_or_default();
341 let is_truthy = a.query_state(liquid_core::model::State::Truthy);
342 Ok(is_truthy)
343 }
344}
345
346impl fmt::Display for ExistenceCondition {
347 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
348 write!(f, "{}", self.lh)
349 }
350}
351
352struct PeekableTagTokenIter<'a> {
353 iter: TagTokenIter<'a>,
354 peeked: Option<Option<TagToken<'a>>>,
355}
356
357impl<'a> Iterator for PeekableTagTokenIter<'a> {
358 type Item = TagToken<'a>;
359
360 fn next(&mut self) -> Option<TagToken<'a>> {
361 match self.peeked.take() {
362 Some(v) => v,
363 None => self.iter.next(),
364 }
365 }
366}
367
368impl<'a> PeekableTagTokenIter<'a> {
369 pub fn expect_next(&mut self, error_msg: &str) -> Result<TagToken<'a>> {
370 self.next().ok_or_else(|| self.iter.raise_error(error_msg))
371 }
372
373 fn peek(&mut self) -> Option<&TagToken<'a>> {
374 if self.peeked.is_none() {
375 self.peeked = Some(self.iter.next());
376 }
377 match self.peeked {
378 Some(Some(ref value)) => Some(value),
379 Some(None) => None,
380 None => unreachable!(),
381 }
382 }
383}
384
385fn parse_atom_condition(arguments: &mut PeekableTagTokenIter<'_>) -> Result<Condition> {
386 let lh = arguments
387 .expect_next("Value expected.")?
388 .expect_value()
389 .into_result()?;
390 let cond = match arguments
391 .peek()
392 .map(TagToken::as_str)
393 .and_then(|op| ComparisonOperator::from_str(op).ok())
394 {
395 Some(op) => {
396 arguments.next();
397 let rh = arguments
398 .expect_next("Value expected.")?
399 .expect_value()
400 .into_result()?;
401 Condition::Binary(BinaryCondition {
402 lh,
403 comparison: op,
404 rh,
405 })
406 }
407 None => Condition::Existence(ExistenceCondition { lh }),
408 };
409
410 Ok(cond)
411}
412
413fn parse_conjunction_chain(arguments: &mut PeekableTagTokenIter<'_>) -> Result<Condition> {
414 let mut lh = parse_atom_condition(arguments)?;
415
416 while let Some("and") = arguments.peek().map(TagToken::as_str) {
417 arguments.next();
418 let rh = parse_atom_condition(arguments)?;
419 lh = Condition::Conjunction(Box::new(lh), Box::new(rh));
420 }
421
422 Ok(lh)
423}
424
425fn parse_condition(arguments: TagTokenIter<'_>) -> Result<Condition> {
427 let mut arguments = PeekableTagTokenIter {
428 iter: arguments,
429 peeked: None,
430 };
431 let mut lh = parse_conjunction_chain(&mut arguments)?;
432
433 while let Some(token) = arguments.next() {
434 token
435 .expect_str("or")
436 .into_result_custom_msg("\"and\" or \"or\" expected.")?;
437
438 let rh = parse_conjunction_chain(&mut arguments)?;
439 lh = Condition::Disjunction(Box::new(lh), Box::new(rh));
440 }
441
442 Ok(lh)
443}
444
445fn unexpected_value_error<S: ToString>(expected: &str, actual: Option<S>) -> Error {
447 let actual = actual.map(|x| x.to_string());
448 unexpected_value_error_string(expected, actual)
449}
450
451fn unexpected_value_error_string(expected: &str, actual: Option<String>) -> Error {
452 let actual = actual.unwrap_or_else(|| "nothing".to_owned());
453 Error::with_msg(format!("Expected {}, found `{}`", expected, actual))
454}
455
456#[cfg(test)]
457mod test {
458 use super::*;
459
460 use liquid_core::model::Object;
461 use liquid_core::model::Value;
462 use liquid_core::parser;
463 use liquid_core::runtime;
464 use liquid_core::runtime::RuntimeBuilder;
465
466 fn options() -> Language {
467 let mut options = Language::default();
468 options.blocks.register("if".to_string(), IfBlock.into());
469 options
470 .blocks
471 .register("unless".to_string(), UnlessBlock.into());
472 options
473 }
474
475 #[test]
476 fn number_comparison() {
477 let text = "{% if 6 < 7 %}if true{% endif %}";
478 let template = parser::parse(text, &options())
479 .map(runtime::Template::new)
480 .unwrap();
481
482 let runtime = RuntimeBuilder::new().build();
483 let output = template.render(&runtime).unwrap();
484 assert_eq!(output, "if true");
485
486 let text = "{% if 7 < 6 %}if true{% else %}if false{% endif %}";
487 let template = parser::parse(text, &options())
488 .map(runtime::Template::new)
489 .unwrap();
490
491 let runtime = RuntimeBuilder::new().build();
492 let output = template.render(&runtime).unwrap();
493 assert_eq!(output, "if false");
494 }
495
496 #[test]
497 fn string_comparison() {
498 let text = r#"{% if "one" == "one" %}if true{% endif %}"#;
499 let template = parser::parse(text, &options())
500 .map(runtime::Template::new)
501 .unwrap();
502
503 let runtime = RuntimeBuilder::new().build();
504 let output = template.render(&runtime).unwrap();
505 assert_eq!(output, "if true");
506
507 let text = r#"{% if "one" == "two" %}if true{% else %}if false{% endif %}"#;
508 let template = parser::parse(text, &options())
509 .map(runtime::Template::new)
510 .unwrap();
511
512 let runtime = RuntimeBuilder::new().build();
513 let output = template.render(&runtime).unwrap();
514 assert_eq!(output, "if false");
515 }
516
517 #[test]
518 fn implicit_comparison() {
519 let text = concat!(
520 "{% if truthy %}",
521 "yep",
522 "{% else %}",
523 "nope",
524 "{% endif %}"
525 );
526
527 let template = parser::parse(text, &options())
528 .map(runtime::Template::new)
529 .unwrap();
530
531 let runtime = RuntimeBuilder::new().build();
533 let output = template.render(&runtime).unwrap();
534 assert_eq!(output, "nope");
535
536 let runtime = RuntimeBuilder::new().build();
538 runtime.set_global("truthy".into(), Value::Nil);
539 let output = template.render(&runtime).unwrap();
540 assert_eq!(output, "nope");
541
542 let runtime = RuntimeBuilder::new().build();
544 runtime.set_global("truthy".into(), Value::scalar(false));
545 let output = template.render(&runtime).unwrap();
546 assert_eq!(output, "nope");
547
548 let runtime = RuntimeBuilder::new().build();
550 runtime.set_global("truthy".into(), Value::scalar(true));
551 let output = template.render(&runtime).unwrap();
552 assert_eq!(output, "yep");
553 }
554
555 #[test]
556 fn unless() {
557 let text = concat!(
558 "{% unless some_value == 1 %}",
559 "unless body",
560 "{% endunless %}"
561 );
562
563 let template = parser::parse(text, &options())
564 .map(runtime::Template::new)
565 .unwrap();
566
567 let runtime = RuntimeBuilder::new().build();
568 runtime.set_global("some_value".into(), Value::scalar(1f64));
569 let output = template.render(&runtime).unwrap();
570 assert_eq!(output, "");
571
572 let runtime = RuntimeBuilder::new().build();
573 runtime.set_global("some_value".into(), Value::scalar(42f64));
574 let output = template.render(&runtime).unwrap();
575 assert_eq!(output, "unless body");
576 }
577
578 #[test]
579 fn nested_if_else() {
580 let text = concat!(
581 "{% if truthy %}",
582 "yep, ",
583 "{% if also_truthy %}",
584 "also truthy",
585 "{% else %}",
586 "not also truthy",
587 "{% endif %}",
588 "{% else %}",
589 "nope",
590 "{% endif %}"
591 );
592 let template = parser::parse(text, &options())
593 .map(runtime::Template::new)
594 .unwrap();
595
596 let runtime = RuntimeBuilder::new().build();
597 runtime.set_global("truthy".into(), Value::scalar(true));
598 runtime.set_global("also_truthy".into(), Value::scalar(false));
599 let output = template.render(&runtime).unwrap();
600 assert_eq!(output, "yep, not also truthy");
601 }
602
603 #[test]
604 fn multiple_elif_blocks() {
605 let text = concat!(
606 "{% if a == 1 %}",
607 "first",
608 "{% elsif a == 2 %}",
609 "second",
610 "{% elsif a == 3 %}",
611 "third",
612 "{% else %}",
613 "fourth",
614 "{% endif %}"
615 );
616
617 let template = parser::parse(text, &options())
618 .map(runtime::Template::new)
619 .unwrap();
620
621 let runtime = RuntimeBuilder::new().build();
622 runtime.set_global("a".into(), Value::scalar(1f64));
623 let output = template.render(&runtime).unwrap();
624 assert_eq!(output, "first");
625
626 let runtime = RuntimeBuilder::new().build();
627 runtime.set_global("a".into(), Value::scalar(2f64));
628 let output = template.render(&runtime).unwrap();
629 assert_eq!(output, "second");
630
631 let runtime = RuntimeBuilder::new().build();
632 runtime.set_global("a".into(), Value::scalar(3f64));
633 let output = template.render(&runtime).unwrap();
634 assert_eq!(output, "third");
635
636 let runtime = RuntimeBuilder::new().build();
637 runtime.set_global("a".into(), Value::scalar("else"));
638 let output = template.render(&runtime).unwrap();
639 assert_eq!(output, "fourth");
640 }
641
642 #[test]
643 fn string_contains_with_literals() {
644 let text = "{% if \"Star Wars\" contains \"Star\" %}if true{% endif %}";
645 let template = parser::parse(text, &options())
646 .map(runtime::Template::new)
647 .unwrap();
648
649 let runtime = RuntimeBuilder::new().build();
650 let output = template.render(&runtime).unwrap();
651 assert_eq!(output, "if true");
652
653 let text = "{% if \"Star Wars\" contains \"Alf\" %}if true{% else %}if false{% endif %}";
654 let template = parser::parse(text, &options())
655 .map(runtime::Template::new)
656 .unwrap();
657
658 let runtime = RuntimeBuilder::new().build();
659 let output = template.render(&runtime).unwrap();
660 assert_eq!(output, "if false");
661 }
662
663 #[test]
664 fn string_contains_with_variables() {
665 let text = "{% if movie contains \"Star\" %}if true{% endif %}";
666 let template = parser::parse(text, &options())
667 .map(runtime::Template::new)
668 .unwrap();
669
670 let runtime = RuntimeBuilder::new().build();
671 runtime.set_global("movie".into(), Value::scalar("Star Wars"));
672 let output = template.render(&runtime).unwrap();
673 assert_eq!(output, "if true");
674
675 let text = "{% if movie contains \"Star\" %}if true{% else %}if false{% endif %}";
676 let template = parser::parse(text, &options())
677 .map(runtime::Template::new)
678 .unwrap();
679
680 let runtime = RuntimeBuilder::new().build();
681 runtime.set_global("movie".into(), Value::scalar("Batman"));
682 let output = template.render(&runtime).unwrap();
683 assert_eq!(output, "if false");
684 }
685
686 #[test]
687 fn contains_with_object_and_key() {
688 let text = "{% if movies contains \"Star Wars\" %}if true{% endif %}";
689 let template = parser::parse(text, &options())
690 .map(runtime::Template::new)
691 .unwrap();
692
693 let runtime = RuntimeBuilder::new().build();
694 let mut obj = Object::new();
695 obj.insert("Star Wars".into(), Value::scalar("1977"));
696 runtime.set_global("movies".into(), Value::Object(obj));
697 let output = template.render(&runtime).unwrap();
698 assert_eq!(output, "if true");
699 }
700
701 #[test]
702 fn contains_with_object_and_missing_key() {
703 let text = "{% if movies contains \"Star Wars\" %}if true{% else %}if false{% endif %}";
704 let template = parser::parse(text, &options())
705 .map(runtime::Template::new)
706 .unwrap();
707
708 let runtime = RuntimeBuilder::new().build();
709 let obj = Object::new();
710 runtime.set_global("movies".into(), Value::Object(obj));
711 let output = template.render(&runtime).unwrap();
712 assert_eq!(output, "if false");
713 }
714
715 #[test]
716 fn contains_with_array_and_match() {
717 let text = "{% if movies contains \"Star Wars\" %}if true{% endif %}";
718 let template = parser::parse(text, &options())
719 .map(runtime::Template::new)
720 .unwrap();
721
722 let runtime = RuntimeBuilder::new().build();
723 let arr = vec![
724 Value::scalar("Star Wars"),
725 Value::scalar("Star Trek"),
726 Value::scalar("Alien"),
727 ];
728 runtime.set_global("movies".into(), Value::Array(arr));
729 let output = template.render(&runtime).unwrap();
730 assert_eq!(output, "if true");
731 }
732
733 #[test]
734 fn contains_with_array_and_no_match() {
735 let text = "{% if movies contains \"Star Wars\" %}if true{% else %}if false{% endif %}";
736 let template = parser::parse(text, &options())
737 .map(runtime::Template::new)
738 .unwrap();
739
740 let runtime = RuntimeBuilder::new().build();
741 let arr = vec![Value::scalar("Alien")];
742 runtime.set_global("movies".into(), Value::Array(arr));
743 let output = template.render(&runtime).unwrap();
744 assert_eq!(output, "if false");
745 }
746
747 #[test]
748 fn multiple_conditions_and() {
749 let text = "{% if 1 == 1 and 2 == 2 %}if true{% else %}if false{% endif %}";
750 let template = parser::parse(text, &options())
751 .map(runtime::Template::new)
752 .unwrap();
753
754 let runtime = RuntimeBuilder::new().build();
755 let output = template.render(&runtime).unwrap();
756 assert_eq!(output, "if true");
757
758 let text = "{% if 1 == 1 and 2 != 2 %}if true{% else %}if false{% endif %}";
759 let template = parser::parse(text, &options())
760 .map(runtime::Template::new)
761 .unwrap();
762
763 let runtime = RuntimeBuilder::new().build();
764 let output = template.render(&runtime).unwrap();
765 assert_eq!(output, "if false");
766 }
767
768 #[test]
769 fn multiple_conditions_or() {
770 let text = "{% if 1 == 1 or 2 != 2 %}if true{% else %}if false{% endif %}";
771 let template = parser::parse(text, &options())
772 .map(runtime::Template::new)
773 .unwrap();
774
775 let runtime = RuntimeBuilder::new().build();
776 let output = template.render(&runtime).unwrap();
777 assert_eq!(output, "if true");
778
779 let text = "{% if 1 != 1 or 2 != 2 %}if true{% else %}if false{% endif %}";
780 let template = parser::parse(text, &options())
781 .map(runtime::Template::new)
782 .unwrap();
783
784 let runtime = RuntimeBuilder::new().build();
785 let output = template.render(&runtime).unwrap();
786 assert_eq!(output, "if false");
787 }
788
789 #[test]
790 fn multiple_conditions_and_or() {
791 let text = "{% if 1 == 1 or 2 == 2 and 3 != 3 %}if true{% else %}if false{% endif %}";
792 let template = parser::parse(text, &options())
793 .map(runtime::Template::new)
794 .unwrap();
795
796 let runtime = RuntimeBuilder::new().build();
797 let output = template.render(&runtime).unwrap();
798 assert_eq!(output, "if true");
799 }
800}