2020-04-05 00:16:44 +00:00
|
|
|
use nom::branch::alt;
|
2020-04-07 00:20:53 +00:00
|
|
|
use nom::bytes::complete::escaped;
|
2020-04-07 01:09:06 +00:00
|
|
|
use nom::bytes::complete::escaped_transform;
|
2020-04-05 00:37:35 +00:00
|
|
|
use nom::bytes::complete::is_a;
|
2020-04-07 00:20:53 +00:00
|
|
|
use nom::bytes::complete::is_not;
|
2020-04-05 00:16:44 +00:00
|
|
|
use nom::bytes::complete::tag;
|
|
|
|
use nom::bytes::complete::take_until;
|
2020-04-05 02:45:56 +00:00
|
|
|
use nom::character::complete::one_of;
|
2020-04-07 02:47:24 +00:00
|
|
|
use nom::character::complete::space1;
|
2020-04-05 00:16:44 +00:00
|
|
|
use nom::combinator::map;
|
2020-04-07 02:47:24 +00:00
|
|
|
use nom::combinator::not;
|
2020-04-05 02:45:56 +00:00
|
|
|
use nom::combinator::opt;
|
2020-04-05 00:37:35 +00:00
|
|
|
use nom::combinator::recognize;
|
2020-04-05 01:30:56 +00:00
|
|
|
use nom::combinator::rest;
|
2020-04-05 00:16:44 +00:00
|
|
|
use nom::combinator::value;
|
2020-04-05 02:45:56 +00:00
|
|
|
use nom::combinator::verify;
|
2020-04-05 00:51:10 +00:00
|
|
|
use nom::multi::many0;
|
2020-04-05 03:42:27 +00:00
|
|
|
use nom::multi::many1;
|
2020-04-05 00:37:35 +00:00
|
|
|
use nom::multi::separated_list;
|
2020-04-05 00:16:44 +00:00
|
|
|
use nom::sequence::delimited;
|
2020-04-05 00:51:10 +00:00
|
|
|
use nom::sequence::preceded;
|
2020-04-07 03:21:03 +00:00
|
|
|
use nom::sequence::separated_pair;
|
2020-04-05 00:37:35 +00:00
|
|
|
use nom::sequence::tuple;
|
2020-04-05 00:16:44 +00:00
|
|
|
use nom::IResult;
|
|
|
|
|
2020-04-05 03:42:27 +00:00
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
2020-04-05 00:16:44 +00:00
|
|
|
enum DustTag<'a> {
|
|
|
|
DTSpecial(Special),
|
|
|
|
DTComment(Comment<'a>),
|
2020-04-05 00:37:35 +00:00
|
|
|
DTReference(Reference<'a>),
|
2020-04-05 21:05:22 +00:00
|
|
|
DTSection(Container<'a>),
|
|
|
|
DTExists(Container<'a>),
|
|
|
|
DTNotExists(Container<'a>),
|
2020-04-06 03:47:55 +00:00
|
|
|
DTBlock(NamedBlock<'a>),
|
|
|
|
DTInlinePartial(NamedBlock<'a>),
|
2020-04-07 02:02:10 +00:00
|
|
|
DTPartial(ParameterizedBlock<'a>),
|
2020-04-05 00:16:44 +00:00
|
|
|
}
|
|
|
|
|
2020-04-05 02:45:56 +00:00
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
2020-04-05 00:16:44 +00:00
|
|
|
enum Special {
|
|
|
|
Space,
|
|
|
|
NewLine,
|
|
|
|
CarriageReturn,
|
|
|
|
LeftCurlyBrace,
|
|
|
|
RightCurlyBrace,
|
|
|
|
}
|
|
|
|
|
2020-04-05 02:45:56 +00:00
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
2020-04-05 00:16:44 +00:00
|
|
|
struct Comment<'a> {
|
|
|
|
value: &'a str,
|
|
|
|
}
|
|
|
|
|
2020-04-05 02:45:56 +00:00
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
2020-04-05 00:37:35 +00:00
|
|
|
struct Path<'a> {
|
|
|
|
keys: Vec<&'a str>,
|
|
|
|
}
|
|
|
|
|
2020-04-05 02:45:56 +00:00
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
2020-04-05 00:37:35 +00:00
|
|
|
struct Reference<'a> {
|
|
|
|
path: Path<'a>,
|
2020-04-05 00:51:10 +00:00
|
|
|
filters: Vec<Filter>,
|
|
|
|
}
|
|
|
|
|
2020-04-05 02:45:56 +00:00
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
2020-04-05 00:51:10 +00:00
|
|
|
enum Filter {
|
|
|
|
HtmlEncode,
|
|
|
|
DisableHtmlEncode,
|
|
|
|
JavascriptStringEncode,
|
|
|
|
EncodeUri,
|
|
|
|
EncodeUriComponent,
|
|
|
|
JsonStringify,
|
|
|
|
JsonParse,
|
2020-04-05 00:37:35 +00:00
|
|
|
}
|
|
|
|
|
2020-04-05 02:45:56 +00:00
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
2020-04-05 01:30:56 +00:00
|
|
|
struct Span<'a> {
|
|
|
|
contents: &'a str,
|
|
|
|
}
|
|
|
|
|
2020-04-05 03:42:27 +00:00
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
2020-04-05 21:05:22 +00:00
|
|
|
struct Container<'a> {
|
2020-04-05 03:42:27 +00:00
|
|
|
path: Path<'a>,
|
2020-04-05 23:29:16 +00:00
|
|
|
contents: Option<Body<'a>>,
|
|
|
|
else_contents: Option<Body<'a>>,
|
2020-04-05 03:42:27 +00:00
|
|
|
}
|
|
|
|
|
2020-04-06 03:47:55 +00:00
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
|
|
struct NamedBlock<'a> {
|
|
|
|
name: &'a str,
|
|
|
|
contents: Option<Body<'a>>,
|
|
|
|
}
|
|
|
|
|
2020-04-07 02:02:10 +00:00
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
|
|
struct ParameterizedBlock<'a> {
|
|
|
|
name: String,
|
|
|
|
contents: Option<Body<'a>>,
|
2020-04-07 02:47:24 +00:00
|
|
|
params: Vec<&'a str>,
|
2020-04-07 02:02:10 +00:00
|
|
|
}
|
|
|
|
|
2020-04-05 03:42:27 +00:00
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
2020-04-05 23:29:16 +00:00
|
|
|
struct Body<'a> {
|
2020-04-05 01:30:56 +00:00
|
|
|
elements: Vec<TemplateElement<'a>>,
|
|
|
|
}
|
|
|
|
|
2020-04-05 02:52:14 +00:00
|
|
|
#[derive(Clone, Debug)]
|
2020-04-05 03:42:27 +00:00
|
|
|
pub struct Template<'a> {
|
2020-04-05 23:29:16 +00:00
|
|
|
contents: Body<'a>,
|
2020-04-05 03:42:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
2020-04-05 01:30:56 +00:00
|
|
|
enum TemplateElement<'a> {
|
|
|
|
TESpan(Span<'a>),
|
|
|
|
TETag(DustTag<'a>),
|
|
|
|
}
|
|
|
|
|
2020-04-07 03:30:42 +00:00
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
|
|
enum RValue<'a> {
|
|
|
|
RVPath(Path<'a>),
|
|
|
|
RVLiteral(&'a str),
|
|
|
|
}
|
|
|
|
|
2020-04-05 23:34:46 +00:00
|
|
|
/// Any element significant to dust that isn't plain text
|
|
|
|
///
|
|
|
|
/// These elements are always wrapped in curly braces
|
2020-04-05 00:16:44 +00:00
|
|
|
fn dust_tag(i: &str) -> IResult<&str, DustTag> {
|
|
|
|
alt((
|
|
|
|
map(special, DustTag::DTSpecial),
|
|
|
|
map(comment, DustTag::DTComment),
|
2020-04-05 00:37:35 +00:00
|
|
|
map(reference, DustTag::DTReference),
|
2020-04-05 23:39:07 +00:00
|
|
|
conditional("{#", DustTag::DTSection),
|
|
|
|
conditional("{?", DustTag::DTExists),
|
|
|
|
conditional("{^", DustTag::DTNotExists),
|
2020-04-06 03:47:55 +00:00
|
|
|
named_block("{+", DustTag::DTBlock),
|
|
|
|
named_block("{<", DustTag::DTInlinePartial),
|
2020-04-07 02:02:10 +00:00
|
|
|
parameterized_self_closing_block("{>", DustTag::DTPartial),
|
2020-04-05 00:16:44 +00:00
|
|
|
))(i)
|
|
|
|
}
|
|
|
|
|
2020-04-05 23:34:46 +00:00
|
|
|
/// Special characters
|
2020-04-05 00:16:44 +00:00
|
|
|
fn special(i: &str) -> IResult<&str, Special> {
|
|
|
|
delimited(
|
|
|
|
tag("{~"),
|
|
|
|
alt((
|
2020-04-05 02:45:56 +00:00
|
|
|
value(Special::LeftCurlyBrace, tag("lb")),
|
|
|
|
value(Special::RightCurlyBrace, tag("rb")),
|
2020-04-05 00:16:44 +00:00
|
|
|
value(Special::Space, tag("s")),
|
2020-04-05 02:45:56 +00:00
|
|
|
value(Special::NewLine, tag("n")),
|
|
|
|
value(Special::CarriageReturn, tag("r")),
|
2020-04-05 00:16:44 +00:00
|
|
|
)),
|
|
|
|
tag("}"),
|
|
|
|
)(i)
|
|
|
|
}
|
|
|
|
|
2020-04-05 23:34:46 +00:00
|
|
|
/// Part of a dust template that does not get rendered
|
2020-04-05 00:16:44 +00:00
|
|
|
fn comment(i: &str) -> IResult<&str, Comment> {
|
|
|
|
map(delimited(tag("{!"), take_until("!}"), tag("!}")), |body| {
|
|
|
|
Comment { value: body }
|
|
|
|
})(i)
|
|
|
|
}
|
2020-04-05 00:37:35 +00:00
|
|
|
|
2020-04-05 23:34:46 +00:00
|
|
|
/// A single element of a path
|
|
|
|
fn key(i: &str) -> IResult<&str, &str> {
|
|
|
|
recognize(tuple((
|
|
|
|
one_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_$"),
|
|
|
|
opt(is_a(
|
|
|
|
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_$0123456789-",
|
|
|
|
)),
|
|
|
|
)))(i)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A series of keys separated by '.' to reference a variable in the context
|
2020-04-05 00:37:35 +00:00
|
|
|
fn path(i: &str) -> IResult<&str, Path> {
|
2020-04-05 23:34:46 +00:00
|
|
|
map(separated_list(tag("."), key), |body| Path { keys: body })(i)
|
2020-04-05 00:37:35 +00:00
|
|
|
}
|
|
|
|
|
2020-04-07 03:21:03 +00:00
|
|
|
/// Either a literal or a path to a value
|
2020-04-07 03:30:42 +00:00
|
|
|
fn rvalue(i: &str) -> IResult<&str, RValue> {
|
|
|
|
map(path, RValue::RVPath)(i)
|
2020-04-07 03:21:03 +00:00
|
|
|
}
|
|
|
|
|
2020-04-07 03:30:42 +00:00
|
|
|
/// Parameters for a partial
|
|
|
|
fn key_value_pair(i: &str) -> IResult<&str, (&str, RValue)> {
|
|
|
|
separated_pair(key, tag("="), rvalue)(i)
|
2020-04-07 03:21:03 +00:00
|
|
|
}
|
|
|
|
|
2020-04-05 23:34:46 +00:00
|
|
|
/// Display a value from the context
|
2020-04-05 00:37:35 +00:00
|
|
|
fn reference(i: &str) -> IResult<&str, Reference> {
|
2020-04-05 00:51:10 +00:00
|
|
|
let (remaining, (p, filters)) = delimited(tag("{"), tuple((path, many0(filter))), tag("}"))(i)?;
|
|
|
|
Ok((
|
|
|
|
remaining,
|
|
|
|
Reference {
|
|
|
|
path: p,
|
|
|
|
filters: filters,
|
|
|
|
},
|
|
|
|
))
|
|
|
|
}
|
|
|
|
|
2020-04-05 23:39:07 +00:00
|
|
|
fn conditional<'a, F>(
|
2020-04-05 20:53:15 +00:00
|
|
|
open_matcher: &'static str,
|
2020-04-05 21:05:22 +00:00
|
|
|
constructor: F,
|
2020-04-05 20:53:15 +00:00
|
|
|
) -> impl Fn(&'a str) -> IResult<&'a str, DustTag<'a>>
|
2020-04-05 21:12:48 +00:00
|
|
|
where
|
|
|
|
F: Copy + Fn(Container<'a>) -> DustTag<'a>,
|
|
|
|
{
|
|
|
|
alt((
|
2020-04-05 23:39:07 +00:00
|
|
|
conditional_with_body(open_matcher, constructor),
|
|
|
|
self_closing_conditional(open_matcher, constructor),
|
2020-04-05 21:12:48 +00:00
|
|
|
))
|
|
|
|
}
|
|
|
|
|
2020-04-05 23:39:07 +00:00
|
|
|
fn conditional_with_body<'a, F>(
|
2020-04-05 21:12:48 +00:00
|
|
|
open_matcher: &'static str,
|
|
|
|
constructor: F,
|
|
|
|
) -> impl Fn(&'a str) -> IResult<&'a str, DustTag<'a>>
|
2020-04-05 20:53:15 +00:00
|
|
|
where
|
2020-04-05 21:05:22 +00:00
|
|
|
F: Fn(Container<'a>) -> DustTag<'a>,
|
2020-04-05 20:53:15 +00:00
|
|
|
{
|
|
|
|
move |i: &'a str| {
|
2020-04-05 21:24:13 +00:00
|
|
|
let (i, (opening_name, inner, maybe_else, _closing_name)) = verify(
|
2020-04-05 20:53:15 +00:00
|
|
|
tuple((
|
|
|
|
delimited(tag(open_matcher), path, tag("}")),
|
2020-04-05 23:29:16 +00:00
|
|
|
opt(body),
|
|
|
|
opt(preceded(tag("{:else}"), opt(body))),
|
2020-04-05 20:53:15 +00:00
|
|
|
delimited(tag("{/"), path, tag("}")),
|
|
|
|
)),
|
2020-04-05 21:24:13 +00:00
|
|
|
|(open, _inn, _maybe_else, close)| open == close,
|
2020-04-05 20:53:15 +00:00
|
|
|
)(i)?;
|
|
|
|
|
2020-04-05 21:05:22 +00:00
|
|
|
Ok((
|
|
|
|
i,
|
|
|
|
constructor(Container {
|
|
|
|
path: opening_name,
|
|
|
|
contents: inner,
|
2020-04-05 21:24:13 +00:00
|
|
|
else_contents: maybe_else.flatten(),
|
2020-04-05 21:05:22 +00:00
|
|
|
}),
|
|
|
|
))
|
2020-04-05 20:53:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-05 23:39:07 +00:00
|
|
|
fn self_closing_conditional<'a, F>(
|
2020-04-05 21:12:48 +00:00
|
|
|
open_matcher: &'static str,
|
|
|
|
constructor: F,
|
|
|
|
) -> impl Fn(&'a str) -> IResult<&'a str, DustTag<'a>>
|
|
|
|
where
|
|
|
|
F: Fn(Container<'a>) -> DustTag<'a>,
|
|
|
|
{
|
|
|
|
move |i: &'a str| {
|
|
|
|
let (i, path) = delimited(tag(open_matcher), path, tag("/}"))(i)?;
|
|
|
|
|
|
|
|
Ok((
|
|
|
|
i,
|
|
|
|
constructor(Container {
|
|
|
|
path: path,
|
|
|
|
contents: None,
|
2020-04-05 21:24:13 +00:00
|
|
|
else_contents: None,
|
2020-04-05 21:12:48 +00:00
|
|
|
}),
|
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-06 03:47:55 +00:00
|
|
|
fn named_block<'a, F>(
|
|
|
|
open_matcher: &'static str,
|
|
|
|
constructor: F,
|
|
|
|
) -> impl Fn(&'a str) -> IResult<&'a str, DustTag<'a>>
|
|
|
|
where
|
|
|
|
F: Copy + Fn(NamedBlock<'a>) -> DustTag<'a>,
|
|
|
|
{
|
|
|
|
alt((
|
|
|
|
named_block_with_body(open_matcher, constructor),
|
|
|
|
self_closing_named_block(open_matcher, constructor),
|
|
|
|
))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn named_block_with_body<'a, F>(
|
|
|
|
open_matcher: &'static str,
|
|
|
|
constructor: F,
|
|
|
|
) -> impl Fn(&'a str) -> IResult<&'a str, DustTag<'a>>
|
|
|
|
where
|
|
|
|
F: Fn(NamedBlock<'a>) -> DustTag<'a>,
|
|
|
|
{
|
|
|
|
move |i: &'a str| {
|
|
|
|
let (i, (opening_name, inner, _closing_name)) = verify(
|
|
|
|
tuple((
|
|
|
|
delimited(tag(open_matcher), key, tag("}")),
|
|
|
|
opt(body),
|
|
|
|
delimited(tag("{/"), key, tag("}")),
|
|
|
|
)),
|
|
|
|
|(open, _inn, close)| open == close,
|
|
|
|
)(i)?;
|
|
|
|
|
|
|
|
Ok((
|
|
|
|
i,
|
|
|
|
constructor(NamedBlock {
|
|
|
|
name: opening_name,
|
|
|
|
contents: inner,
|
|
|
|
}),
|
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn self_closing_named_block<'a, F>(
|
|
|
|
open_matcher: &'static str,
|
|
|
|
constructor: F,
|
|
|
|
) -> impl Fn(&'a str) -> IResult<&'a str, DustTag<'a>>
|
|
|
|
where
|
|
|
|
F: Fn(NamedBlock<'a>) -> DustTag<'a>,
|
|
|
|
{
|
|
|
|
move |i: &'a str| {
|
|
|
|
let (i, name) = delimited(tag(open_matcher), key, tag("/}"))(i)?;
|
|
|
|
|
|
|
|
Ok((
|
|
|
|
i,
|
|
|
|
constructor(NamedBlock {
|
|
|
|
name: name,
|
|
|
|
contents: None,
|
|
|
|
}),
|
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-07 02:02:10 +00:00
|
|
|
fn parameterized_self_closing_block<'a, F>(
|
|
|
|
open_matcher: &'static str,
|
|
|
|
constructor: F,
|
|
|
|
) -> impl Fn(&'a str) -> IResult<&'a str, DustTag<'a>>
|
|
|
|
where
|
|
|
|
F: Fn(ParameterizedBlock<'a>) -> DustTag<'a>,
|
|
|
|
{
|
|
|
|
move |i: &'a str| {
|
2020-04-07 02:47:24 +00:00
|
|
|
let (i, (name, params)) = delimited(
|
|
|
|
tag(open_matcher),
|
|
|
|
tuple((
|
|
|
|
key,
|
|
|
|
opt(preceded(space1, separated_list(space1, is_not(" ")))),
|
|
|
|
)),
|
|
|
|
tag("/}"),
|
|
|
|
)(i)?;
|
2020-04-07 02:02:10 +00:00
|
|
|
|
|
|
|
Ok((
|
|
|
|
i,
|
|
|
|
constructor(ParameterizedBlock {
|
|
|
|
name: name.to_owned(),
|
|
|
|
contents: None,
|
2020-04-07 02:47:24 +00:00
|
|
|
params: params.unwrap_or(Vec::new()),
|
2020-04-07 02:02:10 +00:00
|
|
|
}),
|
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-05 00:51:10 +00:00
|
|
|
fn filter(i: &str) -> IResult<&str, Filter> {
|
|
|
|
preceded(
|
|
|
|
tag("|"),
|
|
|
|
alt((
|
|
|
|
value(Filter::JsonStringify, tag("js")),
|
|
|
|
value(Filter::JsonParse, tag("jp")),
|
|
|
|
value(Filter::EncodeUriComponent, tag("uc")),
|
|
|
|
value(Filter::HtmlEncode, tag("h")),
|
|
|
|
value(Filter::DisableHtmlEncode, tag("s")),
|
|
|
|
value(Filter::JavascriptStringEncode, tag("j")),
|
|
|
|
value(Filter::EncodeUri, tag("u")),
|
|
|
|
)),
|
|
|
|
)(i)
|
2020-04-05 00:37:35 +00:00
|
|
|
}
|
2020-04-05 01:30:56 +00:00
|
|
|
|
|
|
|
/// Any text that is not a Dust element
|
|
|
|
fn span(i: &str) -> IResult<&str, Span> {
|
2020-04-05 02:45:56 +00:00
|
|
|
let (remaining, body) = verify(alt((take_until("{"), rest)), |s: &str| s.len() > 0)(i)?;
|
2020-04-05 01:30:56 +00:00
|
|
|
Ok((remaining, Span { contents: body }))
|
|
|
|
}
|
|
|
|
|
2020-04-05 23:29:16 +00:00
|
|
|
fn body(i: &str) -> IResult<&str, Body> {
|
2020-04-05 03:42:27 +00:00
|
|
|
let (remaining, template_elements) = many1(alt((
|
2020-04-05 01:30:56 +00:00
|
|
|
map(span, TemplateElement::TESpan),
|
|
|
|
map(dust_tag, TemplateElement::TETag),
|
|
|
|
)))(i)?;
|
|
|
|
Ok((
|
|
|
|
remaining,
|
2020-04-05 23:29:16 +00:00
|
|
|
Body {
|
2020-04-05 01:30:56 +00:00
|
|
|
elements: template_elements,
|
|
|
|
},
|
|
|
|
))
|
|
|
|
}
|
2020-04-05 02:45:56 +00:00
|
|
|
|
2020-04-05 03:42:27 +00:00
|
|
|
pub fn template(i: &str) -> IResult<&str, Template> {
|
2020-04-05 23:29:16 +00:00
|
|
|
let (remaining, contents) = body(i)?;
|
2020-04-05 03:42:27 +00:00
|
|
|
Ok((remaining, Template { contents: contents }))
|
|
|
|
}
|
|
|
|
|
2020-04-07 01:09:06 +00:00
|
|
|
fn quoted_string(i: &str) -> IResult<&str, String> {
|
2020-04-07 01:03:03 +00:00
|
|
|
delimited(
|
|
|
|
tag(r#"""#),
|
2020-04-07 01:09:06 +00:00
|
|
|
escaped_transform(is_not(r#"\""#), '\\', one_of(r#"\""#)),
|
2020-04-07 01:03:03 +00:00
|
|
|
tag(r#"""#),
|
|
|
|
)(i)
|
2020-04-07 00:20:53 +00:00
|
|
|
}
|
|
|
|
|
2020-04-05 02:45:56 +00:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
use nom::bytes::complete::is_a;
|
|
|
|
use nom::error::ErrorKind;
|
|
|
|
use nom::Err::Error;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_reference() {
|
|
|
|
assert_eq!(
|
|
|
|
super::reference("{foo.bar.baz|js|s}"),
|
|
|
|
Ok((
|
|
|
|
"",
|
|
|
|
Reference {
|
|
|
|
path: Path {
|
|
|
|
keys: vec!["foo", "bar", "baz"]
|
|
|
|
},
|
|
|
|
filters: vec![Filter::JsonStringify, Filter::DisableHtmlEncode],
|
|
|
|
}
|
|
|
|
))
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_path() {
|
|
|
|
assert_eq!(
|
|
|
|
is_a::<_, _, (_, ErrorKind)>("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_$")(
|
|
|
|
"foo"
|
|
|
|
),
|
|
|
|
Ok(("", "foo"))
|
|
|
|
);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
super::path("foo.bar.baz"),
|
|
|
|
Ok((
|
|
|
|
"",
|
|
|
|
Path {
|
|
|
|
keys: vec!["foo", "bar", "baz"]
|
|
|
|
}
|
|
|
|
))
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_special() {
|
|
|
|
assert_eq!(super::special("{~s}"), Ok(("", Special::Space)));
|
|
|
|
assert_eq!(super::special("{~n}"), Ok(("", Special::NewLine)));
|
|
|
|
assert_eq!(super::special("{~r}"), Ok(("", Special::CarriageReturn)));
|
|
|
|
assert_eq!(super::special("{~lb}"), Ok(("", Special::LeftCurlyBrace)));
|
|
|
|
assert_eq!(super::special("{~rb}"), Ok(("", Special::RightCurlyBrace)));
|
|
|
|
assert_eq!(
|
|
|
|
super::special("{~zzz}"),
|
|
|
|
Err(Error(("zzz}", ErrorKind::Tag)))
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_comment() {
|
|
|
|
assert_eq!(
|
|
|
|
super::comment("{! yo dawg} this is a comment !}"),
|
|
|
|
Ok((
|
|
|
|
"",
|
|
|
|
Comment {
|
|
|
|
value: " yo dawg} this is a comment "
|
|
|
|
}
|
|
|
|
))
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
super::special("{! this is a comment without a close"),
|
|
|
|
Err(Error((
|
|
|
|
"{! this is a comment without a close",
|
|
|
|
ErrorKind::Tag
|
|
|
|
)))
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_span() {
|
|
|
|
assert_eq!(
|
|
|
|
super::span("this is just some text"),
|
|
|
|
Ok((
|
|
|
|
"",
|
|
|
|
Span {
|
|
|
|
contents: "this is just some text"
|
|
|
|
}
|
|
|
|
))
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
super::span("this is just some text {~lb}"),
|
|
|
|
Ok((
|
|
|
|
"{~lb}",
|
|
|
|
Span {
|
|
|
|
contents: "this is just some text "
|
|
|
|
}
|
|
|
|
))
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
super::span("{~lb}"),
|
|
|
|
Err(Error(("{~lb}", ErrorKind::Verify)))
|
|
|
|
);
|
|
|
|
}
|
2020-04-05 03:42:27 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_section_mismatched_paths() {
|
|
|
|
assert_eq!(
|
2020-04-05 21:05:22 +00:00
|
|
|
super::dust_tag("{#foo.bar}{/baz}"),
|
|
|
|
Err(Error(("{#foo.bar}{/baz}", ErrorKind::Tag)))
|
2020-04-05 03:42:27 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_empty_section() {
|
|
|
|
assert_eq!(
|
2020-04-05 21:05:22 +00:00
|
|
|
super::dust_tag("{#foo.bar}{/foo.bar}"),
|
2020-04-05 03:42:27 +00:00
|
|
|
Ok((
|
|
|
|
"",
|
2020-04-05 21:05:22 +00:00
|
|
|
DustTag::DTSection(Container {
|
2020-04-05 21:12:48 +00:00
|
|
|
path: Path {
|
|
|
|
keys: vec!["foo", "bar"]
|
|
|
|
},
|
2020-04-05 21:24:13 +00:00
|
|
|
contents: None,
|
|
|
|
else_contents: None,
|
2020-04-05 21:12:48 +00:00
|
|
|
})
|
|
|
|
))
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_self_closing_section() {
|
|
|
|
assert_eq!(
|
|
|
|
super::dust_tag("{#foo.bar/}"),
|
|
|
|
Ok((
|
|
|
|
"",
|
|
|
|
DustTag::DTSection(Container {
|
2020-04-05 03:42:27 +00:00
|
|
|
path: Path {
|
|
|
|
keys: vec!["foo", "bar"]
|
|
|
|
},
|
2020-04-05 21:24:13 +00:00
|
|
|
contents: None,
|
|
|
|
else_contents: None,
|
2020-04-05 21:05:22 +00:00
|
|
|
})
|
2020-04-05 03:42:27 +00:00
|
|
|
))
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_section_with_body() {
|
|
|
|
assert_eq!(
|
2020-04-05 21:05:22 +00:00
|
|
|
super::dust_tag("{#foo.bar}hello {name}{/foo.bar}"),
|
2020-04-05 03:42:27 +00:00
|
|
|
Ok((
|
|
|
|
"",
|
2020-04-05 21:05:22 +00:00
|
|
|
DustTag::DTSection(Container {
|
2020-04-05 03:42:27 +00:00
|
|
|
path: Path {
|
|
|
|
keys: vec!["foo", "bar"]
|
|
|
|
},
|
2020-04-05 23:29:16 +00:00
|
|
|
contents: Some(Body {
|
2020-04-05 03:42:27 +00:00
|
|
|
elements: vec![
|
|
|
|
TemplateElement::TESpan(Span { contents: "hello " }),
|
|
|
|
TemplateElement::TETag(DustTag::DTReference(Reference {
|
|
|
|
path: Path { keys: vec!["name"] },
|
|
|
|
filters: Vec::new()
|
|
|
|
}))
|
|
|
|
]
|
2020-04-05 21:24:13 +00:00
|
|
|
}),
|
|
|
|
else_contents: None,
|
|
|
|
})
|
|
|
|
))
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_section_with_else_body() {
|
|
|
|
assert_eq!(
|
|
|
|
super::dust_tag("{#greeting}hello {name}{:else}goodbye {name}{/greeting}"),
|
|
|
|
Ok((
|
|
|
|
"",
|
|
|
|
DustTag::DTSection(Container {
|
|
|
|
path: Path {
|
|
|
|
keys: vec!["greeting"]
|
|
|
|
},
|
2020-04-05 23:29:16 +00:00
|
|
|
contents: Some(Body {
|
2020-04-05 21:24:13 +00:00
|
|
|
elements: vec![
|
|
|
|
TemplateElement::TESpan(Span { contents: "hello " }),
|
|
|
|
TemplateElement::TETag(DustTag::DTReference(Reference {
|
|
|
|
path: Path { keys: vec!["name"] },
|
|
|
|
filters: Vec::new()
|
|
|
|
}))
|
|
|
|
]
|
|
|
|
}),
|
2020-04-05 23:29:16 +00:00
|
|
|
else_contents: Some(Body {
|
2020-04-05 21:24:13 +00:00
|
|
|
elements: vec![
|
|
|
|
TemplateElement::TESpan(Span {
|
|
|
|
contents: "goodbye "
|
|
|
|
}),
|
|
|
|
TemplateElement::TETag(DustTag::DTReference(Reference {
|
|
|
|
path: Path { keys: vec!["name"] },
|
|
|
|
filters: Vec::new()
|
|
|
|
}))
|
|
|
|
]
|
|
|
|
}),
|
2020-04-05 21:05:22 +00:00
|
|
|
})
|
2020-04-05 03:42:27 +00:00
|
|
|
))
|
|
|
|
);
|
|
|
|
}
|
2020-04-06 03:47:55 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_self_closing_block() {
|
|
|
|
assert_eq!(
|
|
|
|
super::dust_tag("{+foo/}"),
|
|
|
|
Ok((
|
|
|
|
"",
|
|
|
|
DustTag::DTBlock(NamedBlock {
|
|
|
|
name: "foo",
|
|
|
|
contents: None
|
|
|
|
})
|
|
|
|
))
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_block() {
|
|
|
|
assert_eq!(
|
|
|
|
super::dust_tag("{+foo}hello {name}{/foo}"),
|
|
|
|
Ok((
|
|
|
|
"",
|
|
|
|
DustTag::DTBlock(NamedBlock {
|
|
|
|
name: "foo",
|
|
|
|
contents: Some(Body {
|
|
|
|
elements: vec![
|
|
|
|
TemplateElement::TESpan(Span { contents: "hello " }),
|
|
|
|
TemplateElement::TETag(DustTag::DTReference(Reference {
|
|
|
|
path: Path { keys: vec!["name"] },
|
|
|
|
filters: Vec::new()
|
|
|
|
}))
|
|
|
|
]
|
|
|
|
})
|
|
|
|
})
|
|
|
|
))
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_self_closing_inline_partial() {
|
|
|
|
assert_eq!(
|
|
|
|
super::dust_tag("{<foo/}"),
|
|
|
|
Ok((
|
|
|
|
"",
|
|
|
|
DustTag::DTInlinePartial(NamedBlock {
|
|
|
|
name: "foo",
|
|
|
|
contents: None
|
|
|
|
})
|
|
|
|
))
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_inline_partial() {
|
|
|
|
assert_eq!(
|
|
|
|
super::dust_tag("{<foo}hello {name}{/foo}"),
|
|
|
|
Ok((
|
|
|
|
"",
|
|
|
|
DustTag::DTInlinePartial(NamedBlock {
|
|
|
|
name: "foo",
|
|
|
|
contents: Some(Body {
|
|
|
|
elements: vec![
|
|
|
|
TemplateElement::TESpan(Span { contents: "hello " }),
|
|
|
|
TemplateElement::TETag(DustTag::DTReference(Reference {
|
|
|
|
path: Path { keys: vec!["name"] },
|
|
|
|
filters: Vec::new()
|
|
|
|
}))
|
|
|
|
]
|
|
|
|
})
|
|
|
|
})
|
|
|
|
))
|
|
|
|
);
|
|
|
|
}
|
2020-04-07 00:20:53 +00:00
|
|
|
|
|
|
|
#[test]
|
2020-04-07 01:26:08 +00:00
|
|
|
fn test_quoted_string() {
|
2020-04-07 01:09:06 +00:00
|
|
|
assert_eq!(
|
2020-04-07 01:26:08 +00:00
|
|
|
quoted_string(r#""foo\"bar\\baz""#),
|
|
|
|
Ok(("", r#"foo"bar\baz"#.to_owned()))
|
2020-04-07 01:09:06 +00:00
|
|
|
);
|
2020-04-07 00:20:53 +00:00
|
|
|
}
|
2020-04-07 02:02:10 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_self_closing_unquoted_partial() {
|
|
|
|
assert_eq!(
|
2020-04-07 02:47:24 +00:00
|
|
|
delimited(
|
|
|
|
tag("{>"),
|
2020-04-07 03:21:03 +00:00
|
|
|
tuple((
|
|
|
|
key,
|
|
|
|
opt(preceded(space1, separated_list(space1, key_value_pair))),
|
|
|
|
)),
|
2020-04-07 02:47:24 +00:00
|
|
|
tag("/}"),
|
2020-04-07 03:21:03 +00:00
|
|
|
)("{>foo bar=baz/}"),
|
2020-04-07 03:30:42 +00:00
|
|
|
Ok((
|
|
|
|
"",
|
|
|
|
(
|
|
|
|
"foo",
|
|
|
|
Some(vec![("bar", RValue::RVPath(Path { keys: vec!["baz"] }))])
|
|
|
|
)
|
|
|
|
))
|
2020-04-07 02:02:10 +00:00
|
|
|
);
|
|
|
|
}
|
2020-04-05 02:45:56 +00:00
|
|
|
}
|