liquid_lib/stdlib/tags/
increment_tags.rs

1use std::io::Write;
2
3use liquid_core::error::ResultLiquidReplaceExt;
4use liquid_core::model::{Value, ValueView};
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 IncrementTag;
13
14impl IncrementTag {
15    pub fn new() -> Self {
16        Self
17    }
18}
19
20impl TagReflection for IncrementTag {
21    fn tag(&self) -> &'static str {
22        "increment"
23    }
24
25    fn description(&self) -> &'static str {
26        ""
27    }
28}
29
30impl ParseTag for IncrementTag {
31    fn parse(
32        &self,
33        mut arguments: TagTokenIter<'_>,
34        _options: &Language,
35    ) -> Result<Box<dyn Renderable>> {
36        let id = arguments
37            .expect_next("Identifier expected.")?
38            .expect_identifier()
39            .into_result()?
40            .to_owned()
41            .into();
42
43        // no more arguments should be supplied, trying to supply them is an error
44        arguments.expect_nothing()?;
45
46        Ok(Box::new(Increment { id }))
47    }
48
49    fn reflection(&self) -> &dyn TagReflection {
50        self
51    }
52}
53
54#[derive(Clone, Debug)]
55struct Increment {
56    id: liquid_core::model::KString,
57}
58
59impl Renderable for Increment {
60    fn render_to(&self, writer: &mut dyn Write, runtime: &dyn Runtime) -> Result<()> {
61        let mut val = runtime
62            .get_index(&self.id)
63            .and_then(|i| i.as_scalar().and_then(|i| i.to_integer()))
64            .unwrap_or(0);
65
66        write!(writer, "{val}").replace("Failed to render")?;
67        val += 1;
68        runtime.set_index(self.id.clone(), Value::scalar(val));
69        Ok(())
70    }
71}
72
73#[derive(Copy, Clone, Debug, Default)]
74pub struct DecrementTag;
75
76impl DecrementTag {
77    pub fn new() -> Self {
78        Self
79    }
80}
81
82impl TagReflection for DecrementTag {
83    fn tag(&self) -> &'static str {
84        "decrement"
85    }
86
87    fn description(&self) -> &'static str {
88        ""
89    }
90}
91
92impl ParseTag for DecrementTag {
93    fn parse(
94        &self,
95        mut arguments: TagTokenIter<'_>,
96        _options: &Language,
97    ) -> Result<Box<dyn Renderable>> {
98        let id = arguments
99            .expect_next("Identifier expected.")?
100            .expect_identifier()
101            .into_result()?
102            .to_owned()
103            .into();
104
105        // no more arguments should be supplied, trying to supply them is an error
106        arguments.expect_nothing()?;
107
108        Ok(Box::new(Decrement { id }))
109    }
110
111    fn reflection(&self) -> &dyn TagReflection {
112        self
113    }
114}
115
116#[derive(Clone, Debug)]
117struct Decrement {
118    id: liquid_core::model::KString,
119}
120
121impl Renderable for Decrement {
122    fn render_to(&self, writer: &mut dyn Write, runtime: &dyn Runtime) -> Result<()> {
123        let mut val = runtime
124            .get_index(&self.id)
125            .and_then(|i| i.as_scalar().and_then(|i| i.to_integer()))
126            .unwrap_or(0);
127
128        val -= 1;
129        write!(writer, "{val}").replace("Failed to render")?;
130        runtime.set_index(self.id.clone(), Value::scalar(val));
131        Ok(())
132    }
133}
134
135#[cfg(test)]
136mod test {
137    use super::*;
138
139    use liquid_core::parser;
140    use liquid_core::runtime;
141    use liquid_core::runtime::RuntimeBuilder;
142
143    use crate::stdlib;
144
145    fn options() -> Language {
146        let mut options = Language::default();
147        options
148            .tags
149            .register("assign".to_owned(), stdlib::AssignTag.into());
150        options
151            .tags
152            .register("increment".to_owned(), IncrementTag.into());
153        options
154            .tags
155            .register("decrement".to_owned(), DecrementTag.into());
156        options
157    }
158
159    #[test]
160    fn increment() {
161        let text = "{% increment val %}{{ val }}";
162        let template = parser::parse(text, &options())
163            .map(runtime::Template::new)
164            .unwrap();
165
166        let runtime = RuntimeBuilder::new().build();
167        let output = template.render(&runtime).unwrap();
168        assert_eq!(output, "01");
169    }
170
171    #[test]
172    fn decrement() {
173        let text = "{% decrement val %}{{ val }}";
174        let template = parser::parse(text, &options())
175            .map(runtime::Template::new)
176            .unwrap();
177
178        let runtime = RuntimeBuilder::new().build();
179        let output = template.render(&runtime).unwrap();
180        assert_eq!(output, "-1-1");
181    }
182
183    #[test]
184    fn increment_and_decrement() {
185        let text = "{% increment val %}{% increment val %}{% decrement val %}{% decrement val %}";
186        let template = parser::parse(text, &options())
187            .map(runtime::Template::new)
188            .unwrap();
189
190        let runtime = RuntimeBuilder::new().build();
191        let output = template.render(&runtime).unwrap();
192        assert_eq!(output, "0110");
193    }
194
195    #[test]
196    fn assign_and_increment() {
197        let text = "{%- assign val = 9 -%}{% increment val %}{% increment val %}{{ val }}";
198        let template = parser::parse(text, &options())
199            .map(runtime::Template::new)
200            .unwrap();
201
202        let runtime = RuntimeBuilder::new().build();
203        let output = template.render(&runtime).unwrap();
204        assert_eq!(output, "019");
205    }
206}