loose_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::default()
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_string()
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;
99 use liquid_core::runtime::RuntimeBuilder;
100
101 fn options() -> Language {
102 let mut options = Language::default();
103 options
104 .blocks
105 .register("capture".to_string(), CaptureBlock.into());
106 options
107 }
108
109 #[test]
110 fn test_capture() {
111 let text = concat!(
112 "{% capture attribute_name %}",
113 "{{ item }}-{{ i }}-color",
114 "{% endcapture %}"
115 );
116 let options = options();
117 let template = parser::parse(text, &options)
118 .map(runtime::Template::new)
119 .unwrap();
120
121 let rt = RuntimeBuilder::new().build();
122 rt.set_global("item".into(), Value::scalar("potato"));
123 rt.set_global("i".into(), Value::scalar(42f64));
124
125 let output = template.render(&rt).unwrap();
126 assert_eq!(
127 rt.get(&[Scalar::new("attribute_name")]).unwrap(),
128 "potato-42-color"
129 );
130 assert_eq!(output, "");
131 }
132
133 #[test]
134 fn trailing_tokens_are_an_error() {
135 let text = concat!(
136 "{% capture foo bar baz %}",
137 "We should never see this",
138 "{% endcapture %}"
139 );
140 let options = options();
141 let template = parser::parse(text, &options).map(runtime::Template::new);
142 assert!(template.is_err());
143 }
144}