liquid_lib/stdlib/tags/
assign_tag.rs1use std::io::Write;
2
3use liquid_core::error::ResultLiquidExt;
4use liquid_core::parser::FilterChain;
5use liquid_core::Language;
6use liquid_core::Renderable;
7use liquid_core::Result;
8use liquid_core::Runtime;
9use liquid_core::{ParseTag, TagReflection, TagTokenIter};
10
11#[derive(Copy, Clone, Debug, Default)]
12pub struct AssignTag;
13
14impl AssignTag {
15 pub fn new() -> Self {
16 Self
17 }
18}
19
20impl TagReflection for AssignTag {
21 fn tag(&self) -> &'static str {
22 "assign"
23 }
24
25 fn description(&self) -> &'static str {
26 ""
27 }
28}
29
30impl ParseTag for AssignTag {
31 fn parse(
32 &self,
33 mut arguments: TagTokenIter<'_>,
34 options: &Language,
35 ) -> Result<Box<dyn Renderable>> {
36 let dst = arguments
37 .expect_next("Identifier expected.")?
38 .expect_identifier()
39 .into_result()?
40 .to_owned()
41 .into();
42
43 arguments
44 .expect_next("Assignment operator \"=\" expected.")?
45 .expect_str("=")
46 .into_result_custom_msg("Assignment operator \"=\" expected.")?;
47
48 let src = arguments
49 .expect_next("FilterChain expected.")?
50 .expect_filter_chain(options)
51 .into_result()?;
52
53 arguments.expect_nothing()?;
55
56 Ok(Box::new(Assign { dst, src }))
57 }
58
59 fn reflection(&self) -> &dyn TagReflection {
60 self
61 }
62}
63
64#[derive(Debug)]
65struct Assign {
66 dst: liquid_core::model::KString,
67 src: FilterChain,
68}
69
70impl Assign {
71 fn trace(&self) -> String {
72 format!("{{% assign {} = {}%}}", self.dst, self.src)
73 }
74}
75
76impl Renderable for Assign {
77 fn render_to(&self, _writer: &mut dyn Write, runtime: &dyn Runtime) -> Result<()> {
78 let value = self
79 .src
80 .evaluate(runtime)
81 .trace_with(|| self.trace().into())?
82 .into_owned();
83 runtime.set_global(self.dst.clone(), value);
84 Ok(())
85 }
86}
87
88#[cfg(test)]
89mod test {
90 use super::*;
91
92 use liquid_core::model::Scalar;
93 use liquid_core::model::Value;
94 use liquid_core::parser;
95 use liquid_core::runtime;
96 use liquid_core::runtime::RuntimeBuilder;
97
98 use crate::stdlib;
99
100 fn options() -> Language {
101 let mut options = Language::default();
102 options.tags.register("assign".to_owned(), AssignTag.into());
103 options
104 .blocks
105 .register("if".to_owned(), stdlib::IfBlock.into());
106 options
107 .blocks
108 .register("for".to_owned(), stdlib::ForBlock.into());
109 options
110 }
111
112 #[test]
113 fn assign() {
114 let options = options();
115 let template = parser::parse("{% assign freestyle = false %}{{ freestyle }}", &options)
116 .map(runtime::Template::new)
117 .unwrap();
118
119 let runtime = RuntimeBuilder::new().build();
120
121 let output = template.render(&runtime).unwrap();
122 assert_eq!(output, "false");
123 }
124
125 #[test]
126 fn assign_array_indexing() {
127 let text = concat!("{% assign freestyle = tags[1] %}", "{{ freestyle }}");
128 let options = options();
129 let template = parser::parse(text, &options)
130 .map(runtime::Template::new)
131 .unwrap();
132
133 let runtime = RuntimeBuilder::new().build();
134 runtime.set_global(
135 "tags".into(),
136 Value::Array(vec![
137 Value::scalar("alpha"),
138 Value::scalar("beta"),
139 Value::scalar("gamma"),
140 ]),
141 );
142
143 let output = template.render(&runtime).unwrap();
144 assert_eq!(output, "beta");
145 }
146
147 #[test]
148 fn assign_object_indexing() {
149 let text = concat!(
150 r#"{% assign freestyle = tags["greek"] %}"#,
151 "{{ freestyle }}"
152 );
153 let options = options();
154 let template = parser::parse(text, &options)
155 .map(runtime::Template::new)
156 .unwrap();
157
158 let runtime = RuntimeBuilder::new().build();
159 runtime.set_global(
160 "tags".into(),
161 Value::Object(
162 vec![("greek".into(), Value::scalar("alpha"))]
163 .into_iter()
164 .collect(),
165 ),
166 );
167
168 let output = template.render(&runtime).unwrap();
169 assert_eq!(output, "alpha");
170 }
171
172 #[test]
173 fn assign_in_loop_persists_on_loop_exit() {
174 let text = concat!(
175 "{% assign freestyle = false %}",
176 "{% for t in tags %}{% if t == 'freestyle' %}",
177 "{% assign freestyle = true %}",
178 "{% endif %}{% endfor %}",
179 "{% if freestyle %}",
180 "<p>Freestyle!</p>",
181 "{% endif %}"
182 );
183
184 let options = options();
185 let template = parser::parse(text, &options)
186 .map(runtime::Template::new)
187 .unwrap();
188
189 {
191 let runtime = RuntimeBuilder::new().build();
192 runtime.set_global(
193 "tags".into(),
194 Value::Array(vec![
195 Value::scalar("alpha"),
196 Value::scalar("beta"),
197 Value::scalar("gamma"),
198 ]),
199 );
200
201 let output = template.render(&runtime).unwrap();
202 assert_eq!(runtime.get(&[Scalar::new("freestyle")]).unwrap(), false);
203 assert_eq!(output, "");
204 }
205
206 {
208 let runtime = RuntimeBuilder::new().build();
209 runtime.set_global(
210 "tags".into(),
211 Value::Array(vec![
212 Value::scalar("alpha"),
213 Value::scalar("beta"),
214 Value::scalar("freestyle"),
215 Value::scalar("gamma"),
216 ]),
217 );
218
219 let output = template.render(&runtime).unwrap();
220 assert_eq!(runtime.get(&[Scalar::new("freestyle")]).unwrap(), true);
221 assert_eq!(output, "<p>Freestyle!</p>");
222 }
223 }
224}