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
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::RuntimeBuilder;
113
114 use crate::stdlib;
115
116 fn options() -> Language {
117 let mut options = Language::default();
118 options
119 .blocks
120 .register("ifchanged".to_owned(), IfChangedBlock.into());
121 options
122 .blocks
123 .register("for".to_owned(), stdlib::ForBlock.into());
124 options
125 .blocks
126 .register("if".to_owned(), stdlib::IfBlock.into());
127 options
128 }
129
130 #[test]
131 fn test_ifchanged_block() {
132 let text = concat!(
133 "{% for a in (0..10) %}",
134 "{% ifchanged %}",
135 "\nHey! ",
136 "{% if a > 5 %}",
137 "Numbers are now bigger than 5!",
138 "{% endif %}",
139 "{% endifchanged %}",
140 "{% endfor %}",
141 );
142 let template = parser::parse(text, &options()).map(Template::new).unwrap();
143
144 let runtime = RuntimeBuilder::new().build();
145 let output = template.render(&runtime).unwrap();
146 assert_eq!(output, "\nHey! \nHey! Numbers are now bigger than 5!");
147 }
148}