liquid_lib/stdlib/blocks/
capture_block.rs

1use std::io::Write;
2
3use liquid_core::error::ResultLiquidExt;
4use liquid_core::model::Value;
5use liquid_core::Language;
6use liquid_core::Renderable;
7use liquid_core::Result;
8use liquid_core::Runtime;
9use liquid_core::Template;
10use liquid_core::{BlockReflection, ParseBlock, TagBlock, TagTokenIter};
11
12#[derive(Copy, Clone, Debug, Default)]
13pub struct CaptureBlock;
14
15impl CaptureBlock {
16    pub fn new() -> Self {
17        Self
18    }
19}
20
21impl BlockReflection for CaptureBlock {
22    fn start_tag(&self) -> &str {
23        "capture"
24    }
25
26    fn end_tag(&self) -> &str {
27        "endcapture"
28    }
29
30    fn description(&self) -> &str {
31        ""
32    }
33}
34
35impl ParseBlock for CaptureBlock {
36    fn parse(
37        &self,
38        mut arguments: TagTokenIter<'_>,
39        mut tokens: TagBlock<'_, '_>,
40        options: &Language,
41    ) -> Result<Box<dyn Renderable>> {
42        let id = arguments
43            .expect_next("Identifier expected")?
44            .expect_identifier()
45            .into_result()?
46            .to_owned()
47            .into();
48
49        // no more arguments should be supplied, trying to supply them is an error
50        arguments.expect_nothing()?;
51
52        let template = Template::new(
53            tokens
54                .parse_all(options)
55                .trace_with(|| format!("{{% capture {} %}}", &id).into())?,
56        );
57
58        tokens.assert_empty();
59        Ok(Box::new(Capture { id, template }))
60    }
61
62    fn reflection(&self) -> &dyn BlockReflection {
63        self
64    }
65}
66
67#[derive(Debug)]
68struct Capture {
69    id: liquid_core::model::KString,
70    template: Template,
71}
72
73impl Capture {
74    fn trace(&self) -> String {
75        format!("{{% capture {} %}}", self.id)
76    }
77}
78
79impl Renderable for Capture {
80    fn render_to(&self, _writer: &mut dyn Write, runtime: &dyn Runtime) -> Result<()> {
81        let mut captured = Vec::new();
82        self.template
83            .render_to(&mut captured, runtime)
84            .trace_with(|| self.trace().into())?;
85
86        let output = String::from_utf8(captured).expect("render only writes UTF-8");
87        runtime.set_global(self.id.clone(), Value::scalar(output));
88        Ok(())
89    }
90}
91
92#[cfg(test)]
93mod test {
94    use super::*;
95
96    use liquid_core::model::Scalar;
97    use liquid_core::parser;
98    use liquid_core::runtime::RuntimeBuilder;
99
100    fn options() -> Language {
101        let mut options = Language::default();
102        options
103            .blocks
104            .register("capture".to_owned(), CaptureBlock.into());
105        options
106    }
107
108    #[test]
109    fn test_capture() {
110        let text = concat!(
111            "{% capture attribute_name %}",
112            "{{ item }}-{{ i }}-color",
113            "{% endcapture %}"
114        );
115        let options = options();
116        let template = parser::parse(text, &options).map(Template::new).unwrap();
117
118        let rt = RuntimeBuilder::new().build();
119        rt.set_global("item".into(), Value::scalar("potato"));
120        rt.set_global("i".into(), Value::scalar(42f64));
121
122        let output = template.render(&rt).unwrap();
123        assert_eq!(
124            rt.get(&[Scalar::new("attribute_name")]).unwrap(),
125            "potato-42-color"
126        );
127        assert_eq!(output, "");
128    }
129
130    #[test]
131    fn trailing_tokens_are_an_error() {
132        let text = concat!(
133            "{% capture foo bar baz %}",
134            "We should never see this",
135            "{% endcapture %}"
136        );
137        let options = options();
138        let template = parser::parse(text, &options).map(Template::new);
139        assert!(template.is_err());
140    }
141}