1use std::fs::File;
2use std::io::prelude::Read;
3use std::path;
4use std::sync;
5
6use liquid_core::error::{Result, ResultLiquidExt, ResultLiquidReplaceExt};
7use liquid_core::parser;
8use liquid_core::runtime;
9
10use super::Template;
11use crate::reflection;
12use liquid_core::partials;
13#[cfg(feature = "stdlib")]
14use liquid_lib::stdlib;
15
16type Partials = partials::EagerCompiler<partials::InMemorySource>;
17
18pub struct ParserBuilder<P = Partials>
19where
20 P: partials::PartialCompiler,
21{
22 blocks: parser::PluginRegistry<Box<dyn parser::ParseBlock>>,
23 tags: parser::PluginRegistry<Box<dyn parser::ParseTag>>,
24 filters: parser::PluginRegistry<Box<dyn parser::ParseFilter>>,
25 partials: Option<P>,
26}
27
28impl ParserBuilder<Partials> {
29 pub fn new() -> Self {
31 Self::default()
32 }
33
34 #[cfg(feature = "stdlib")]
35 pub fn with_stdlib() -> Self {
36 Self::new().stdlib()
37 }
38}
39
40impl<P> ParserBuilder<P>
41where
42 P: partials::PartialCompiler,
43{
44 #[cfg(feature = "stdlib")]
45 pub fn stdlib(self) -> Self {
47 self.tag(stdlib::AssignTag)
48 .tag(stdlib::BreakTag)
49 .tag(stdlib::ContinueTag)
50 .tag(stdlib::CycleTag)
51 .tag(stdlib::IncludeTag)
52 .tag(stdlib::IncrementTag)
53 .tag(stdlib::DecrementTag)
54 .tag(stdlib::RenderTag)
55 .block(stdlib::RawBlock)
56 .block(stdlib::IfBlock)
57 .block(stdlib::UnlessBlock)
58 .block(stdlib::IfChangedBlock)
59 .block(stdlib::ForBlock)
60 .block(stdlib::TableRowBlock)
61 .block(stdlib::CommentBlock)
62 .block(stdlib::CaptureBlock)
63 .block(stdlib::CaseBlock)
64 .filter(stdlib::Abs)
65 .filter(stdlib::Append)
66 .filter(stdlib::AtLeast)
67 .filter(stdlib::AtMost)
68 .filter(stdlib::Capitalize)
69 .filter(stdlib::Ceil)
70 .filter(stdlib::Compact)
71 .filter(stdlib::Concat)
72 .filter(stdlib::Date)
73 .filter(stdlib::Default)
74 .filter(stdlib::DividedBy)
75 .filter(stdlib::Downcase)
76 .filter(stdlib::Escape)
77 .filter(stdlib::EscapeOnce)
78 .filter(stdlib::First)
79 .filter(stdlib::Floor)
80 .filter(stdlib::Join)
81 .filter(stdlib::Last)
82 .filter(stdlib::Lstrip)
83 .filter(stdlib::Map)
84 .filter(stdlib::Minus)
85 .filter(stdlib::Modulo)
86 .filter(stdlib::NewlineToBr)
87 .filter(stdlib::Plus)
88 .filter(stdlib::Prepend)
89 .filter(stdlib::Remove)
90 .filter(stdlib::RemoveFirst)
91 .filter(stdlib::Replace)
92 .filter(stdlib::ReplaceFirst)
93 .filter(stdlib::Reverse)
94 .filter(stdlib::Round)
95 .filter(stdlib::Rstrip)
96 .filter(stdlib::Size)
97 .filter(stdlib::Slice)
98 .filter(stdlib::Sort)
99 .filter(stdlib::SortNatural)
100 .filter(stdlib::Split)
101 .filter(stdlib::Strip)
102 .filter(stdlib::StripHtml)
103 .filter(stdlib::StripNewlines)
104 .filter(stdlib::Times)
105 .filter(stdlib::Truncate)
106 .filter(stdlib::TruncateWords)
107 .filter(stdlib::Uniq)
108 .filter(stdlib::Upcase)
109 .filter(stdlib::UrlDecode)
110 .filter(stdlib::UrlEncode)
111 .filter(stdlib::Where)
112 }
113
114 pub fn block<B: Into<Box<dyn parser::ParseBlock>>>(mut self, block: B) -> Self {
116 let block = block.into();
117 self.blocks
118 .register(block.reflection().start_tag().to_owned(), block);
119 self
120 }
121
122 pub fn tag<T: Into<Box<dyn parser::ParseTag>>>(mut self, tag: T) -> Self {
124 let tag = tag.into();
125 self.tags.register(tag.reflection().tag().to_owned(), tag);
126 self
127 }
128
129 pub fn filter<F: Into<Box<dyn parser::ParseFilter>>>(mut self, filter: F) -> Self {
131 let filter = filter.into();
132 self.filters
133 .register(filter.reflection().name().to_owned(), filter);
134 self
135 }
136
137 pub fn partials<N: partials::PartialCompiler>(self, partials: N) -> ParserBuilder<N> {
139 let Self {
140 blocks,
141 tags,
142 filters,
143 partials: _partials,
144 } = self;
145 ParserBuilder {
146 blocks,
147 tags,
148 filters,
149 partials: Some(partials),
150 }
151 }
152
153 pub fn build(self) -> Result<Parser> {
155 let Self {
156 blocks,
157 tags,
158 filters,
159 partials,
160 } = self;
161
162 let mut options = parser::Language::empty();
163 options.blocks = blocks;
164 options.tags = tags;
165 options.filters = filters;
166 let options = sync::Arc::new(options);
167 let partials = partials
168 .map(|p| p.compile(options.clone()))
169 .map(|r| r.map(Some))
170 .unwrap_or(Ok(None))?
171 .map(|p| p.into());
172 let p = Parser { options, partials };
173 Ok(p)
174 }
175}
176
177impl<P> Default for ParserBuilder<P>
178where
179 P: partials::PartialCompiler,
180{
181 fn default() -> Self {
182 Self {
183 blocks: Default::default(),
184 tags: Default::default(),
185 filters: Default::default(),
186 partials: Default::default(),
187 }
188 }
189}
190
191impl<P> reflection::ParserReflection for ParserBuilder<P>
192where
193 P: partials::PartialCompiler,
194{
195 fn blocks<'r>(&'r self) -> Box<dyn Iterator<Item = &dyn parser::BlockReflection> + 'r> {
196 Box::new(self.blocks.plugins().map(|p| p.reflection()))
197 }
198
199 fn tags<'r>(&'r self) -> Box<dyn Iterator<Item = &dyn parser::TagReflection> + 'r> {
200 Box::new(self.tags.plugins().map(|p| p.reflection()))
201 }
202
203 fn filters<'r>(&'r self) -> Box<dyn Iterator<Item = &dyn parser::FilterReflection> + 'r> {
204 Box::new(self.filters.plugins().map(|p| p.reflection()))
205 }
206
207 fn partials<'r>(&'r self) -> Box<dyn Iterator<Item = &str> + 'r> {
208 Box::new(
209 self.partials
210 .as_ref()
211 .into_iter()
212 .flat_map(|s| s.source().names()),
213 )
214 }
215}
216
217#[derive(Default, Clone)]
218pub struct Parser {
219 options: sync::Arc<parser::Language>,
220 partials: Option<sync::Arc<dyn runtime::PartialStore + Send + Sync>>,
221}
222
223impl Parser {
224 pub fn new() -> Self {
225 Default::default()
226 }
227
228 pub fn parse(&self, text: &str) -> Result<Template> {
244 let template = parser::parse(text, &self.options).map(runtime::Template::new)?;
245 Ok(Template {
246 template,
247 partials: self.partials.clone(),
248 })
249 }
250
251 pub fn parse_file<P: AsRef<path::Path>>(&self, file: P) -> Result<Template> {
277 self.parse_file_path(file.as_ref())
278 }
279
280 fn parse_file_path(&self, file: &path::Path) -> Result<Template> {
281 let mut f = File::open(file)
282 .replace("Cannot open file")
283 .context_key("path")
284 .value_with(|| file.to_string_lossy().into_owned().into())?;
285 let mut buf = String::new();
286 f.read_to_string(&mut buf)
287 .replace("Cannot read file")
288 .context_key("path")
289 .value_with(|| file.to_string_lossy().into_owned().into())?;
290
291 self.parse(&buf)
292 }
293}
294
295impl reflection::ParserReflection for Parser {
296 fn blocks<'r>(&'r self) -> Box<dyn Iterator<Item = &dyn parser::BlockReflection> + 'r> {
297 Box::new(self.options.blocks.plugins().map(|p| p.reflection()))
298 }
299
300 fn tags<'r>(&'r self) -> Box<dyn Iterator<Item = &dyn parser::TagReflection> + 'r> {
301 Box::new(self.options.tags.plugins().map(|p| p.reflection()))
302 }
303
304 fn filters<'r>(&'r self) -> Box<dyn Iterator<Item = &dyn parser::FilterReflection> + 'r> {
305 Box::new(self.options.filters.plugins().map(|p| p.reflection()))
306 }
307
308 fn partials<'r>(&'r self) -> Box<dyn Iterator<Item = &str> + 'r> {
309 Box::new(self.partials.as_ref().into_iter().flat_map(|s| s.names()))
310 }
311}