liquid_core/partials/
lazy.rs1use std::collections::HashMap;
2use std::fmt;
3use std::sync;
4
5use crate::error::Result;
6use crate::parser;
7use crate::parser::Language;
8use crate::runtime;
9use crate::runtime::PartialStore;
10use crate::runtime::Renderable;
11
12use super::PartialCompiler;
13use super::PartialSource;
14
15#[derive(Debug)]
24pub struct LazyCompiler<S: PartialSource> {
25 source: S,
26}
27
28impl<S> LazyCompiler<S>
29where
30 S: PartialSource,
31{
32 pub fn new(source: S) -> Self {
34 LazyCompiler { source }
35 }
36}
37
38impl<S> LazyCompiler<S>
39where
40 S: PartialSource + Default,
41{
42 pub fn empty() -> Self {
44 Default::default()
45 }
46}
47
48impl<S> Default for LazyCompiler<S>
49where
50 S: PartialSource + Default,
51{
52 fn default() -> Self {
53 Self {
54 source: Default::default(),
55 }
56 }
57}
58
59impl<S> ::std::ops::Deref for LazyCompiler<S>
60where
61 S: PartialSource,
62{
63 type Target = S;
64
65 fn deref(&self) -> &S {
66 &self.source
67 }
68}
69
70impl<S> ::std::ops::DerefMut for LazyCompiler<S>
71where
72 S: PartialSource,
73{
74 fn deref_mut(&mut self) -> &mut S {
75 &mut self.source
76 }
77}
78
79impl<S> PartialCompiler for LazyCompiler<S>
80where
81 S: PartialSource + Send + Sync + 'static,
82{
83 fn compile(self, language: sync::Arc<Language>) -> Result<Box<dyn PartialStore + Send + Sync>> {
84 let store = LazyStore {
85 language,
86 source: self.source,
87 cache: sync::Mutex::new(Default::default()),
88 };
89 Ok(Box::new(store))
90 }
91
92 fn source(&self) -> &dyn PartialSource {
93 &self.source
94 }
95}
96
97struct LazyStore<S: PartialSource> {
98 language: sync::Arc<Language>,
99 source: S,
100 cache: sync::Mutex<HashMap<String, Result<sync::Arc<dyn runtime::Renderable>>>>,
101}
102
103impl<S> LazyStore<S>
104where
105 S: PartialSource,
106{
107 fn try_get_or_create(&self, name: &str) -> Option<sync::Arc<dyn Renderable>> {
108 let mut cache = self.cache.lock().expect("not to be poisoned and reused");
109 if let Some(result) = cache.get(name) {
110 result.as_ref().ok().cloned()
111 } else {
112 let s = self.source.try_get(name)?;
113 let s = s.as_ref();
114 let template = parser::parse(s, &self.language)
115 .map(runtime::Template::new)
116 .map(sync::Arc::new)
117 .map(|t| t as sync::Arc<dyn Renderable>);
118 cache.insert(name.to_string(), template.clone());
119 template.ok()
120 }
121 }
122
123 fn get_or_create(&self, name: &str) -> Result<sync::Arc<dyn Renderable>> {
124 let mut cache = self.cache.lock().expect("not to be poisoned and reused");
125 if let Some(result) = cache.get(name) {
126 result.clone()
127 } else {
128 let s = self.source.get(name)?;
129 let s = s.as_ref();
130 let template = parser::parse(s, &self.language)
131 .map(runtime::Template::new)
132 .map(sync::Arc::new)
133 .map(|t| t as sync::Arc<dyn Renderable>);
134 cache.insert(name.to_string(), template.clone());
135 template
136 }
137 }
138}
139
140impl<S> PartialStore for LazyStore<S>
141where
142 S: PartialSource,
143{
144 fn contains(&self, name: &str) -> bool {
145 self.source.contains(name)
146 }
147
148 fn names(&self) -> Vec<&str> {
149 self.source.names()
150 }
151
152 fn try_get(&self, name: &str) -> Option<sync::Arc<dyn Renderable>> {
153 self.try_get_or_create(name)
154 }
155
156 fn get(&self, name: &str) -> Result<sync::Arc<dyn Renderable>> {
157 self.get_or_create(name)
158 }
159}
160
161impl<S> fmt::Debug for LazyStore<S>
162where
163 S: PartialSource,
164{
165 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166 self.source.fmt(f)
167 }
168}
169
170#[cfg(test)]
171mod test {
172 use crate::partials::lazy;
173 use crate::runtime::PartialStore;
174 use crate::{partials, Language};
175 use std::{borrow, sync};
176
177 #[derive(Default, Debug, Clone, Copy)]
178 struct TestSource;
179
180 impl partials::PartialSource for TestSource {
181 fn contains(&self, _name: &str) -> bool {
182 true
183 }
184
185 fn names(&self) -> Vec<&str> {
186 vec![]
187 }
188
189 fn try_get<'a>(&'a self, name: &str) -> Option<borrow::Cow<'a, str>> {
190 match name {
191 "example.txt" => Some("Hello Liquid!".into()),
192 _ => None,
193 }
194 }
195 }
196
197 #[test]
198 fn test_store_caches_get() {
199 let options = Language::empty();
200 let store = lazy::LazyStore {
201 language: sync::Arc::new(options),
202 source: TestSource,
203 cache: sync::Mutex::new(Default::default()),
204 };
205
206 assert!(
207 !store.cache.lock().unwrap().contains_key("example.txt"),
208 "The store cache should not contain the key yet."
209 );
210
211 let _ = store.get("example.txt").unwrap();
213
214 assert!(
215 store.cache.lock().unwrap().contains_key("example.txt"),
216 "The store cache should now contain the key."
217 );
218 }
219
220 #[test]
221 fn test_store_caches_try_get() {
222 let options = Language::empty();
223 let store = lazy::LazyStore {
224 language: sync::Arc::new(options),
225 source: TestSource,
226 cache: sync::Mutex::new(Default::default()),
227 };
228
229 assert!(
230 !store.cache.lock().unwrap().contains_key("example.txt"),
231 "The store cache should not contain the key yet."
232 );
233
234 let _ = store.try_get("example.txt").unwrap();
236
237 assert!(
238 store.cache.lock().unwrap().contains_key("example.txt"),
239 "The store cache should now contain the key."
240 );
241 }
242}