Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Require braces around the body of an element #137

Merged
merged 6 commits into from
Jun 20, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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