liquid_lib/stdlib/tags/
interrupt_tags.rs

1use std::io::Write;
2
3use liquid_core::runtime::{Interrupt, InterruptRegister};
4use liquid_core::Language;
5use liquid_core::Renderable;
6use liquid_core::Result;
7use liquid_core::Runtime;
8use liquid_core::{ParseTag, TagReflection, TagTokenIter};
9
10#[derive(Copy, Clone, Debug, Default)]
11pub struct BreakTag;
12
13impl BreakTag {
14    pub fn new() -> Self {
15        Self
16    }
17}
18
19impl TagReflection for BreakTag {
20    fn tag(&self) -> &'static str {
21        "break"
22    }
23
24    fn description(&self) -> &'static str {
25        ""
26    }
27}
28
29impl ParseTag for BreakTag {
30    fn parse(
31        &self,
32        mut arguments: TagTokenIter<'_>,
33        _options: &Language,
34    ) -> Result<Box<dyn Renderable>> {
35        // no arguments should be supplied, trying to supply them is an error
36        arguments.expect_nothing()?;
37        Ok(Box::new(Break))
38    }
39
40    fn reflection(&self) -> &dyn TagReflection {
41        self
42    }
43}
44
45#[derive(Copy, Clone, Debug)]
46struct Break;
47
48impl Renderable for Break {
49    fn render_to(&self, _writer: &mut dyn Write, runtime: &dyn Runtime) -> Result<()> {
50        runtime
51            .registers()
52            .get_mut::<InterruptRegister>()
53            .set(Interrupt::Break);
54        Ok(())
55    }
56}
57
58#[derive(Copy, Clone, Debug, Default)]
59pub struct ContinueTag;
60
61impl ContinueTag {
62    pub fn new() -> Self {
63        Self
64    }
65}
66
67impl TagReflection for ContinueTag {
68    fn tag(&self) -> &'static str {
69        "continue"
70    }
71
72    fn description(&self) -> &'static str {
73        ""
74    }
75}
76
77impl ParseTag for ContinueTag {
78    fn parse(
79        &self,
80        mut arguments: TagTokenIter<'_>,
81        _options: &Language,
82    ) -> Result<Box<dyn Renderable>> {
83        // no arguments should be supplied, trying to supply them is an error
84        arguments.expect_nothing()?;
85        Ok(Box::new(Continue))
86    }
87
88    fn reflection(&self) -> &dyn TagReflection {
89        self
90    }
91}
92
93#[derive(Copy, Clone, Debug)]
94struct Continue;
95
96impl Renderable for Continue {
97    fn render_to(&self, _writer: &mut dyn Write, runtime: &dyn Runtime) -> Result<()> {
98        runtime
99            .registers()
100            .get_mut::<InterruptRegister>()
101            .set(Interrupt::Continue);
102        Ok(())
103    }
104}
105
106#[cfg(test)]
107mod test {
108    use super::*;
109
110    use liquid_core::parser;
111    use liquid_core::runtime;
112    use liquid_core::runtime::RuntimeBuilder;
113
114    use crate::stdlib;
115
116    fn options() -> Language {
117        let mut options = Language::default();
118        options.tags.register("break".to_owned(), BreakTag.into());
119        options
120            .tags
121            .register("continue".to_owned(), ContinueTag.into());
122        options
123            .blocks
124            .register("for".to_owned(), stdlib::ForBlock.into());
125        options
126            .blocks
127            .register("if".to_owned(), stdlib::IfBlock.into());
128        options
129    }
130
131    #[test]
132    fn test_simple_break() {
133        let text = concat!(
134            "{% for i in (0..10) %}",
135            "enter-{{i}};",
136            "{% if i == 2 %}break-{{i}}\n{% break %}{% endif %}",
137            "exit-{{i}}\n",
138            "{% endfor %}"
139        );
140        let template = parser::parse(text, &options())
141            .map(runtime::Template::new)
142            .unwrap();
143
144        let rt = RuntimeBuilder::new().build();
145        let output = template.render(&rt).unwrap();
146        assert_eq!(
147            output,
148            concat!("enter-0;exit-0\n", "enter-1;exit-1\n", "enter-2;break-2\n")
149        );
150    }
151
152    #[test]
153    fn test_nested_break() {
154        // assert that a {% break %} only breaks out of the innermost loop
155        let text = concat!(
156            "{% for outer in (0..3) %}",
157            "enter-{{outer}}; ",
158            "{% for inner in (6..10) %}",
159            "{% if inner == 8 %}break, {% break %}{% endif %}",
160            "{{ inner }}, ",
161            "{% endfor %}",
162            "exit-{{outer}}\n",
163            "{% endfor %}"
164        );
165        let template = parser::parse(text, &options())
166            .map(runtime::Template::new)
167            .unwrap();
168
169        let rt = RuntimeBuilder::new().build();
170        let output = template.render(&rt).unwrap();
171        assert_eq!(
172            output,
173            concat!(
174                "enter-0; 6, 7, break, exit-0\n",
175                "enter-1; 6, 7, break, exit-1\n",
176                "enter-2; 6, 7, break, exit-2\n",
177                "enter-3; 6, 7, break, exit-3\n",
178            )
179        );
180    }
181
182    #[test]
183    fn test_simple_continue() {
184        let text = concat!(
185            "{% for i in (0..5) %}",
186            "enter-{{i}};",
187            "{% if i == 2 %}continue-{{i}}\n{% continue %}{% endif %}",
188            "exit-{{i}}\n",
189            "{% endfor %}"
190        );
191        let template = parser::parse(text, &options())
192            .map(runtime::Template::new)
193            .unwrap();
194
195        let rt = RuntimeBuilder::new().build();
196        let output = template.render(&rt).unwrap();
197        assert_eq!(
198            output,
199            concat!(
200                "enter-0;exit-0\n",
201                "enter-1;exit-1\n",
202                "enter-2;continue-2\n",
203                "enter-3;exit-3\n",
204                "enter-4;exit-4\n",
205                "enter-5;exit-5\n",
206            )
207        );
208    }
209
210    #[test]
211    fn test_nested_continue() {
212        // assert that a {% continue %} only jumps out of the innermost loop
213        let text = concat!(
214            "{% for outer in (0..3) %}",
215            "enter-{{outer}}; ",
216            "{% for inner in (6..10) %}",
217            "{% if inner == 8 %}continue, {% continue %}{% endif %}",
218            "{{ inner }}, ",
219            "{% endfor %}",
220            "exit-{{outer}}\n",
221            "{% endfor %}"
222        );
223        let template = parser::parse(text, &options())
224            .map(runtime::Template::new)
225            .unwrap();
226
227        let rt = RuntimeBuilder::new().build();
228        let output = template.render(&rt).unwrap();
229        assert_eq!(
230            output,
231            concat!(
232                "enter-0; 6, 7, continue, 9, 10, exit-0\n",
233                "enter-1; 6, 7, continue, 9, 10, exit-1\n",
234                "enter-2; 6, 7, continue, 9, 10, exit-2\n",
235                "enter-3; 6, 7, continue, 9, 10, exit-3\n",
236            )
237        );
238    }
239}