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 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 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 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 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}