Skip to content

Commit

Permalink
Merge pull request #137 from lfairy/snaggletooth
Browse files Browse the repository at this point in the history
Require braces around the body of an element
  • Loading branch information
lambda-fairy authored Jun 20, 2018
2 parents 8ce98f6 + 41aea5f commit 1f68ef0
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 54 deletions.
20 changes: 7 additions & 13 deletions maud/tests/basic_syntax.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,6 @@ fn simple_elements() {
assert_eq!(s, "<p><b>pickle</b>barrel<i>kumquat</i></p>");
}

#[test]
fn nesting_elements() {
let s = html!(html body div p sup "butts").into_string();
assert_eq!(s, "<html><body><div><p><sup>butts</sup></p></div></body></html>");
}

#[test]
fn empty_elements() {
let s = html!("pinkie" br; "pie").into_string();
Expand All @@ -73,7 +67,7 @@ fn simple_attributes() {
let s = html! {
link rel="stylesheet" href="styles.css";
section id="midriff" {
p class="hotpink" "Hello!"
p class="hotpink" { "Hello!" }
}
}.into_string();
assert_eq!(s, concat!(
Expand All @@ -83,7 +77,7 @@ fn simple_attributes() {

#[test]
fn empty_attributes() {
let s = html!(div readonly? input type="checkbox" checked?;).into_string();
let s = html!(div readonly? { input type="checkbox" checked?; }).into_string();
assert_eq!(s, r#"<div readonly><input type="checkbox" checked></div>"#);
}

Expand Down Expand Up @@ -112,7 +106,7 @@ fn toggle_empty_attributes_braces() {

#[test]
fn colons_in_names() {
let s = html!(pon-pon:controls-alpha a on:click="yay()" "Yay!").into_string();
let s = html!(pon-pon:controls-alpha { a on:click="yay()" { "Yay!" } }).into_string();
assert_eq!(s, concat!(
r#"<pon-pon:controls-alpha>"#,
r#"<a on:click="yay()">Yay!</a>"#,
Expand Down Expand Up @@ -151,14 +145,14 @@ fn classes_shorthand() {

#[test]
fn hyphens_in_class_names() {
let s = html!(p.rocks-these.are--my--rocks "yes").into_string();
let s = html!(p.rocks-these.are--my--rocks { "yes" }).into_string();
assert_eq!(s, r#"<p class="rocks-these are--my--rocks">yes</p>"#);
}

#[test]
fn toggle_classes() {
fn test(is_cupcake: bool, is_muffin: bool) -> Markup {
html!(p.cupcake[is_cupcake].muffin[is_muffin] "Testing!")
html!(p.cupcake[is_cupcake].muffin[is_muffin] { "Testing!" })
}
assert_eq!(test(true, true).into_string(), r#"<p class="cupcake muffin">Testing!</p>"#);
assert_eq!(test(false, true).into_string(), r#"<p class=" muffin">Testing!</p>"#);
Expand All @@ -169,14 +163,14 @@ fn toggle_classes() {
#[test]
fn toggle_classes_braces() {
struct Maud { rocks: bool }
let s = html!(p.rocks[Maud { rocks: true }.rocks] "Awesome!").into_string();
let s = html!(p.rocks[Maud { rocks: true }.rocks] { "Awesome!" }).into_string();
assert_eq!(s, r#"<p class="rocks">Awesome!</p>"#);
}

#[test]
fn mixed_classes() {
fn test(is_muffin: bool) -> Markup {
html!(p.cupcake.muffin[is_muffin].lamington "Testing!")
html!(p.cupcake.muffin[is_muffin].lamington { "Testing!" })
}
assert_eq!(test(true).into_string(), r#"<p class="cupcake lamington muffin">Testing!</p>"#);
assert_eq!(test(false).into_string(), r#"<p class="cupcake lamington">Testing!</p>"#);
Expand Down
22 changes: 14 additions & 8 deletions maud/tests/control_structures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,10 @@ fn if_let() {
fn while_expr() {
let mut numbers = (0..3).into_iter().peekable();
let s = html! {
ul @while numbers.peek().is_some() {
li (numbers.next().unwrap())
ul {
@while numbers.peek().is_some() {
li { (numbers.next().unwrap()) }
}
}
}.into_string();
assert_eq!(s, "<ul><li>0</li><li>1</li><li>2</li></ul>");
Expand All @@ -56,8 +58,10 @@ fn while_let_expr() {
let mut numbers = (0..3).into_iter();
#[cfg_attr(feature = "cargo-clippy", allow(while_let_on_iterator))]
let s = html! {
ul @while let Some(n) = numbers.next() {
li (n)
ul {
@while let Some(n) = numbers.next() {
li { (n) }
}
}
}.into_string();
assert_eq!(s, "<ul><li>0</li><li>1</li><li>2</li></ul>");
Expand All @@ -67,8 +71,10 @@ fn while_let_expr() {
fn for_expr() {
let ponies = ["Apple Bloom", "Scootaloo", "Sweetie Belle"];
let s = html! {
ul @for pony in &ponies {
li (pony)
ul {
@for pony in &ponies {
li { (pony) }
}
}
}.into_string();
assert_eq!(s, concat!(
Expand All @@ -85,7 +91,7 @@ fn match_expr() {
let s = html! {
@match input {
Some(value) => {
div (value)
div { (value) }
},
None => {
"oh noes"
Expand All @@ -102,7 +108,7 @@ fn match_expr_without_delims() {
let s = html! {
@match input {
Some(value) => (value),
None => span "oh noes",
None => span { "oh noes" },
}
}.into_string();
assert_eq!(s, output);
Expand Down
2 changes: 1 addition & 1 deletion maud_extras/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ pub struct Title<T: AsRef<str>>(pub T);
impl<T: AsRef<str>> Render for Title<T> {
fn render(&self) -> Markup {
html! {
title (self.0.as_ref())
title { (self.0.as_ref()) }
}
}
}
Expand Down
78 changes: 75 additions & 3 deletions maud_macros/src/ast.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use proc_macro::{Span, TokenStream};
use proc_macro::{Span, TokenStream, TokenTree};

#[derive(Debug)]
pub enum Markup {
Expand All @@ -12,13 +12,15 @@ pub enum Markup {
},
Splice {
expr: TokenStream,
outer_span: Span,
},
Element {
name: TokenStream,
attrs: Attrs,
body: Option<Box<Markup>>,
body: ElementBody,
},
Let {
at_span: Span,
tokens: TokenStream,
},
Special {
Expand All @@ -32,6 +34,30 @@ pub enum Markup {
},
}

impl Markup {
pub fn span(&self) -> Span {
match *self {
Markup::Block(ref block) => block.span(),
Markup::Literal { span, .. } => span,
Markup::Symbol { ref symbol } => span_tokens(symbol.clone()),
Markup::Splice { outer_span, .. } => outer_span,
Markup::Element { ref name, ref body, .. } => {
let name_span = span_tokens(name.clone());
name_span.join(body.span()).unwrap_or(name_span)
},
Markup::Let { at_span, ref tokens } => {
at_span.join(span_tokens(tokens.clone())).unwrap_or(at_span)
},
Markup::Special { ref segments } => {
join_spans(segments.iter().map(|segment| segment.span()))
},
Markup::Match { at_span, arms_span, .. } => {
at_span.join(arms_span).unwrap_or(at_span)
},
}
}
}

#[derive(Debug)]
pub struct Attrs {
pub classes_static: Vec<ClassOrId>,
Expand All @@ -42,10 +68,31 @@ pub struct Attrs {

pub type ClassOrId = TokenStream;

#[derive(Debug)]
pub enum ElementBody {
Void { semi_span: Span },
Block { block: Block },
}

impl ElementBody {
pub fn span(&self) -> Span {
match *self {
ElementBody::Void { semi_span } => semi_span,
ElementBody::Block { ref block } => block.span(),
}
}
}

#[derive(Debug)]
pub struct Block {
pub markups: Vec<Markup>,
pub span: Span,
pub outer_span: Span,
}

impl Block {
pub fn span(&self) -> Span {
self.outer_span
}
}

#[derive(Debug)]
Expand All @@ -55,6 +102,13 @@ pub struct Special {
pub body: Block,
}

impl Special {
pub fn span(&self) -> Span {
let body_span = self.body.span();
self.at_span.join(body_span).unwrap_or(self.at_span)
}
}

#[derive(Debug)]
pub struct Attribute {
pub name: TokenStream,
Expand Down Expand Up @@ -82,3 +136,21 @@ pub struct MatchArm {
pub head: TokenStream,
pub body: Block,
}

pub fn span_tokens<I: IntoIterator<Item=TokenTree>>(tokens: I) -> Span {
join_spans(tokens.into_iter().map(|token| token.span()))
}

pub fn join_spans<I: IntoIterator<Item=Span>>(spans: I) -> Span {
let mut iter = spans.into_iter();
let mut span = match iter.next() {
Some(span) => span,
None => return Span::call_site(),
};
for new_span in iter {
if let Some(joined) = span.join(new_span) {
span = joined;
}
}
span
}
32 changes: 11 additions & 21 deletions maud_macros/src/generate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,18 @@ impl Generator {

fn markup(&self, markup: Markup, build: &mut Builder) {
match markup {
Markup::Block(Block { markups, span }) => {
Markup::Block(Block { markups, outer_span }) => {
if markups.iter().any(|markup| matches!(*markup, Markup::Let { .. })) {
build.push_tokens(self.block(Block { markups, span }));
build.push_tokens(self.block(Block { markups, outer_span }));
} else {
self.markups(markups, build);
}
},
Markup::Literal { content, .. } => build.push_escaped(&content),
Markup::Symbol { symbol } => self.name(symbol, build),
Markup::Splice { expr } => build.push_tokens(self.splice(expr)),
Markup::Splice { expr, .. } => build.push_tokens(self.splice(expr)),
Markup::Element { name, attrs, body } => self.element(name, attrs, body, build),
Markup::Let { tokens } => build.push_tokens(tokens),
Markup::Let { tokens, .. } => build.push_tokens(tokens),
Markup::Special { segments } => {
for segment in segments {
build.push_tokens(self.special(segment));
Expand All @@ -70,11 +70,11 @@ impl Generator {
}
}

fn block(&self, Block { markups, span }: Block) -> TokenStream {
fn block(&self, Block { markups, outer_span }: Block) -> TokenStream {
let mut build = self.builder();
self.markups(markups, &mut build);
let mut block = TokenTree::Group(Group::new(Delimiter::Brace, build.finish()));
block.set_span(span);
block.set_span(outer_span);
TokenStream::from(block)
}

Expand All @@ -96,15 +96,15 @@ impl Generator {
&self,
name: TokenStream,
attrs: Attrs,
body: Option<Box<Markup>>,
body: ElementBody,
build: &mut Builder,
) {
build.push_str("<");
self.name(name.clone(), build);
self.attrs(attrs, build);
build.push_str(">");
if let Some(body) = body {
self.markup(*body, build);
if let ElementBody::Block { block } = body {
self.markups(block.markups, build);
build.push_str("</");
self.name(name, build);
build.push_str(">");
Expand Down Expand Up @@ -179,7 +179,7 @@ fn desugar_classes_or_ids(
for (symbol, toggler) in values_toggled {
let body = Block {
markups: prepend_leading_space(symbol, &mut leading_space),
span: toggler.cond_span,
outer_span: toggler.cond_span,
};
let head = desugar_toggler(toggler);
markups.push(Markup::Special {
Expand All @@ -191,7 +191,7 @@ fn desugar_classes_or_ids(
attr_type: AttrType::Normal {
value: Markup::Block(Block {
markups,
span: Span::call_site(),
outer_span: Span::call_site(),
}),
},
})
Expand Down Expand Up @@ -224,16 +224,6 @@ fn desugar_toggler(Toggler { mut cond, cond_span }: Toggler) -> TokenStream {
quote!(if $cond)
}

fn span_tokens<I: IntoIterator<Item=TokenTree>>(tokens: I) -> Span {
tokens
.into_iter()
.fold(None, |span: Option<Span>, token| Some(match span {
None => token.span(),
Some(span) => span.join(token.span()).unwrap_or(span),
}))
.unwrap_or_else(Span::def_site)
}

////////////////////////////////////////////////////////

struct Builder {
Expand Down
Loading

0 comments on commit 1f68ef0

Please sign in to comment.