liquid_core/partials/
eager.rs

1use std::collections::HashMap;
2use std::fmt;
3use std::sync;
4
5use crate::error::Error;
6use crate::error::Result;
7use crate::parser;
8use crate::parser::Language;
9use crate::runtime;
10use crate::runtime::PartialStore;
11use crate::runtime::Renderable;
12
13use super::PartialCompiler;
14use super::PartialSource;
15
16/// An eagerly-caching compiler for `PartialSource`.
17///
18/// This would be useful in cases where:
19/// - Most partial-templates are used
20/// - Of the used partial-templates, they are generally used many times.
21///
22/// Note: partial-compilation error reporting is deferred to render-time so content can still be
23/// generated even when the content is in an intermediate-state.
24#[derive(Debug)]
25pub struct EagerCompiler<S: PartialSource> {
26    source: S,
27}
28
29impl<S> EagerCompiler<S>
30where
31    S: PartialSource,
32{
33    /// Create an on-demand compiler for `PartialSource`.
34    pub fn new(source: S) -> Self {
35        EagerCompiler { source }
36    }
37}
38
39impl<S> EagerCompiler<S>
40where
41    S: PartialSource + Default,
42{
43    /// Create an empty compiler for `PartialSource`.
44    pub fn empty() -> Self {
45        Default::default()
46    }
47}
48
49impl<S> Default for EagerCompiler<S>
50where
51    S: PartialSource + Default,
52{
53    fn default() -> Self {
54        Self {
55            source: Default::default(),
56        }
57    }
58}
59
60impl<S> ::std::ops::Deref for EagerCompiler<S>
61where
62    S: PartialSource,
63{
64    type Target = S;
65
66    fn deref(&self) -> &S {
67        &self.source
68    }
69}
70
71impl<S> ::std::ops::DerefMut for EagerCompiler<S>
72where
73    S: PartialSource,
74{
75    fn deref_mut(&mut self) -> &mut S {
76        &mut self.source
77    }
78}
79
80impl<S> PartialCompiler for EagerCompiler<S>
81where
82    S: PartialSource + Send + Sync + 'static,
83{
84    fn compile(self, language: sync::Arc<Language>) -> Result<Box<dyn PartialStore + Send + Sync>> {
85        let store: HashMap<_, _> = self
86            .source
87            .names()
88            .into_iter()
89            .map(|name| {
90                let source = self.source.get(name).and_then(|s| {
91                    parser::parse(s.as_ref(), &language)
92                        .map(runtime::Template::new)
93                        .map(|t| {
94                            let t: sync::Arc<dyn runtime::Renderable> = sync::Arc::new(t);
95                            t
96                        })
97                });
98                (name.to_owned(), source)
99            })
100            .collect();
101        let store = EagerStore { store };
102        Ok(Box::new(store))
103    }
104
105    fn source(&self) -> &dyn PartialSource {
106        &self.source
107    }
108}
109
110struct EagerStore {
111    store: HashMap<String, Result<sync::Arc<dyn runtime::Renderable>>>,
112}
113
114impl PartialStore for EagerStore {
115    fn contains(&self, name: &str) -> bool {
116        self.store.contains_key(name)
117    }
118
119    fn names(&self) -> Vec<&str> {
120        self.store.keys().map(|s| s.as_str()).collect()
121    }
122
123    fn try_get(&self, name: &str) -> Option<sync::Arc<dyn Renderable>> {
124        self.store.get(name).and_then(|r| r.clone().ok())
125    }
126
127    fn get(&self, name: &str) -> Result<sync::Arc<dyn Renderable>> {
128        let result = self.store.get(name).ok_or_else(|| {
129            let mut available: Vec<_> = self.names();
130            available.sort_unstable();
131            let available = itertools::join(available, ", ");
132            Error::with_msg("Unknown partial-template")
133                .context("requested partial", name.to_owned())
134                .context("available partials", available)
135        })?;
136        result.clone()
137    }
138}
139
140impl fmt::Debug for EagerStore {
141    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142        self.names().fmt(f)
143    }
144}