Skip to content

Commit

Permalink
feat: Reflection for tags/blocks
Browse files Browse the repository at this point in the history
Fixes #315

BREAKING CHANGE: Tag and Block APIs have changed, including name of
tag/block implementations.
  • Loading branch information
epage committed May 7, 2019
1 parent 22cf37f commit 72f8cee
Show file tree
Hide file tree
Showing 18 changed files with 853 additions and 628 deletions.
87 changes: 23 additions & 64 deletions liquid-compiler/src/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,22 @@ use super::Language;
use super::TagBlock;
use super::TagTokenIter;

pub trait BlockReflection {
fn start_tag(&self) -> &'static str;

fn end_tag(&self) -> &'static str;

fn description(&self) -> &'static str;

fn example(&self) -> Option<&'static str> {
None
}

fn spec(&self) -> Option<&'static str> {
None
}
}

/// A trait for creating custom custom block-size tags (`{% if something %}{% endif %}`).
/// This is a simple type alias for a function.
///
Expand All @@ -13,10 +29,9 @@ use super::TagTokenIter;
/// of the block, the argument [Tokens](lexer/enum.Token.html) passed to
/// the block, a Vec of all [Elements](lexer/enum.Element.html) inside the block and
/// the global [`Language`](struct.Language.html).
pub trait ParseBlock: Send + Sync + ParseBlockClone {
pub trait ParseBlock: Send + Sync + ParseBlockClone + BlockReflection {
fn parse(
&self,
tag_name: &str,
arguments: TagTokenIter,
block: TagBlock,
options: &Language,
Expand All @@ -42,67 +57,11 @@ impl Clone for Box<ParseBlock> {
}
}

pub type FnParseBlock = fn(&str, TagTokenIter, TagBlock, &Language) -> Result<Box<Renderable>>;

#[derive(Clone)]
struct FnBlockParser {
parser: FnParseBlock,
}

impl FnBlockParser {
fn new(parser: FnParseBlock) -> Self {
Self { parser }
}
}

impl ParseBlock for FnBlockParser {
fn parse(
&self,
tag_name: &str,
arguments: TagTokenIter,
tokens: TagBlock,
options: &Language,
) -> Result<Box<Renderable>> {
(self.parser)(tag_name, arguments, tokens, options)
}
}

#[derive(Clone)]
enum BlockParserEnum {
Fun(FnBlockParser),
Heap(Box<ParseBlock>),
}

#[derive(Clone)]
pub struct BoxedBlockParser {
parser: BlockParserEnum,
}

impl ParseBlock for BoxedBlockParser {
fn parse(
&self,
tag_name: &str,
arguments: TagTokenIter,
tokens: TagBlock,
options: &Language,
) -> Result<Box<Renderable>> {
match self.parser {
BlockParserEnum::Fun(ref f) => f.parse(tag_name, arguments, tokens, options),
BlockParserEnum::Heap(ref f) => f.parse(tag_name, arguments, tokens, options),
}
}
}

impl From<FnParseBlock> for BoxedBlockParser {
fn from(parser: FnParseBlock) -> BoxedBlockParser {
let parser = BlockParserEnum::Fun(FnBlockParser::new(parser));
Self { parser }
}
}

impl From<Box<ParseBlock>> for BoxedBlockParser {
fn from(parser: Box<ParseBlock>) -> BoxedBlockParser {
let parser = BlockParserEnum::Heap(parser);
Self { parser }
impl<T> From<T> for Box<ParseBlock>
where
T: 'static + ParseBlock,
{
fn from(filter: T) -> Self {
Box::new(filter)
}
}
8 changes: 4 additions & 4 deletions liquid-compiler/src/lang.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use super::BoxedBlockParser;
use super::BoxedTagParser;
use super::ParseBlock;
use super::ParseFilter;
use super::ParseTag;
use super::PluginRegistry;

#[derive(Clone)]
pub struct Language {
pub blocks: PluginRegistry<BoxedBlockParser>,
pub tags: PluginRegistry<BoxedTagParser>,
pub blocks: PluginRegistry<Box<ParseBlock>>,
pub tags: PluginRegistry<Box<ParseTag>>,
pub filters: PluginRegistry<Box<ParseFilter>>,
non_exhaustive: (),
}
Expand Down
6 changes: 2 additions & 4 deletions liquid-compiler/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ use liquid_interpreter::Variable;
use liquid_value::Value;

use super::Language;
use super::ParseBlock;
use super::ParseTag;
use super::Text;
use super::{Filter, FilterArguments, FilterChain};

Expand Down Expand Up @@ -537,10 +535,10 @@ impl<'a> Tag<'a> {
let name = name.as_str();

if let Some(plugin) = options.tags.get(name) {
plugin.parse(name, tokens, options)
plugin.parse(tokens, options)
} else if let Some(plugin) = options.blocks.get(name) {
let block = TagBlock::new(name, next_elements);
let renderables = plugin.parse(name, tokens, block, options)?;
let renderables = plugin.parse(tokens, block, options)?;
Ok(renderables)
} else {
let pest_error = ::pest::error::Error::new_from_span(
Expand Down
88 changes: 22 additions & 66 deletions liquid-compiler/src/tag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,28 @@ use liquid_interpreter::Renderable;
use super::Language;
use super::TagTokenIter;

pub trait TagReflection {
fn tag(&self) -> &'static str;

fn description(&self) -> &'static str;

fn example(&self) -> Option<&'static str> {
None
}

fn spec(&self) -> Option<&'static str> {
None
}
}

/// A trait for creating custom tags. This is a simple type alias for a function.
///
/// This function will be called whenever the parser encounters a tag and returns
/// a new [Renderable](trait.Renderable.html) based on its parameters. The received parameters
/// specify the name of the tag, the argument [Tokens](lexer/enum.Token.html) passed to
/// the tag and the global [`Language`](struct.Language.html).
pub trait ParseTag: Send + Sync + ParseTagClone {
fn parse(
&self,
tag_name: &str,
arguments: TagTokenIter,
options: &Language,
) -> Result<Box<Renderable>>;
pub trait ParseTag: Send + Sync + ParseTagClone + TagReflection {
fn parse(&self, arguments: TagTokenIter, options: &Language) -> Result<Box<Renderable>>;
}

pub trait ParseTagClone {
Expand All @@ -37,65 +46,12 @@ impl Clone for Box<ParseTag> {
self.clone_box()
}
}
pub type FnParseTag = fn(&str, TagTokenIter, &Language) -> Result<Box<Renderable>>;

#[derive(Clone)]
struct FnTagParser {
parser: FnParseTag,
}

impl FnTagParser {
fn new(parser: FnParseTag) -> Self {
Self { parser }
}
}

impl ParseTag for FnTagParser {
fn parse(
&self,
tag_name: &str,
arguments: TagTokenIter,
options: &Language,
) -> Result<Box<Renderable>> {
(self.parser)(tag_name, arguments, options)
}
}

#[derive(Clone)]
enum TagParserEnum {
Fun(FnTagParser),
Heap(Box<ParseTag>),
}

#[derive(Clone)]
pub struct BoxedTagParser {
parser: TagParserEnum,
}

impl ParseTag for BoxedTagParser {
fn parse(
&self,
tag_name: &str,
arguments: TagTokenIter,
options: &Language,
) -> Result<Box<Renderable>> {
match self.parser {
TagParserEnum::Fun(ref f) => f.parse(tag_name, arguments, options),
TagParserEnum::Heap(ref f) => f.parse(tag_name, arguments, options),
}
}
}

impl From<FnParseTag> for BoxedTagParser {
fn from(parser: FnParseTag) -> BoxedTagParser {
let parser = TagParserEnum::Fun(FnTagParser::new(parser));
Self { parser }
}
}

impl From<Box<ParseTag>> for BoxedTagParser {
fn from(parser: Box<ParseTag>) -> BoxedTagParser {
let parser = TagParserEnum::Heap(parser);
Self { parser }
impl<T> From<T> for Box<ParseTag>
where
T: 'static + ParseTag,
{
fn from(filter: T) -> Self {
Box::new(filter)
}
}
53 changes: 25 additions & 28 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ pub struct ParserBuilder<P = Partials>
where
P: partials::PartialCompiler,
{
blocks: compiler::PluginRegistry<compiler::BoxedBlockParser>,
tags: compiler::PluginRegistry<compiler::BoxedTagParser>,
blocks: compiler::PluginRegistry<Box<compiler::ParseBlock>>,
tags: compiler::PluginRegistry<Box<compiler::ParseTag>>,
filters: compiler::PluginRegistry<Box<compiler::ParseFilter>>,
partials: Option<P>,
}
Expand All @@ -49,26 +49,26 @@ where

/// Register built-in Liquid tags
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)
self.tag(tags::AssignTag)
.tag(tags::BreakTag)
.tag(tags::ContinueTag)
.tag(tags::CycleTag)
.tag(tags::IncludeTag)
.tag(tags::IncrementTag)
.tag(tags::DecrementTag)
}

/// Register built-in Liquid blocks
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)
self.block(tags::RawBlock)
.block(tags::IfBlock)
.block(tags::UnlessBlock)
.block(tags::IfChangedBlock)
.block(tags::ForBlock)
.block(tags::TableRowBlock)
.block(tags::CommentBlock)
.block(tags::CaptureBlock)
.block(tags::CaseBlock)
}

/// Register built-in Liquid filters
Expand Down Expand Up @@ -153,26 +153,23 @@ where
}

/// Inserts a new custom block into the parser
pub fn block<B: Into<compiler::BoxedBlockParser>>(
mut self,
name: &'static str,
block: B,
) -> Self {
self.blocks.register(name, block.into());
pub fn block<B: Into<Box<compiler::ParseBlock>>>(mut self, block: B) -> Self {
let block = block.into();
self.blocks.register(block.start_tag(), block);
self
}

/// Inserts a new custom tag into the parser
pub fn tag<T: Into<compiler::BoxedTagParser>>(mut self, name: &'static str, tag: T) -> Self {
self.tags.register(name, tag.into());
pub fn tag<T: Into<Box<compiler::ParseTag>>>(mut self, tag: T) -> Self {
let tag = tag.into();
self.tags.register(tag.tag(), tag);
self
}

/// Inserts a new custom filter into the parser
pub fn filter<F: Into<Box<compiler::ParseFilter>>>(mut self, filter: F) -> Self {
let filter = filter.into();
self.filters
.register(compiler::FilterReflection::name(&*filter), filter);
self.filters.register(filter.name(), filter);
self
}

Expand Down
Loading

0 comments on commit 72f8cee

Please sign in to comment.