liquid_lib/stdlib/blocks/
capture_block.rs1use 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 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}