loose_liquid_lib/stdlib/blocks/
ifchanged_block.rs1use std::io::Write;
2
3use liquid_core::error::{ResultLiquidExt, ResultLiquidReplaceExt};
4use liquid_core::Language;
5use liquid_core::Renderable;
6use liquid_core::Result;
7use liquid_core::Runtime;
8use liquid_core::Template;
9use liquid_core::{BlockReflection, ParseBlock, TagBlock, TagTokenIter};
10
11#[derive(Copy, Clone, Debug, Default)]
12pub struct IfChangedBlock;
13
14impl IfChangedBlock {
15 pub fn new() -> Self {
16 Self::default()
17 }
18}
19
20impl BlockReflection for IfChangedBlock {
21 fn start_tag(&self) -> &str {
22 "ifchanged"
23 }
24
25 fn end_tag(&self) -> &str {
26 "endifchanged"
27 }
28
29 fn description(&self) -> &str {
30 ""
31 }
32}
33
34impl ParseBlock for IfChangedBlock {
35 fn parse(
36 &self,
37 mut arguments: TagTokenIter<'_>,
38 mut tokens: TagBlock<'_, '_>,
39 options: &Language,
40 ) -> Result<Box<dyn Renderable>> {
41 arguments.expect_nothing()?;
43
44 let if_changed = Template::new(tokens.parse_all(options)?);
45
46 tokens.assert_empty();
47 Ok(Box::new(IfChanged { if_changed }))
48 }
49
50 fn reflection(&self) -> &dyn BlockReflection {
51 self
52 }
53}
54
55#[derive(Debug)]
56struct IfChanged {
57 if_changed: Template,
58}
59
60impl IfChanged {
61 fn trace(&self) -> String {
62 "{{% ifchanged %}}".to_owned()
63 }
64}
65
66impl Renderable for IfChanged {
67 fn render_to(&self, writer: &mut dyn Write, runtime: &dyn Runtime) -> Result<()> {
68 let mut rendered = Vec::new();
69 self.if_changed
70 .render_to(&mut rendered, runtime)
71 .trace_with(|| self.trace().into())?;
72
73 let rendered = String::from_utf8(rendered).expect("render only writes UTF-8");
74 if runtime
75 .registers()
76 .get_mut::<ChangedRegister>()
77 .has_changed(&rendered)
78 {
79 write!(writer, "{}", rendered).replace("Failed to render")?;
80 }
81
82 Ok(())
83 }
84}
85
86#[derive(Debug, Clone, PartialEq, Eq, Default)]
88struct ChangedRegister {
89 last_rendered: Option<String>,
90}
91
92impl ChangedRegister {
93 fn has_changed(&mut self, rendered: &str) -> bool {
96 let has_changed = if let Some(last_rendered) = &self.last_rendered {
97 last_rendered != rendered
98 } else {
99 true
100 };
101 self.last_rendered = Some(rendered.to_owned());
102
103 has_changed
104 }
105}
106
107#[cfg(test)]
108mod test {
109 use super::*;
110
111 use liquid_core::parser;
112 use liquid_core::runtime;
113 use liquid_core::runtime::RuntimeBuilder;
114
115 use crate::stdlib;
116
117 fn options() -> Language {
118 let mut options = Language::default();
119 options
120 .blocks
121 .register("ifchanged".to_string(), IfChangedBlock.into());
122 options
123 .blocks
124 .register("for".to_string(), stdlib::ForBlock.into());
125 options
126 .blocks
127 .register("if".to_string(), stdlib::IfBlock.into());
128 options
129 }
130
131 #[test]
132 fn test_ifchanged_block() {
133 let text = concat!(
134 "{% for a in (0..10) %}",
135 "{% ifchanged %}",
136 "\nHey! ",
137 "{% if a > 5 %}",
138 "Numbers are now bigger than 5!",
139 "{% endif %}",
140 "{% endifchanged %}",
141 "{% endfor %}",
142 );
143 let template = parser::parse(text, &options())
144 .map(runtime::Template::new)
145 .unwrap();
146
147 let runtime = RuntimeBuilder::new().build();
148 let output = template.render(&runtime).unwrap();
149 assert_eq!(output, "\nHey! \nHey! Numbers are now bigger than 5!");
150 }
151}