1 use std::collections::HashMap;
2 use std::fmt;
3 use std::sync;
4 
5 use crate::error::Result;
6 use crate::parser;
7 use crate::parser::Language;
8 use crate::runtime;
9 use crate::runtime::PartialStore;
10 use crate::runtime::Renderable;
11 
12 use super::PartialCompiler;
13 use super::PartialSource;
14 
15 /// An lazily-caching compiler for `PartialSource`.
16 ///
17 /// This would be useful in cases where:
18 /// - Most partial-templates aren't used
19 /// - Of the used partial-templates, they are generally used many times.
20 ///
21 /// Note: partial-compilation error reporting is deferred to render-time so content can still be
22 /// generated even when the content is in an intermediate-state.
23 #[derive(Debug)]
24 pub struct LazyCompiler<S: PartialSource> {
25     source: S,
26 }
27 
28 impl<S> LazyCompiler<S>
29 where
30     S: PartialSource,
31 {
32     /// Create an on-demand compiler for `PartialSource`.
new(source: S) -> Self33     pub fn new(source: S) -> Self {
34         LazyCompiler { source }
35     }
36 }
37 
38 impl<S> LazyCompiler<S>
39 where
40     S: PartialSource + Default,
41 {
42     /// Create an empty compiler for `PartialSource`.
empty() -> Self43     pub fn empty() -> Self {
44         Default::default()
45     }
46 }
47 
48 impl<S> Default for LazyCompiler<S>
49 where
50     S: PartialSource + Default,
51 {
default() -> Self52     fn default() -> Self {
53         Self {
54             source: Default::default(),
55         }
56     }
57 }
58 
59 impl<S> ::std::ops::Deref for LazyCompiler<S>
60 where
61     S: PartialSource,
62 {
63     type Target = S;
64 
deref(&self) -> &S65     fn deref(&self) -> &S {
66         &self.source
67     }
68 }
69 
70 impl<S> ::std::ops::DerefMut for LazyCompiler<S>
71 where
72     S: PartialSource,
73 {
deref_mut(&mut self) -> &mut S74     fn deref_mut(&mut self) -> &mut S {
75         &mut self.source
76     }
77 }
78 
79 impl<S> PartialCompiler for LazyCompiler<S>
80 where
81     S: PartialSource + Send + Sync + 'static,
82 {
compile(self, language: sync::Arc<Language>) -> Result<Box<dyn PartialStore + Send + Sync>>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 
source(&self) -> &dyn PartialSource92     fn source(&self) -> &dyn PartialSource {
93         &self.source
94     }
95 }
96 
97 struct 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 
103 impl<S> LazyStore<S>
104 where
105     S: PartialSource,
106 {
try_get_or_create(&self, name: &str) -> Option<sync::Arc<dyn Renderable>>107     fn try_get_or_create(&self, name: &str) -> Option<sync::Arc<dyn Renderable>> {
108         let 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                 .ok()?;
118             Some(template)
119         }
120     }
121 
get_or_create(&self, name: &str) -> Result<sync::Arc<dyn Renderable>>122     fn get_or_create(&self, name: &str) -> Result<sync::Arc<dyn Renderable>> {
123         let cache = self.cache.lock().expect("not to be poisoned and reused");
124         if let Some(result) = cache.get(name) {
125             result.clone()
126         } else {
127             let s = self.source.get(name)?;
128             let s = s.as_ref();
129             let template = parser::parse(s, &self.language)
130                 .map(runtime::Template::new)
131                 .map(sync::Arc::new)?;
132             Ok(template)
133         }
134     }
135 }
136 
137 impl<S> PartialStore for LazyStore<S>
138 where
139     S: PartialSource,
140 {
contains(&self, name: &str) -> bool141     fn contains(&self, name: &str) -> bool {
142         self.source.contains(name)
143     }
144 
names(&self) -> Vec<&str>145     fn names(&self) -> Vec<&str> {
146         self.source.names()
147     }
148 
try_get(&self, name: &str) -> Option<sync::Arc<dyn Renderable>>149     fn try_get(&self, name: &str) -> Option<sync::Arc<dyn Renderable>> {
150         self.try_get_or_create(name)
151     }
152 
get(&self, name: &str) -> Result<sync::Arc<dyn Renderable>>153     fn get(&self, name: &str) -> Result<sync::Arc<dyn Renderable>> {
154         self.get_or_create(name)
155     }
156 }
157 
158 impl<S> fmt::Debug for LazyStore<S>
159 where
160     S: PartialSource,
161 {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result162     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163         self.source.fmt(f)
164     }
165 }
166