cobalt/cobalt_model/
template.rs

1use std::fmt;
2use std::path;
3
4use super::files;
5use crate::error::Result;
6use crate::syntax_highlight;
7use liquid;
8use log::warn;
9use log::{debug, trace};
10use serde::Serialize;
11
12#[derive(Debug, Clone, Serialize)]
13#[serde(deny_unknown_fields)]
14pub struct LiquidBuilder {
15    pub includes_path: path::PathBuf,
16    pub theme: Option<liquid::model::KString>,
17    #[serde(skip)]
18    pub syntax: std::sync::Arc<crate::SyntaxHighlight>,
19}
20
21impl LiquidBuilder {
22    pub fn build(self) -> Result<Liquid> {
23        let highlight = syntax_highlight::CodeBlockParser::new(self.syntax, self.theme)?;
24        let highlight: Box<dyn liquid_core::ParseBlock> = Box::new(highlight);
25        let parser = liquid::ParserBuilder::with_stdlib()
26            .filter(liquid_lib::extra::DateInTz)
27            .filter(liquid_lib::shopify::Pluralize)
28            // Intentionally staying with `stdlib::IncludeTag` rather than `jekyll::IncludeTag`
29            .filter(liquid_lib::jekyll::Slugify)
30            .filter(liquid_lib::jekyll::Pop)
31            .filter(liquid_lib::jekyll::Push)
32            .filter(liquid_lib::jekyll::Shift)
33            .filter(liquid_lib::jekyll::Unshift)
34            .filter(liquid_lib::jekyll::ArrayToSentenceString)
35            .partials(load_partials_from_path(self.includes_path)?)
36            .block(highlight)
37            .build()?;
38        Ok(Liquid { parser })
39    }
40}
41
42type Partials = liquid::partials::EagerCompiler<liquid::partials::InMemorySource>;
43
44fn load_partials_from_path(root: path::PathBuf) -> Result<Partials> {
45    let mut source = Partials::empty();
46
47    debug!("Loading snippets from `{}`", root.display());
48    let template_files = files::FilesBuilder::new(root)?
49        .ignore_hidden(false)?
50        .build()?;
51    for file_path in template_files.files() {
52        let rel_path = file_path
53            .strip_prefix(template_files.root())
54            .expect("file was found under the root")
55            .to_str()
56            .expect("only UTF-8 characters supported in paths")
57            .to_owned();
58        trace!("Loading snippet `{rel_path}`");
59        match files::read_file(file_path) {
60            Ok(content) => {
61                source.add(rel_path, content);
62            }
63            Err(err) => {
64                warn!("Ignoring snippet {rel_path}: {err}");
65            }
66        }
67    }
68    Ok(source)
69}
70
71pub struct Liquid {
72    parser: liquid::Parser,
73}
74
75impl Liquid {
76    pub fn parse(&self, template: &str) -> Result<liquid::Template> {
77        let template = self.parser.parse(template)?;
78        Ok(template)
79    }
80}
81
82impl fmt::Debug for Liquid {
83    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84        write!(f, "Liquid{{}}")
85    }
86}