• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

cobalt-org / liquid-rust / 17333951250

29 Aug 2025 08:47PM UTC coverage: 56.499% (+3.2%) from 53.304%
17333951250

push

github

web-flow
Merge pull request #592 from juharris/patch-1

docs(README) Correct typo "suite" -> "suit"

2756 of 4878 relevant lines covered (56.5%)

4.14 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

81.58
/src/parser.rs
1
use std::path;
2
use std::sync;
3

4
use liquid_core::error::{Result, ResultLiquidExt, ResultLiquidReplaceExt};
5
use liquid_core::parser;
6
use liquid_core::runtime;
7

8
use super::Template;
9
use crate::reflection;
10
use liquid_core::partials;
11
#[cfg(feature = "stdlib")]
12
use liquid_lib::stdlib;
13

14
type Partials = partials::EagerCompiler<partials::InMemorySource>;
×
15

16
pub struct ParserBuilder<P = Partials>
17
where
18
    P: partials::PartialCompiler,
19
{
20
    blocks: parser::PluginRegistry<Box<dyn parser::ParseBlock>>,
21
    tags: parser::PluginRegistry<Box<dyn parser::ParseTag>>,
22
    filters: parser::PluginRegistry<Box<dyn parser::ParseFilter>>,
23
    partials: Option<P>,
24
}
25

26
impl ParserBuilder<Partials> {
3✔
27
    /// Create an empty Liquid parser
28
    pub fn new() -> Self {
21✔
29
        Self::default()
20✔
30
    }
31

32
    #[cfg(feature = "stdlib")]
33
    pub fn with_stdlib() -> Self {
15✔
34
        Self::new().stdlib()
17✔
35
    }
36
}
37

38
impl<P> ParserBuilder<P>
×
39
where
40
    P: partials::PartialCompiler,
41
{
42
    #[cfg(feature = "stdlib")]
43
    /// Create a Liquid parser with built-in Liquid features
44
    pub fn stdlib(self) -> Self {
18✔
45
        self.tag(stdlib::AssignTag)
17✔
46
            .tag(stdlib::BreakTag)
16✔
47
            .tag(stdlib::ContinueTag)
16✔
48
            .tag(stdlib::CycleTag)
16✔
49
            .tag(stdlib::IncludeTag)
17✔
50
            .tag(stdlib::IncrementTag)
15✔
51
            .tag(stdlib::DecrementTag)
14✔
52
            .tag(stdlib::RenderTag)
13✔
53
            .block(stdlib::RawBlock)
13✔
54
            .block(stdlib::IfBlock)
11✔
55
            .block(stdlib::UnlessBlock)
11✔
56
            .block(stdlib::IfChangedBlock)
11✔
57
            .block(stdlib::ForBlock)
10✔
58
            .block(stdlib::TableRowBlock)
10✔
59
            .block(stdlib::CommentBlock)
11✔
60
            .block(stdlib::CaptureBlock)
11✔
61
            .block(stdlib::CaseBlock)
10✔
62
            .filter(stdlib::Abs)
9✔
63
            .filter(stdlib::Append)
10✔
64
            .filter(stdlib::AtLeast)
11✔
65
            .filter(stdlib::AtMost)
10✔
66
            .filter(stdlib::Capitalize)
11✔
67
            .filter(stdlib::Ceil)
10✔
68
            .filter(stdlib::Compact)
11✔
69
            .filter(stdlib::Concat)
9✔
70
            .filter(stdlib::Date)
12✔
71
            .filter(stdlib::Default)
10✔
72
            .filter(stdlib::DividedBy)
12✔
73
            .filter(stdlib::Downcase)
10✔
74
            .filter(stdlib::Escape)
14✔
75
            .filter(stdlib::EscapeOnce)
10✔
76
            .filter(stdlib::First)
12✔
77
            .filter(stdlib::Floor)
12✔
78
            .filter(stdlib::Join)
15✔
79
            .filter(stdlib::Last)
13✔
80
            .filter(stdlib::Lstrip)
15✔
81
            .filter(stdlib::Map)
13✔
82
            .filter(stdlib::Minus)
15✔
83
            .filter(stdlib::Modulo)
13✔
84
            .filter(stdlib::NewlineToBr)
15✔
85
            .filter(stdlib::Plus)
13✔
86
            .filter(stdlib::Prepend)
14✔
87
            .filter(stdlib::Remove)
10✔
88
            .filter(stdlib::RemoveFirst)
11✔
89
            .filter(stdlib::Replace)
10✔
90
            .filter(stdlib::ReplaceFirst)
10✔
91
            .filter(stdlib::Reverse)
12✔
92
            .filter(stdlib::Round)
10✔
93
            .filter(stdlib::Rstrip)
12✔
94
            .filter(stdlib::Size)
9✔
95
            .filter(stdlib::Slice)
10✔
96
            .filter(stdlib::Sort)
15✔
97
            .filter(stdlib::SortNatural)
17✔
98
            .filter(stdlib::Split)
11✔
99
            .filter(stdlib::Strip)
13✔
100
            .filter(stdlib::StripHtml)
10✔
101
            .filter(stdlib::StripNewlines)
13✔
102
            .filter(stdlib::Times)
11✔
103
            .filter(stdlib::Truncate)
10✔
104
            .filter(stdlib::TruncateWords)
11✔
105
            .filter(stdlib::Uniq)
10✔
106
            .filter(stdlib::Upcase)
12✔
107
            .filter(stdlib::UrlDecode)
10✔
108
            .filter(stdlib::UrlEncode)
11✔
109
            .filter(stdlib::Where)
10✔
110
    }
111

112
    /// Inserts a new custom block into the parser
113
    pub fn block<B: Into<Box<dyn parser::ParseBlock>>>(mut self, block: B) -> Self {
95✔
114
        let block = block.into();
189✔
115
        self.blocks
×
116
            .register(block.reflection().start_tag().to_owned(), block);
191✔
117
        self
94✔
118
    }
119

120
    /// Inserts a new custom tag into the parser
121
    pub fn tag<T: Into<Box<dyn parser::ParseTag>>>(mut self, tag: T) -> Self {
124✔
122
        let tag = tag.into();
245✔
123
        self.tags.register(tag.reflection().tag().to_owned(), tag);
247✔
124
        self
123✔
125
    }
126

127
    /// Inserts a new custom filter into the parser
128
    pub fn filter<F: Into<Box<dyn parser::ParseFilter>>>(mut self, filter: F) -> Self {
591✔
129
        let filter = filter.into();
1,164✔
130
        self.filters
595✔
131
            .register(filter.reflection().name().to_owned(), filter);
1,173✔
132
        self
582✔
133
    }
134

135
    /// Set which partial-templates will be available.
136
    pub fn partials<N: partials::PartialCompiler>(self, partials: N) -> ParserBuilder<N> {
6✔
137
        let Self {
×
138
            blocks,
7✔
139
            tags,
6✔
140
            filters,
8✔
141
            partials: _partials,
7✔
142
        } = self;
×
143
        ParserBuilder {
144
            blocks,
145
            tags,
146
            filters,
147
            partials: Some(partials),
9✔
148
        }
149
    }
150

151
    /// Create a parser
152
    pub fn build(self) -> Result<Parser> {
19✔
153
        let Self {
18✔
154
            blocks,
19✔
155
            tags,
18✔
156
            filters,
19✔
157
            partials,
18✔
158
        } = self;
×
159

160
        let mut options = parser::Language::empty();
17✔
161
        options.blocks = blocks;
18✔
162
        options.tags = tags;
17✔
163
        options.filters = filters;
18✔
164
        let options = sync::Arc::new(options);
35✔
165
        let partials = partials
49✔
166
            .map(|p| p.compile(options.clone()))
32✔
167
            .map(|r| r.map(Some))
31✔
168
            .unwrap_or(Ok(None))?
19✔
169
            .map(|p| p.into());
32✔
170
        let p = Parser { options, partials };
19✔
171
        Ok(p)
16✔
172
    }
173
}
174

175
impl<P> Default for ParserBuilder<P>
176
where
177
    P: partials::PartialCompiler,
178
{
179
    fn default() -> Self {
22✔
180
        Self {
181
            blocks: Default::default(),
22✔
182
            tags: Default::default(),
22✔
183
            filters: Default::default(),
21✔
184
            partials: Default::default(),
21✔
185
        }
186
    }
187
}
188

189
impl<P> reflection::ParserReflection for ParserBuilder<P>
×
190
where
191
    P: partials::PartialCompiler,
192
{
193
    fn blocks(&self) -> Box<dyn Iterator<Item = &dyn parser::BlockReflection> + '_> {
×
194
        Box::new(self.blocks.plugins().map(|p| p.reflection()))
×
195
    }
196

197
    fn tags(&self) -> Box<dyn Iterator<Item = &dyn parser::TagReflection> + '_> {
×
198
        Box::new(self.tags.plugins().map(|p| p.reflection()))
×
199
    }
200

201
    fn filters(&self) -> Box<dyn Iterator<Item = &dyn parser::FilterReflection> + '_> {
×
202
        Box::new(self.filters.plugins().map(|p| p.reflection()))
×
203
    }
204

205
    fn partials(&self) -> Box<dyn Iterator<Item = &str> + '_> {
×
206
        Box::new(
207
            self.partials
×
208
                .as_ref()
×
209
                .into_iter()
×
210
                .flat_map(|s| s.source().names()),
×
211
        )
212
    }
213
}
214

215
#[derive(Default, Clone)]
216
pub struct Parser {
217
    options: sync::Arc<parser::Language>,
218
    partials: Option<sync::Arc<dyn runtime::PartialStore + Send + Sync>>,
219
}
220

221
impl Parser {
222
    pub fn new() -> Self {
×
223
        Default::default()
×
224
    }
225

226
    /// Parses a liquid template, returning a Template object.
227
    /// # Examples
228
    ///
229
    /// ## Minimal Template
230
    ///
231
    /// ```
232
    /// let template = liquid::ParserBuilder::with_stdlib()
233
    ///     .build().unwrap()
234
    ///     .parse("Liquid!").unwrap();
235
    ///
236
    /// let globals = liquid::Object::new();
237
    /// let output = template.render(&globals).unwrap();
238
    /// assert_eq!(output, "Liquid!".to_string());
239
    /// ```
240
    ///
241
    pub fn parse(&self, text: &str) -> Result<Template> {
13✔
242
        let template = parser::parse(text, &self.options).map(runtime::Template::new)?;
23✔
243
        Ok(Template {
10✔
244
            template,
10✔
245
            partials: self.partials.clone(),
8✔
246
        })
247
    }
248

249
    /// Parse a liquid template from a file, returning a `Result<Template, Error>`.
250
    /// # Examples
251
    ///
252
    /// ## Minimal Template
253
    ///
254
    /// `template.txt`:
255
    ///
256
    /// ```text
257
    /// "Liquid {{data}}"
258
    /// ```
259
    ///
260
    /// Your rust code:
261
    ///
262
    /// ```rust,no_run
263
    /// let template = liquid::ParserBuilder::with_stdlib()
264
    ///     .build().unwrap()
265
    ///     .parse_file("path/to/template.txt").unwrap();
266
    ///
267
    /// let globals = liquid::object!({
268
    ///     "data": 4f64,
269
    /// });
270
    /// let output = template.render(&globals).unwrap();
271
    /// assert_eq!(output, "Liquid! 4\n".to_string());
272
    /// ```
273
    ///
274
    pub fn parse_file<P: AsRef<path::Path>>(&self, file: P) -> Result<Template> {
6✔
275
        self.parse_file_path(file.as_ref())
10✔
276
    }
277

278
    fn parse_file_path(&self, file: &path::Path) -> Result<Template> {
3✔
279
        let buf = std::fs::read_to_string(file)
11✔
280
            .replace("Cannot open file")
281
            .context_key("path")
282
            .value_with(|| file.to_string_lossy().into_owned().into())?;
6✔
283

284
        self.parse(&buf)
8✔
285
    }
286
}
287

288
impl reflection::ParserReflection for Parser {
289
    fn blocks(&self) -> Box<dyn Iterator<Item = &dyn parser::BlockReflection> + '_> {
×
290
        Box::new(self.options.blocks.plugins().map(|p| p.reflection()))
×
291
    }
292

293
    fn tags(&self) -> Box<dyn Iterator<Item = &dyn parser::TagReflection> + '_> {
×
294
        Box::new(self.options.tags.plugins().map(|p| p.reflection()))
×
295
    }
296

297
    fn filters(&self) -> Box<dyn Iterator<Item = &dyn parser::FilterReflection> + '_> {
×
298
        Box::new(self.options.filters.plugins().map(|p| p.reflection()))
×
299
    }
300

301
    fn partials(&self) -> Box<dyn Iterator<Item = &str> + '_> {
×
302
        Box::new(self.partials.as_ref().into_iter().flat_map(|s| s.names()))
×
303
    }
304
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc