use std::fs::File;
use std::io::prelude::Read;
use std::path;
use std::sync;
use liquid_compiler as compiler;
use liquid_error::{Result, ResultLiquidExt, ResultLiquidReplaceExt};
use liquid_interpreter as interpreter;
use super::Template;
use filters;
use partials;
use tags;
pub type Partials = partials::EagerCompiler<partials::InMemorySource>;
pub struct ParserBuilder<P = Partials>
where
P: partials::PartialCompiler,
{
blocks: compiler::PluginRegistry<compiler::BoxedBlockParser>,
tags: compiler::PluginRegistry<compiler::BoxedTagParser>,
filters: compiler::PluginRegistry<compiler::BoxedValueFilter>,
partials: Option<P>,
}
impl ParserBuilder<Partials> {
pub fn new() -> Self {
Self::default()
}
pub fn with_liquid() -> Self {
Self::new().liquid()
}
}
impl<P> ParserBuilder<P>
where
P: partials::PartialCompiler,
{
pub fn liquid(self) -> Self {
self.liquid_tags().liquid_blocks().liquid_filters()
}
pub fn liquid_tags(self) -> Self {
self.tag("assign", tags::assign_tag as compiler::FnParseTag)
.tag("break", tags::break_tag as compiler::FnParseTag)
.tag("continue", tags::continue_tag as compiler::FnParseTag)
.tag("cycle", tags::cycle_tag as compiler::FnParseTag)
.tag("include", tags::include_tag as compiler::FnParseTag)
.tag("increment", tags::increment_tag as compiler::FnParseTag)
.tag("decrement", tags::decrement_tag as compiler::FnParseTag)
}
pub fn liquid_blocks(self) -> Self {
self.block("raw", tags::raw_block as compiler::FnParseBlock)
.block("if", tags::if_block as compiler::FnParseBlock)
.block("unless", tags::unless_block as compiler::FnParseBlock)
.block("ifchanged", tags::ifchanged_block as compiler::FnParseBlock)
.block("for", tags::for_block as compiler::FnParseBlock)
.block("tablerow", tags::tablerow_block as compiler::FnParseBlock)
.block("comment", tags::comment_block as compiler::FnParseBlock)
.block("capture", tags::capture_block as compiler::FnParseBlock)
.block("case", tags::case_block as compiler::FnParseBlock)
}
pub fn liquid_filters(self) -> Self {
self.filter("abs", filters::abs as compiler::FnFilterValue)
.filter("append", filters::append as compiler::FnFilterValue)
.filter("at_least", filters::at_least as compiler::FnFilterValue)
.filter("at_most", filters::at_most as compiler::FnFilterValue)
.filter("capitalize", filters::capitalize as compiler::FnFilterValue)
.filter("ceil", filters::ceil as compiler::FnFilterValue)
.filter("compact", filters::compact as compiler::FnFilterValue)
.filter("concat", filters::concat as compiler::FnFilterValue)
.filter("date", filters::date as compiler::FnFilterValue)
.filter("default", filters::default as compiler::FnFilterValue)
.filter("divided_by", filters::divided_by as compiler::FnFilterValue)
.filter("downcase", filters::downcase as compiler::FnFilterValue)
.filter("escape", filters::escape as compiler::FnFilterValue)
.filter(
"escape_once",
filters::escape_once as compiler::FnFilterValue,
)
.filter("first", filters::first as compiler::FnFilterValue)
.filter("floor", filters::floor as compiler::FnFilterValue)
.filter("join", filters::join as compiler::FnFilterValue)
.filter("last", filters::last as compiler::FnFilterValue)
.filter("lstrip", filters::lstrip as compiler::FnFilterValue)
.filter("map", filters::map as compiler::FnFilterValue)
.filter("minus", filters::minus as compiler::FnFilterValue)
.filter("modulo", filters::modulo as compiler::FnFilterValue)
.filter(
"newline_to_br",
filters::newline_to_br as compiler::FnFilterValue,
)
.filter("plus", filters::plus as compiler::FnFilterValue)
.filter("prepend", filters::prepend as compiler::FnFilterValue)
.filter("remove", filters::remove as compiler::FnFilterValue)
.filter(
"remove_first",
filters::remove_first as compiler::FnFilterValue,
)
.filter("replace", filters::replace as compiler::FnFilterValue)
.filter(
"replace_first",
filters::replace_first as compiler::FnFilterValue,
)
.filter("reverse", filters::reverse as compiler::FnFilterValue)
.filter("round", filters::round as compiler::FnFilterValue)
.filter("rstrip", filters::rstrip as compiler::FnFilterValue)
.filter("size", filters::size as compiler::FnFilterValue)
.filter("slice", filters::slice as compiler::FnFilterValue)
.filter("sort", filters::sort as compiler::FnFilterValue)
.filter(
"sort_natural",
filters::sort_natural as compiler::FnFilterValue,
)
.filter("split", filters::split as compiler::FnFilterValue)
.filter("strip", filters::strip as compiler::FnFilterValue)
.filter("strip_html", filters::strip_html as compiler::FnFilterValue)
.filter(
"strip_newlines",
filters::strip_newlines as compiler::FnFilterValue,
)
.filter("times", filters::times as compiler::FnFilterValue)
.filter("truncate", filters::truncate as compiler::FnFilterValue)
.filter(
"truncatewords",
filters::truncatewords as compiler::FnFilterValue,
)
.filter("uniq", filters::uniq as compiler::FnFilterValue)
.filter("upcase", filters::upcase as compiler::FnFilterValue)
.filter("url_decode", filters::url_decode as compiler::FnFilterValue)
.filter("url_encode", filters::url_encode as compiler::FnFilterValue)
}
#[cfg(not(feature = "extra-filters"))]
pub fn extra_filters(self) -> Self {
self
}
#[cfg(feature = "extra-filters")]
pub fn extra_filters(self) -> Self {
self.filter("pluralize", filters::pluralize as compiler::FnFilterValue)
.filter("date_in_tz", filters::date_in_tz as compiler::FnFilterValue)
.filter("push", filters::push as compiler::FnFilterValue)
.filter("pop", filters::pop as compiler::FnFilterValue)
.filter("unshift", filters::unshift as compiler::FnFilterValue)
.filter("shift", filters::shift as compiler::FnFilterValue)
.filter(
"array_to_sentence_string",
filters::array_to_sentence_string as compiler::FnFilterValue,
)
}
#[cfg(not(feature = "jekyll-filters"))]
pub fn jekyll_filters(self) -> Self {
self
}
#[cfg(feature = "jekyll-filters")]
pub fn jekyll_filters(self) -> Self {
self.filter("slugify", filters::slugify as compiler::FnFilterValue)
}
pub fn block<B: Into<compiler::BoxedBlockParser>>(
mut self,
name: &'static str,
block: B,
) -> Self {
self.blocks.register(name, block.into());
self
}
pub fn tag<T: Into<compiler::BoxedTagParser>>(mut self, name: &'static str, tag: T) -> Self {
self.tags.register(name, tag.into());
self
}
pub fn filter<F: Into<compiler::BoxedValueFilter>>(
mut self,
name: &'static str,
filter: F,
) -> Self {
self.filters.register(name, filter.into());
self
}
pub fn partials<N: partials::PartialCompiler>(self, partials: N) -> ParserBuilder<N> {
let Self {
blocks,
tags,
filters,
partials: _partials,
} = self;
ParserBuilder {
blocks,
tags,
filters,
partials: Some(partials),
}
}
pub fn build(self) -> Result<Parser> {
let Self {
blocks,
tags,
filters,
partials,
} = self;
let mut options = compiler::Language::empty();
options.blocks = blocks;
options.tags = tags;
options.filters = filters;
let options = sync::Arc::new(options);
let partials = partials
.map(|p| p.compile(options.clone()))
.map_or(Ok(None), |r| r.map(Some))?
.map(|p| p.into());
let p = Parser { options, partials };
Ok(p)
}
}
impl<P> Default for ParserBuilder<P>
where
P: partials::PartialCompiler,
{
fn default() -> Self {
Self {
blocks: Default::default(),
tags: Default::default(),
filters: Default::default(),
partials: Default::default(),
}
}
}
#[derive(Default, Clone)]
pub struct Parser {
options: sync::Arc<compiler::Language>,
partials: Option<sync::Arc<interpreter::PartialStore + Send + Sync>>,
}
impl Parser {
pub fn new() -> Self {
Default::default()
}
pub fn parse(&self, text: &str) -> Result<Template> {
let template = compiler::parse(text, &self.options).map(interpreter::Template::new)?;
Ok(Template {
template,
partials: self.partials.clone(),
})
}
pub fn parse_file<P: AsRef<path::Path>>(self, file: P) -> Result<Template> {
self.parse_file_path(file.as_ref())
}
fn parse_file_path(self, file: &path::Path) -> Result<Template> {
let mut f = File::open(file)
.replace("Cannot open file")
.context_key("path")
.value_with(|| file.to_string_lossy().into_owned().into())?;
let mut buf = String::new();
f.read_to_string(&mut buf)
.replace("Cannot read file")
.context_key("path")
.value_with(|| file.to_string_lossy().into_owned().into())?;
self.parse(&buf)
}
}