⬆️ Actualiza el código de maud a la versión 0.27.0
This commit is contained in:
parent
5ec1b0dc8f
commit
bceb43e6d0
11 changed files with 1459 additions and 1221 deletions
|
|
@ -1,23 +1,23 @@
|
|||
use proc_macro2::{Delimiter, Group, Ident, Literal, Span, TokenStream, TokenTree};
|
||||
use proc_macro_error::SpanRange;
|
||||
use quote::quote;
|
||||
use proc_macro2::{Ident, Span, TokenStream};
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::{parse_quote, token::Brace, Expr, Local};
|
||||
|
||||
use crate::maud::{ast::*, escape};
|
||||
|
||||
use proc_macro_crate::{crate_name, FoundCrate};
|
||||
|
||||
pub fn generate(markups: Vec<Markup>, output_ident: TokenTree) -> TokenStream {
|
||||
pub fn generate(markups: Markups<Element>, output_ident: Ident) -> TokenStream {
|
||||
let mut build = Builder::new(output_ident.clone());
|
||||
Generator::new(output_ident).markups(markups, &mut build);
|
||||
build.finish()
|
||||
}
|
||||
|
||||
struct Generator {
|
||||
output_ident: TokenTree,
|
||||
output_ident: Ident,
|
||||
}
|
||||
|
||||
impl Generator {
|
||||
fn new(output_ident: TokenTree) -> Generator {
|
||||
fn new(output_ident: Ident) -> Generator {
|
||||
Generator { output_ident }
|
||||
}
|
||||
|
||||
|
|
@ -25,257 +25,341 @@ impl Generator {
|
|||
Builder::new(self.output_ident.clone())
|
||||
}
|
||||
|
||||
fn markups(&self, markups: Vec<Markup>, build: &mut Builder) {
|
||||
for markup in markups {
|
||||
fn markups<E: Into<Element>>(&self, markups: Markups<E>, build: &mut Builder) {
|
||||
for markup in markups.markups {
|
||||
self.markup(markup, build);
|
||||
}
|
||||
}
|
||||
|
||||
fn markup(&self, markup: Markup, build: &mut Builder) {
|
||||
fn markup<E: Into<Element>>(&self, markup: Markup<E>, build: &mut Builder) {
|
||||
match markup {
|
||||
Markup::ParseError { .. } => {}
|
||||
Markup::Block(Block {
|
||||
markups,
|
||||
outer_span,
|
||||
}) => {
|
||||
if markups
|
||||
.iter()
|
||||
.any(|markup| matches!(*markup, Markup::Let { .. }))
|
||||
{
|
||||
self.block(
|
||||
Block {
|
||||
markups,
|
||||
outer_span,
|
||||
},
|
||||
build,
|
||||
);
|
||||
Markup::Block(block) => {
|
||||
if block.markups.markups.iter().any(|markup| {
|
||||
matches!(
|
||||
*markup,
|
||||
Markup::ControlFlow(ControlFlow {
|
||||
kind: ControlFlowKind::Let(_),
|
||||
..
|
||||
})
|
||||
)
|
||||
}) {
|
||||
self.block(block, build);
|
||||
} else {
|
||||
self.markups(markups, build);
|
||||
self.markups(block.markups, build);
|
||||
}
|
||||
}
|
||||
Markup::Literal { content, .. } => build.push_escaped(&content),
|
||||
Markup::Symbol { symbol } => self.name(symbol, build),
|
||||
Markup::Lit(lit) => build.push_escaped(&lit.to_string()),
|
||||
Markup::Splice { expr, .. } => self.splice(expr, build),
|
||||
Markup::Element { name, attrs, body } => self.element(name, attrs, body, build),
|
||||
Markup::Let { tokens, .. } => build.push_tokens(tokens),
|
||||
Markup::Special { segments } => {
|
||||
for Special { head, body, .. } in segments {
|
||||
build.push_tokens(head);
|
||||
self.block(body, build);
|
||||
}
|
||||
}
|
||||
Markup::Match {
|
||||
head,
|
||||
arms,
|
||||
arms_span,
|
||||
..
|
||||
} => {
|
||||
let body = {
|
||||
let mut build = self.builder();
|
||||
for MatchArm { head, body } in arms {
|
||||
build.push_tokens(head);
|
||||
self.block(body, &mut build);
|
||||
}
|
||||
build.finish()
|
||||
};
|
||||
let mut body = TokenTree::Group(Group::new(Delimiter::Brace, body));
|
||||
body.set_span(arms_span.collapse());
|
||||
build.push_tokens(quote!(#head #body));
|
||||
}
|
||||
Markup::Element(element) => self.element(element.into(), build),
|
||||
Markup::ControlFlow(control_flow) => self.control_flow(control_flow, build),
|
||||
Markup::Semi(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn block(
|
||||
&self,
|
||||
Block {
|
||||
markups,
|
||||
outer_span,
|
||||
}: Block,
|
||||
build: &mut Builder,
|
||||
) {
|
||||
let block = {
|
||||
fn block<E: Into<Element>>(&self, block: Block<E>, build: &mut Builder) {
|
||||
let markups = {
|
||||
let mut build = self.builder();
|
||||
self.markups(markups, &mut build);
|
||||
self.markups(block.markups, &mut build);
|
||||
build.finish()
|
||||
};
|
||||
let mut block = TokenTree::Group(Group::new(Delimiter::Brace, block));
|
||||
block.set_span(outer_span.collapse());
|
||||
build.push_tokens(TokenStream::from(block));
|
||||
|
||||
build.push_tokens(quote!({ #markups }));
|
||||
}
|
||||
|
||||
fn splice(&self, expr: TokenStream, build: &mut Builder) {
|
||||
let output_ident = self.output_ident.clone();
|
||||
fn splice(&self, expr: Expr, build: &mut Builder) {
|
||||
let output_ident = &self.output_ident;
|
||||
|
||||
let found_crate = crate_name("pagetop").expect("pagetop is present in `Cargo.toml`");
|
||||
build.push_tokens(match found_crate {
|
||||
FoundCrate::Itself => quote!(
|
||||
crate::html::html_private::render_to!(&#expr, &mut #output_ident);
|
||||
),
|
||||
_ => quote!(
|
||||
pagetop::html::html_private::render_to!(&#expr, &mut #output_ident);
|
||||
),
|
||||
let found_crate = crate_name("pagetop").expect("pagetop debe existir en Cargo.toml");
|
||||
let crate_ident = match found_crate {
|
||||
FoundCrate::Itself => Ident::new("pagetop", Span::call_site()),
|
||||
FoundCrate::Name(name) => Ident::new(&name, Span::call_site()),
|
||||
};
|
||||
build.push_tokens(quote! {
|
||||
#crate_ident::html::html_private::render_to!(&(#expr), &mut #output_ident);
|
||||
});
|
||||
}
|
||||
|
||||
fn element(&self, name: TokenStream, attrs: Vec<Attr>, body: ElementBody, build: &mut Builder) {
|
||||
fn element(&self, element: Element, build: &mut Builder) {
|
||||
let element_name = element.name.clone().unwrap_or_else(|| parse_quote!(div));
|
||||
build.push_str("<");
|
||||
self.name(name.clone(), build);
|
||||
self.attrs(attrs, build);
|
||||
self.name(element_name.clone(), build);
|
||||
self.attrs(element.attrs, build);
|
||||
build.push_str(">");
|
||||
if let ElementBody::Block { block } = body {
|
||||
if let ElementBody::Block(block) = element.body {
|
||||
self.markups(block.markups, build);
|
||||
build.push_str("</");
|
||||
self.name(name, build);
|
||||
self.name(element_name, build);
|
||||
build.push_str(">");
|
||||
}
|
||||
}
|
||||
|
||||
fn name(&self, name: TokenStream, build: &mut Builder) {
|
||||
build.push_escaped(&name_to_string(name));
|
||||
fn name(&self, name: HtmlName, build: &mut Builder) {
|
||||
build.push_escaped(&name.to_string());
|
||||
}
|
||||
|
||||
fn attrs(&self, attrs: Vec<Attr>, build: &mut Builder) {
|
||||
for NamedAttr { name, attr_type } in desugar_attrs(attrs) {
|
||||
match attr_type {
|
||||
AttrType::Normal { value } => {
|
||||
fn name_or_markup(&self, name: HtmlNameOrMarkup, build: &mut Builder) {
|
||||
match name {
|
||||
HtmlNameOrMarkup::HtmlName(name) => self.name(name, build),
|
||||
HtmlNameOrMarkup::Markup(markup) => self.markup(markup, build),
|
||||
}
|
||||
}
|
||||
|
||||
fn attr(&self, name: HtmlName, value: AttributeType, build: &mut Builder) {
|
||||
match value {
|
||||
AttributeType::Normal { value, .. } => {
|
||||
build.push_str(" ");
|
||||
self.name(name, build);
|
||||
build.push_str("=\"");
|
||||
self.markup(value, build);
|
||||
build.push_str("\"");
|
||||
}
|
||||
AttributeType::Optional {
|
||||
toggler: Toggler { cond, .. },
|
||||
..
|
||||
} => {
|
||||
let inner_value: Expr = parse_quote!(inner_value);
|
||||
|
||||
let body = {
|
||||
let mut build = self.builder();
|
||||
build.push_str(" ");
|
||||
self.name(name, build);
|
||||
self.name(name, &mut build);
|
||||
build.push_str("=\"");
|
||||
self.markup(value, build);
|
||||
self.splice(inner_value.clone(), &mut build);
|
||||
build.push_str("\"");
|
||||
}
|
||||
AttrType::Optional {
|
||||
toggler: Toggler { cond, .. },
|
||||
} => {
|
||||
let inner_value = quote!(inner_value);
|
||||
let body = {
|
||||
let mut build = self.builder();
|
||||
build.push_str(" ");
|
||||
self.name(name, &mut build);
|
||||
build.push_str("=\"");
|
||||
self.splice(inner_value.clone(), &mut build);
|
||||
build.push_str("\"");
|
||||
build.finish()
|
||||
};
|
||||
build.push_tokens(quote!(if let Some(#inner_value) = (#cond) { #body }));
|
||||
}
|
||||
AttrType::Empty { toggler: None } => {
|
||||
build.finish()
|
||||
};
|
||||
build.push_tokens(quote!(if let Some(#inner_value) = (#cond) { #body }));
|
||||
}
|
||||
AttributeType::Empty(None) => {
|
||||
build.push_str(" ");
|
||||
self.name(name, build);
|
||||
}
|
||||
AttributeType::Empty(Some(Toggler { cond, .. })) => {
|
||||
let body = {
|
||||
let mut build = self.builder();
|
||||
build.push_str(" ");
|
||||
self.name(name, build);
|
||||
}
|
||||
AttrType::Empty {
|
||||
toggler: Some(Toggler { cond, .. }),
|
||||
} => {
|
||||
let body = {
|
||||
let mut build = self.builder();
|
||||
build.push_str(" ");
|
||||
self.name(name, &mut build);
|
||||
build.finish()
|
||||
};
|
||||
build.push_tokens(quote!(if (#cond) { #body }));
|
||||
}
|
||||
self.name(name, &mut build);
|
||||
build.finish()
|
||||
};
|
||||
build.push_tokens(quote!(if (#cond) { #body }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn attrs(&self, attrs: Vec<Attribute>, build: &mut Builder) {
|
||||
let (classes, id, named_attrs) = split_attrs(attrs);
|
||||
|
||||
if !classes.is_empty() {
|
||||
let mut toggle_class_exprs = vec![];
|
||||
|
||||
build.push_str(" ");
|
||||
self.name(parse_quote!(class), build);
|
||||
build.push_str("=\"");
|
||||
for (i, (name, toggler)) in classes.into_iter().enumerate() {
|
||||
if let Some(toggler) = toggler {
|
||||
toggle_class_exprs.push((i > 0, name, toggler));
|
||||
} else {
|
||||
if i > 0 {
|
||||
build.push_str(" ");
|
||||
}
|
||||
self.name_or_markup(name, build);
|
||||
}
|
||||
}
|
||||
|
||||
for (not_first, name, toggler) in toggle_class_exprs {
|
||||
let body = {
|
||||
let mut build = self.builder();
|
||||
if not_first {
|
||||
build.push_str(" ");
|
||||
}
|
||||
self.name_or_markup(name, &mut build);
|
||||
build.finish()
|
||||
};
|
||||
build.push_tokens(quote!(if (#toggler) { #body }));
|
||||
}
|
||||
|
||||
build.push_str("\"");
|
||||
}
|
||||
|
||||
if let Some(id) = id {
|
||||
build.push_str(" ");
|
||||
self.name(parse_quote!(id), build);
|
||||
build.push_str("=\"");
|
||||
self.name_or_markup(id, build);
|
||||
build.push_str("\"");
|
||||
}
|
||||
|
||||
for (name, attr_type) in named_attrs {
|
||||
self.attr(name, attr_type, build);
|
||||
}
|
||||
}
|
||||
|
||||
fn control_flow<E: Into<Element>>(&self, control_flow: ControlFlow<E>, build: &mut Builder) {
|
||||
match control_flow.kind {
|
||||
ControlFlowKind::If(if_) => self.control_flow_if(if_, build),
|
||||
ControlFlowKind::Let(let_) => self.control_flow_let(let_, build),
|
||||
ControlFlowKind::For(for_) => self.control_flow_for(for_, build),
|
||||
ControlFlowKind::While(while_) => self.control_flow_while(while_, build),
|
||||
ControlFlowKind::Match(match_) => self.control_flow_match(match_, build),
|
||||
}
|
||||
}
|
||||
|
||||
fn control_flow_if<E: Into<Element>>(
|
||||
&self,
|
||||
IfExpr {
|
||||
if_token,
|
||||
cond,
|
||||
then_branch,
|
||||
else_branch,
|
||||
}: IfExpr<E>,
|
||||
build: &mut Builder,
|
||||
) {
|
||||
build.push_tokens(quote!(#if_token #cond));
|
||||
self.block(then_branch, build);
|
||||
|
||||
if let Some((_, else_token, if_or_block)) = else_branch {
|
||||
build.push_tokens(quote!(#else_token));
|
||||
self.control_flow_if_or_block(*if_or_block, build);
|
||||
}
|
||||
}
|
||||
|
||||
fn control_flow_if_or_block<E: Into<Element>>(
|
||||
&self,
|
||||
if_or_block: IfOrBlock<E>,
|
||||
build: &mut Builder,
|
||||
) {
|
||||
match if_or_block {
|
||||
IfOrBlock::If(if_) => self.control_flow_if(if_, build),
|
||||
IfOrBlock::Block(block) => self.block(block, build),
|
||||
}
|
||||
}
|
||||
|
||||
fn control_flow_let(&self, let_: Local, build: &mut Builder) {
|
||||
build.push_tokens(let_.to_token_stream());
|
||||
}
|
||||
|
||||
fn control_flow_for<E: Into<Element>>(
|
||||
&self,
|
||||
ForExpr {
|
||||
for_token,
|
||||
pat,
|
||||
in_token,
|
||||
expr,
|
||||
body,
|
||||
}: ForExpr<E>,
|
||||
build: &mut Builder,
|
||||
) {
|
||||
build.push_tokens(quote!(#for_token #pat #in_token (#expr)));
|
||||
self.block(body, build);
|
||||
}
|
||||
|
||||
fn control_flow_while<E: Into<Element>>(
|
||||
&self,
|
||||
WhileExpr {
|
||||
while_token,
|
||||
cond,
|
||||
body,
|
||||
}: WhileExpr<E>,
|
||||
build: &mut Builder,
|
||||
) {
|
||||
build.push_tokens(quote!(#while_token #cond));
|
||||
self.block(body, build);
|
||||
}
|
||||
|
||||
fn control_flow_match<E: Into<Element>>(
|
||||
&self,
|
||||
MatchExpr {
|
||||
match_token,
|
||||
expr,
|
||||
brace_token,
|
||||
arms,
|
||||
}: MatchExpr<E>,
|
||||
build: &mut Builder,
|
||||
) {
|
||||
let arms = {
|
||||
let mut build = self.builder();
|
||||
for MatchArm {
|
||||
pat,
|
||||
guard,
|
||||
fat_arrow_token,
|
||||
body,
|
||||
comma_token,
|
||||
} in arms
|
||||
{
|
||||
build.push_tokens(quote!(#pat));
|
||||
if let Some((if_token, cond)) = guard {
|
||||
build.push_tokens(quote!(#if_token #cond));
|
||||
}
|
||||
build.push_tokens(quote!(#fat_arrow_token));
|
||||
self.block(
|
||||
Block {
|
||||
brace_token: Brace(Span::call_site()),
|
||||
markups: Markups {
|
||||
markups: vec![body],
|
||||
},
|
||||
},
|
||||
&mut build,
|
||||
);
|
||||
build.push_tokens(quote!(#comma_token));
|
||||
}
|
||||
build.finish()
|
||||
};
|
||||
|
||||
let mut arm_block = TokenStream::new();
|
||||
|
||||
brace_token.surround(&mut arm_block, |tokens| {
|
||||
arms.to_tokens(tokens);
|
||||
});
|
||||
|
||||
build.push_tokens(quote!(#match_token #expr #arm_block));
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
|
||||
fn desugar_attrs(attrs: Vec<Attr>) -> Vec<NamedAttr> {
|
||||
let mut classes_static = vec![];
|
||||
let mut classes_toggled = vec![];
|
||||
let mut ids = vec![];
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn split_attrs(
|
||||
attrs: Vec<Attribute>,
|
||||
) -> (
|
||||
Vec<(HtmlNameOrMarkup, Option<Expr>)>,
|
||||
Option<HtmlNameOrMarkup>,
|
||||
Vec<(HtmlName, AttributeType)>,
|
||||
) {
|
||||
let mut classes = vec![];
|
||||
let mut id = None;
|
||||
let mut named_attrs = vec![];
|
||||
|
||||
for attr in attrs {
|
||||
match attr {
|
||||
Attr::Class {
|
||||
name,
|
||||
toggler: Some(toggler),
|
||||
..
|
||||
} => classes_toggled.push((name, toggler)),
|
||||
Attr::Class {
|
||||
name,
|
||||
toggler: None,
|
||||
..
|
||||
} => classes_static.push(name),
|
||||
Attr::Id { name, .. } => ids.push(name),
|
||||
Attr::Named { named_attr } => named_attrs.push(named_attr),
|
||||
Attribute::Class { name, toggler, .. } => {
|
||||
classes.push((name, toggler.map(|toggler| toggler.cond)))
|
||||
}
|
||||
Attribute::Id { name, .. } => id = Some(name),
|
||||
Attribute::Named { name, attr_type } => named_attrs.push((name, attr_type)),
|
||||
}
|
||||
}
|
||||
let classes = desugar_classes_or_ids("class", classes_static, classes_toggled);
|
||||
let ids = desugar_classes_or_ids("id", ids, vec![]);
|
||||
classes.into_iter().chain(ids).chain(named_attrs).collect()
|
||||
}
|
||||
|
||||
fn desugar_classes_or_ids(
|
||||
attr_name: &'static str,
|
||||
values_static: Vec<Markup>,
|
||||
values_toggled: Vec<(Markup, Toggler)>,
|
||||
) -> Option<NamedAttr> {
|
||||
if values_static.is_empty() && values_toggled.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let mut markups = Vec::new();
|
||||
let mut leading_space = false;
|
||||
for name in values_static {
|
||||
markups.extend(prepend_leading_space(name, &mut leading_space));
|
||||
}
|
||||
for (name, Toggler { cond, cond_span }) in values_toggled {
|
||||
let body = Block {
|
||||
markups: prepend_leading_space(name, &mut leading_space),
|
||||
// TODO: is this correct?
|
||||
outer_span: cond_span,
|
||||
};
|
||||
markups.push(Markup::Special {
|
||||
segments: vec![Special {
|
||||
at_span: SpanRange::call_site(),
|
||||
head: quote!(if (#cond)),
|
||||
body,
|
||||
}],
|
||||
});
|
||||
}
|
||||
Some(NamedAttr {
|
||||
name: TokenStream::from(TokenTree::Ident(Ident::new(attr_name, Span::call_site()))),
|
||||
attr_type: AttrType::Normal {
|
||||
value: Markup::Block(Block {
|
||||
markups,
|
||||
outer_span: SpanRange::call_site(),
|
||||
}),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn prepend_leading_space(name: Markup, leading_space: &mut bool) -> Vec<Markup> {
|
||||
let mut markups = Vec::new();
|
||||
if *leading_space {
|
||||
markups.push(Markup::Literal {
|
||||
content: " ".to_owned(),
|
||||
span: name.span(),
|
||||
});
|
||||
}
|
||||
*leading_space = true;
|
||||
markups.push(name);
|
||||
markups
|
||||
(classes, id, named_attrs)
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
|
||||
struct Builder {
|
||||
output_ident: TokenTree,
|
||||
tokens: Vec<TokenTree>,
|
||||
output_ident: Ident,
|
||||
tokens: TokenStream,
|
||||
tail: String,
|
||||
}
|
||||
|
||||
impl Builder {
|
||||
fn new(output_ident: TokenTree) -> Builder {
|
||||
fn new(output_ident: Ident) -> Builder {
|
||||
Builder {
|
||||
output_ident,
|
||||
tokens: Vec::new(),
|
||||
tokens: TokenStream::new(),
|
||||
tail: String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn push_str(&mut self, string: &str) {
|
||||
fn push_str(&mut self, string: &'static str) {
|
||||
self.tail.push_str(string);
|
||||
}
|
||||
|
||||
|
|
@ -294,8 +378,8 @@ impl Builder {
|
|||
}
|
||||
let push_str_expr = {
|
||||
let output_ident = self.output_ident.clone();
|
||||
let string = TokenTree::Literal(Literal::string(&self.tail));
|
||||
quote!(#output_ident.push_str(#string);)
|
||||
let tail = &self.tail;
|
||||
quote!(#output_ident.push_str(#tail);)
|
||||
};
|
||||
self.tail.clear();
|
||||
self.tokens.extend(push_str_expr);
|
||||
|
|
@ -303,6 +387,6 @@ impl Builder {
|
|||
|
||||
fn finish(mut self) -> TokenStream {
|
||||
self.cut();
|
||||
self.tokens.into_iter().collect()
|
||||
self.tokens
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue