diff --git a/Cargo.lock b/Cargo.lock index cf974c8..43eb210 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -918,13 +918,12 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "pagetop" -version = "0.0.4" +version = "0.0.3" dependencies = [ "actix-web", "colored", "config", "figlet-rs", - "itoa", "pagetop-macros", "serde", "substring", @@ -937,13 +936,9 @@ dependencies = [ [[package]] name = "pagetop-macros" -version = "0.0.2" +version = "0.0.1" dependencies = [ - "proc-macro-crate", - "proc-macro2", - "proc-macro2-diagnostics", "quote", - "syn", ] [[package]] @@ -1043,15 +1038,6 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "proc-macro-crate" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" -dependencies = [ - "toml_edit", -] - [[package]] name = "proc-macro2" version = "1.0.95" @@ -1061,18 +1047,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "proc-macro2-diagnostics" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "version_check", -] - [[package]] name = "quote" version = "1.0.40" diff --git a/Cargo.toml b/Cargo.toml index b8b3f5f..f5948d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pagetop" -version = "0.0.4" +version = "0.0.3" edition = "2021" description = """\ @@ -18,7 +18,6 @@ authors.workspace = true colored = "3.0.0" config = { version = "0.15.11", default-features = false, features = ["toml"] } figlet-rs = "0.1.5" -itoa = "1.0.15" serde.workspace = true substring = "1.4.5" terminal_size = "0.4.2" diff --git a/helpers/pagetop-macros/Cargo.toml b/helpers/pagetop-macros/Cargo.toml index ea6404b..7a4d30e 100644 --- a/helpers/pagetop-macros/Cargo.toml +++ b/helpers/pagetop-macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pagetop-macros" -version = "0.0.2" +version = "0.0.1" edition = "2021" description = """\ @@ -18,8 +18,4 @@ authors.workspace = true proc-macro = true [dependencies] -proc-macro2 = "1.0.95" -proc-macro2-diagnostics = { version = "0.10.1", default-features = false } -proc-macro-crate = "3.3.0" quote = "1.0.40" -syn = { version = "2.0.104", features = ["full"] } diff --git a/helpers/pagetop-macros/README.md b/helpers/pagetop-macros/README.md index 0679966..8bdc94c 100644 --- a/helpers/pagetop-macros/README.md +++ b/helpers/pagetop-macros/README.md @@ -8,13 +8,6 @@ -## Descripción general - -Entre sus macros se incluye una adaptación de [maud-macros](https://crates.io/crates/maud_macros) -([0.25.0](https://github.com/lambda-fairy/maud/tree/v0.25.0/maud_macros)) de -[Chris Wong](https://crates.io/users/lambda-fairy) para no tener que referenciar `maud` en las -dependencias del archivo `Cargo.toml` de cada proyecto `PageTop`. - ## Sobre PageTop [PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la web diff --git a/helpers/pagetop-macros/src/lib.rs b/helpers/pagetop-macros/src/lib.rs index e0f3bad..31d8dcd 100644 --- a/helpers/pagetop-macros/src/lib.rs +++ b/helpers/pagetop-macros/src/lib.rs @@ -14,17 +14,9 @@ //! web clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles //! y configurables, basadas en HTML, CSS y JavaScript. -mod maud; - use proc_macro::TokenStream; use quote::quote; -/// Macro para escribir plantillas HTML ([Maud](https://docs.rs/maud)). -#[proc_macro] -pub fn html(input: TokenStream) -> TokenStream { - maud::expand(input.into()).into() -} - /// Define una función `main` asíncrona como punto de entrada de `PageTop`. /// /// # Ejemplos diff --git a/helpers/pagetop-macros/src/maud.rs b/helpers/pagetop-macros/src/maud.rs deleted file mode 100644 index 9077dbb..0000000 --- a/helpers/pagetop-macros/src/maud.rs +++ /dev/null @@ -1,60 +0,0 @@ -// #![doc(html_root_url = "https://docs.rs/maud_macros/0.27.0")] -// TokenStream values are reference counted, and the mental overhead of tracking -// lifetimes outweighs the marginal gains from explicit borrowing -// #![allow(clippy::needless_pass_by_value)] - -mod ast; -mod escape; -mod generate; - -use ast::DiagnosticParse; -use proc_macro2::{Ident, Span, TokenStream}; -use proc_macro2_diagnostics::Diagnostic; -use proc_macro_crate::{crate_name, FoundCrate}; -use quote::quote; -use syn::parse::{ParseStream, Parser}; - -pub fn expand(input: TokenStream) -> TokenStream { - // Heuristic: the size of the resulting markup tends to correlate with the - // code size of the template itself - let size_hint = input.to_string().len(); - - let mut diagnostics = Vec::new(); - let markups = match Parser::parse2( - |input: ParseStream| ast::Markups::diagnostic_parse(input, &mut diagnostics), - input, - ) { - Ok(data) => data, - Err(err) => { - let err = err.to_compile_error(); - let diag_tokens = diagnostics.into_iter().map(Diagnostic::emit_as_expr_tokens); - - return quote! {{ - #err - #(#diag_tokens)* - }}; - } - }; - - let diag_tokens = diagnostics.into_iter().map(Diagnostic::emit_as_expr_tokens); - - let output_ident = Ident::new("__maud_output", Span::mixed_site()); - let stmts = generate::generate(markups, output_ident.clone()); - - let found_crate = crate_name("pagetop").expect("pagetop must be in Cargo.toml"); - let crate_ident = match found_crate { - FoundCrate::Itself => Ident::new("pagetop", Span::call_site()), - FoundCrate::Name(ref name) => Ident::new(name, Span::call_site()), - }; - let pre_escaped = quote! { - #crate_ident::html::PreEscaped(#output_ident) - }; - - quote! {{ - extern crate alloc; - let mut #output_ident = alloc::string::String::with_capacity(#size_hint); - #stmts - #(#diag_tokens)* - #pre_escaped - }} -} diff --git a/helpers/pagetop-macros/src/maud/ast.rs b/helpers/pagetop-macros/src/maud/ast.rs deleted file mode 100644 index fd499ae..0000000 --- a/helpers/pagetop-macros/src/maud/ast.rs +++ /dev/null @@ -1,1105 +0,0 @@ -use std::fmt::{self, Display, Formatter}; - -use proc_macro2::TokenStream; -use proc_macro2_diagnostics::{Diagnostic, SpanDiagnosticExt}; -use quote::ToTokens; -use syn::{ - braced, bracketed, - ext::IdentExt, - parenthesized, - parse::{Lookahead1, Parse, ParseStream}, - punctuated::{Pair, Punctuated}, - spanned::Spanned, - token::{ - At, Brace, Bracket, Colon, Comma, Dot, Else, Eq, FatArrow, For, If, In, Let, Match, Minus, - Paren, Pound, Question, Semi, Slash, While, - }, - Error, Expr, Ident, Lit, LitBool, LitInt, LitStr, Local, Pat, Stmt, -}; - -#[derive(Debug, Clone)] -pub struct Markups { - pub markups: Vec>, -} - -impl DiagnosticParse for Markups { - fn diagnostic_parse( - input: ParseStream, - diagnostics: &mut Vec, - ) -> syn::Result { - let mut markups = Vec::new(); - while !input.is_empty() { - markups.push(Markup::diagnostic_parse_in_block(input, diagnostics)?) - } - Ok(Self { markups }) - } -} - -impl ToTokens for Markups { - fn to_tokens(&self, tokens: &mut TokenStream) { - for markup in &self.markups { - markup.to_tokens(tokens); - } - } -} - -#[derive(Debug, Clone)] -pub enum Markup { - Block(Block), - Lit(HtmlLit), - Splice { paren_token: Paren, expr: Expr }, - Element(E), - ControlFlow(ControlFlow), - Semi(Semi), -} - -impl Markup { - pub fn diagnostic_parse_in_block( - input: ParseStream, - diagnostics: &mut Vec, - ) -> syn::Result { - if input.peek(Let) - || input.peek(If) - || input.peek(Else) - || input.peek(For) - || input.peek(While) - || input.peek(Match) - { - let kw = input.call(Ident::parse_any)?; - diagnostics.push( - kw.span() - .error(format!("found keyword `{kw}`")) - .help(format!("should this be `@{kw}`?")), - ); - } - - let lookahead = input.lookahead1(); - - if lookahead.peek(Brace) { - input.diagnostic_parse(diagnostics).map(Self::Block) - } else if lookahead.peek(Lit) { - input.diagnostic_parse(diagnostics).map(Self::Lit) - } else if lookahead.peek(Paren) { - let content; - Ok(Self::Splice { - paren_token: parenthesized!(content in input), - expr: content.parse()?, - }) - } else if let Some(parse_element) = E::should_parse(&lookahead) { - parse_element(input, diagnostics).map(Self::Element) - } else if lookahead.peek(At) { - input.diagnostic_parse(diagnostics).map(Self::ControlFlow) - } else if lookahead.peek(Semi) { - input.parse().map(Self::Semi) - } else { - Err(lookahead.error()) - } - } -} - -impl DiagnosticParse for Markup { - fn diagnostic_parse( - input: ParseStream, - diagnostics: &mut Vec, - ) -> syn::Result { - let markup = Self::diagnostic_parse_in_block(input, diagnostics)?; - - if let Self::ControlFlow(ControlFlow { - kind: ControlFlowKind::Let(_), - .. - }) = &markup - { - diagnostics.push( - markup - .span() - .error("`@let` bindings are only allowed inside blocks"), - ) - } - - Ok(markup) - } -} - -impl ToTokens for Markup { - fn to_tokens(&self, tokens: &mut TokenStream) { - match self { - Self::Block(block) => block.to_tokens(tokens), - Self::Lit(lit) => lit.to_tokens(tokens), - Self::Splice { paren_token, expr } => { - paren_token.surround(tokens, |tokens| { - expr.to_tokens(tokens); - }); - } - Self::Element(element) => element.to_tokens(tokens), - Self::ControlFlow(control_flow) => control_flow.to_tokens(tokens), - Self::Semi(semi) => semi.to_tokens(tokens), - } - } -} - -/// Represents a context that may or may not allow elements. -/// -/// An attribute accepts almost the same syntax as an element body, except child elements aren't -/// allowed. To enable code reuse, introduce a trait that abstracts over whether an element is -/// allowed or not. -pub trait MaybeElement: Sized + ToTokens { - /// If an element can be parsed here, returns `Some` with a parser for the rest of the element. - fn should_parse(lookahead: &Lookahead1<'_>) -> Option>; -} - -/// An implementation of `DiagnosticParse::diagnostic_parse`. -pub type DiagnosticParseFn = fn(ParseStream, &mut Vec) -> syn::Result; - -/// Represents an attribute context, where elements are disallowed. -#[derive(Debug, Clone)] -pub enum NoElement {} - -impl MaybeElement for NoElement { - fn should_parse( - _lookahead: &Lookahead1<'_>, - ) -> Option) -> syn::Result> { - None - } -} - -impl ToTokens for NoElement { - fn to_tokens(&self, _tokens: &mut TokenStream) { - match *self {} - } -} - -#[derive(Debug, Clone)] -pub struct Element { - pub name: Option, - pub attrs: Vec, - pub body: ElementBody, -} - -impl From for Element { - fn from(value: NoElement) -> Self { - match value {} - } -} - -impl MaybeElement for Element { - fn should_parse( - lookahead: &Lookahead1<'_>, - ) -> Option) -> syn::Result> { - if lookahead.peek(Ident::peek_any) || lookahead.peek(Dot) || lookahead.peek(Pound) { - Some(Element::diagnostic_parse) - } else { - None - } - } -} - -impl DiagnosticParse for Element { - fn diagnostic_parse( - input: ParseStream, - diagnostics: &mut Vec, - ) -> syn::Result { - Ok(Self { - name: if input.peek(Ident::peek_any) { - Some(input.diagnostic_parse(diagnostics)?) - } else { - None - }, - attrs: { - let mut id_pushed = false; - let mut attrs = Vec::new(); - - while input.peek(Ident::peek_any) - || input.peek(Lit) - || input.peek(Dot) - || input.peek(Pound) - { - let attr = input.diagnostic_parse(diagnostics)?; - - if let Attribute::Id { .. } = attr { - if id_pushed { - return Err(Error::new_spanned( - attr, - "duplicate id (`#`) attribute specified", - )); - } - id_pushed = true; - } - - attrs.push(attr); - } - - if !(input.peek(Brace) || input.peek(Semi) || input.peek(Slash)) { - let lookahead = input.lookahead1(); - - lookahead.peek(Ident::peek_any); - lookahead.peek(Lit); - lookahead.peek(Dot); - lookahead.peek(Pound); - - lookahead.peek(Brace); - lookahead.peek(Semi); - - return Err(lookahead.error()); - } - - attrs - }, - body: input.diagnostic_parse(diagnostics)?, - }) - } -} - -impl ToTokens for Element { - fn to_tokens(&self, tokens: &mut TokenStream) { - if let Some(name) = &self.name { - name.to_tokens(tokens); - } - for attr in &self.attrs { - attr.to_tokens(tokens); - } - self.body.to_tokens(tokens); - } -} - -#[derive(Debug, Clone)] -pub enum ElementBody { - Void(Semi), - Block(Block), -} - -impl DiagnosticParse for ElementBody { - fn diagnostic_parse( - input: ParseStream, - diagnostics: &mut Vec, - ) -> syn::Result { - let lookahead = input.lookahead1(); - - if lookahead.peek(Semi) { - input.parse().map(Self::Void) - } else if lookahead.peek(Brace) { - input.diagnostic_parse(diagnostics).map(Self::Block) - } else if lookahead.peek(Slash) { - diagnostics.push( - input - .parse::()? - .span() - .error("void elements must use `;`, not `/`") - .help("change this to `;`") - .help("see https://github.com/lambda-fairy/maud/pull/315 for details"), - ); - - Ok(Self::Void(::default())) - } else { - Err(lookahead.error()) - } - } -} - -impl ToTokens for ElementBody { - fn to_tokens(&self, tokens: &mut TokenStream) { - match self { - Self::Void(semi) => semi.to_tokens(tokens), - Self::Block(block) => block.to_tokens(tokens), - } - } -} - -#[derive(Debug, Clone)] -pub struct Block { - pub brace_token: Brace, - pub markups: Markups, -} - -impl DiagnosticParse for Block { - fn diagnostic_parse( - input: ParseStream, - diagnostics: &mut Vec, - ) -> syn::Result { - let content; - Ok(Self { - brace_token: braced!(content in input), - markups: content.diagnostic_parse(diagnostics)?, - }) - } -} - -impl ToTokens for Block { - fn to_tokens(&self, tokens: &mut TokenStream) { - self.brace_token.surround(tokens, |tokens| { - self.markups.to_tokens(tokens); - }); - } -} - -#[derive(Debug, Clone)] -pub enum Attribute { - Class { - dot_token: Dot, - name: HtmlNameOrMarkup, - toggler: Option, - }, - Id { - pound_token: Pound, - name: HtmlNameOrMarkup, - }, - Named { - name: HtmlName, - attr_type: AttributeType, - }, -} - -impl DiagnosticParse for Attribute { - fn diagnostic_parse( - input: ParseStream, - diagnostics: &mut Vec, - ) -> syn::Result { - let lookahead = input.lookahead1(); - - if lookahead.peek(Dot) { - Ok(Self::Class { - dot_token: input.parse()?, - name: input.diagnostic_parse(diagnostics)?, - toggler: { - let lookahead = input.lookahead1(); - - if lookahead.peek(Bracket) { - Some(input.diagnostic_parse(diagnostics)?) - } else { - None - } - }, - }) - } else if lookahead.peek(Pound) { - Ok(Self::Id { - pound_token: input.parse()?, - name: input.diagnostic_parse(diagnostics)?, - }) - } else { - let name = input.diagnostic_parse::(diagnostics)?; - - if input.peek(Question) { - input.parse::()?; - } - - let fork = input.fork(); - - let attr = Self::Named { - name: name.clone(), - attr_type: input.diagnostic_parse(diagnostics)?, - }; - - if fork.peek(Eq) && fork.peek2(LitBool) { - diagnostics.push( - attr.span() - .error("attribute value must be a string") - .help(format!("to declare an empty attribute, omit the equals sign: `{name}`")) - .help(format!("to toggle the attribute, use square brackets: `{name}[some_boolean_flag]`")) - ); - } - - Ok(attr) - } - } -} - -impl ToTokens for Attribute { - fn to_tokens(&self, tokens: &mut TokenStream) { - match self { - Self::Class { - dot_token, - name, - toggler, - } => { - dot_token.to_tokens(tokens); - name.to_tokens(tokens); - if let Some(toggler) = toggler { - toggler.to_tokens(tokens); - } - } - Self::Id { pound_token, name } => { - pound_token.to_tokens(tokens); - name.to_tokens(tokens); - } - Self::Named { name, attr_type } => { - name.to_tokens(tokens); - attr_type.to_tokens(tokens); - } - } - } -} - -#[derive(Debug, Clone)] -pub enum HtmlNameOrMarkup { - HtmlName(HtmlName), - Markup(Markup), -} - -impl DiagnosticParse for HtmlNameOrMarkup { - fn diagnostic_parse( - input: ParseStream, - diagnostics: &mut Vec, - ) -> syn::Result { - if input.peek(Ident::peek_any) || input.peek(Lit) { - input.diagnostic_parse(diagnostics).map(Self::HtmlName) - } else { - input.diagnostic_parse(diagnostics).map(Self::Markup) - } - } -} - -impl Parse for HtmlNameOrMarkup { - fn parse(input: ParseStream) -> syn::Result { - Self::diagnostic_parse(input, &mut Vec::new()) - } -} - -impl ToTokens for HtmlNameOrMarkup { - fn to_tokens(&self, tokens: &mut TokenStream) { - match self { - Self::HtmlName(name) => name.to_tokens(tokens), - Self::Markup(markup) => markup.to_tokens(tokens), - } - } -} - -impl Display for HtmlNameOrMarkup { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - Self::HtmlName(name) => name.fmt(f), - Self::Markup(markup) => markup.to_token_stream().fmt(f), - } - } -} - -#[derive(Debug, Clone)] -pub enum AttributeType { - Normal { - eq_token: Eq, - value: Markup, - }, - Optional { - eq_token: Eq, - toggler: Toggler, - }, - Empty(Option), -} - -impl DiagnosticParse for AttributeType { - fn diagnostic_parse( - input: ParseStream, - diagnostics: &mut Vec, - ) -> syn::Result { - let lookahead = input.lookahead1(); - - if lookahead.peek(Eq) { - let eq_token = input.parse()?; - - if input.peek(Bracket) { - Ok(Self::Optional { - eq_token, - toggler: input.diagnostic_parse(diagnostics)?, - }) - } else { - Ok(Self::Normal { - eq_token, - value: input.diagnostic_parse(diagnostics)?, - }) - } - } else if lookahead.peek(Bracket) { - Ok(Self::Empty(Some(input.diagnostic_parse(diagnostics)?))) - } else { - Ok(Self::Empty(None)) - } - } -} - -impl ToTokens for AttributeType { - fn to_tokens(&self, tokens: &mut TokenStream) { - match self { - Self::Normal { eq_token, value } => { - eq_token.to_tokens(tokens); - value.to_tokens(tokens); - } - Self::Optional { eq_token, toggler } => { - eq_token.to_tokens(tokens); - toggler.to_tokens(tokens); - } - Self::Empty(toggler) => { - if let Some(toggler) = toggler { - toggler.to_tokens(tokens); - } - } - } - } -} - -#[derive(Debug, Clone)] -pub struct HtmlName { - pub name: Punctuated, -} - -impl DiagnosticParse for HtmlName { - fn diagnostic_parse( - input: ParseStream, - diagnostics: &mut Vec, - ) -> syn::Result { - Ok(Self { - name: { - let mut punctuated = Punctuated::new(); - - loop { - punctuated.push_value(input.diagnostic_parse(diagnostics)?); - - if !(input.peek(Minus) || input.peek(Colon)) { - break; - } - - let punct = input.diagnostic_parse(diagnostics)?; - punctuated.push_punct(punct); - } - - punctuated - }, - }) - } -} - -impl Parse for HtmlName { - fn parse(input: ParseStream) -> syn::Result { - Self::diagnostic_parse(input, &mut Vec::new()) - } -} - -impl ToTokens for HtmlName { - fn to_tokens(&self, tokens: &mut TokenStream) { - self.name.to_tokens(tokens); - } -} - -impl Display for HtmlName { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - for pair in self.name.pairs() { - match pair { - Pair::Punctuated(fragment, punct) => { - fragment.fmt(f)?; - punct.fmt(f)?; - } - Pair::End(fragment) => { - fragment.fmt(f)?; - } - } - } - - Ok(()) - } -} - -#[derive(Debug, Clone)] -pub enum HtmlNameFragment { - Ident(Ident), - LitInt(LitInt), - LitStr(LitStr), - Empty, -} - -impl DiagnosticParse for HtmlNameFragment { - fn diagnostic_parse( - input: ParseStream, - _diagnostics: &mut Vec, - ) -> syn::Result { - let lookahead = input.lookahead1(); - - if lookahead.peek(Ident::peek_any) { - input.call(Ident::parse_any).map(Self::Ident) - } else if lookahead.peek(LitInt) { - input.parse().map(Self::LitInt) - } else if lookahead.peek(LitStr) { - input.parse().map(Self::LitStr) - } else if lookahead.peek(Minus) || lookahead.peek(Colon) { - Ok(Self::Empty) - } else { - Err(lookahead.error()) - } - } -} - -impl ToTokens for HtmlNameFragment { - fn to_tokens(&self, tokens: &mut TokenStream) { - match self { - Self::Ident(ident) => ident.to_tokens(tokens), - Self::LitInt(lit) => lit.to_tokens(tokens), - Self::LitStr(lit) => lit.to_tokens(tokens), - Self::Empty => {} - } - } -} - -impl Display for HtmlNameFragment { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - Self::Ident(ident) => ident.fmt(f), - Self::LitInt(lit) => lit.fmt(f), - Self::LitStr(lit) => lit.value().fmt(f), - Self::Empty => Ok(()), - } - } -} - -#[derive(Debug, Clone)] -pub struct HtmlLit { - pub lit: LitStr, -} - -impl DiagnosticParse for HtmlLit { - fn diagnostic_parse( - input: ParseStream, - diagnostics: &mut Vec, - ) -> syn::Result { - let lookahead = input.lookahead1(); - - if lookahead.peek(Lit) { - let lit = input.parse()?; - match lit { - Lit::Str(lit) => Ok(Self { lit }), - Lit::Int(lit) => { - diagnostics.push( - lit.span() - .error(format!(r#"literal must be double-quoted: `"{lit}"`"#)), - ); - Ok(Self { - lit: LitStr::new("", lit.span()), - }) - } - Lit::Float(lit) => { - diagnostics.push( - lit.span() - .error(format!(r#"literal must be double-quoted: `"{lit}"`"#)), - ); - Ok(Self { - lit: LitStr::new("", lit.span()), - }) - } - Lit::Char(lit) => { - diagnostics.push(lit.span().error(format!( - r#"literal must be double-quoted: `"{}"`"#, - lit.value() - ))); - Ok(Self { - lit: LitStr::new("", lit.span()), - }) - } - Lit::Bool(_) => { - // diagnostic handled earlier with more information - Ok(Self { - lit: LitStr::new("", lit.span()), - }) - } - _ => { - diagnostics.push(lit.span().error("expected string")); - Ok(Self { - lit: LitStr::new("", lit.span()), - }) - } - } - } else { - Err(lookahead.error()) - } - } -} - -impl ToTokens for HtmlLit { - fn to_tokens(&self, tokens: &mut TokenStream) { - self.lit.to_tokens(tokens); - } -} - -impl Display for HtmlLit { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - self.lit.value().fmt(f) - } -} - -#[derive(Debug, Clone)] -pub enum HtmlNamePunct { - Colon(Colon), - Hyphen(Minus), -} - -impl DiagnosticParse for HtmlNamePunct { - fn diagnostic_parse(input: ParseStream, _: &mut Vec) -> syn::Result { - let lookahead = input.lookahead1(); - - if lookahead.peek(Colon) { - input.parse().map(Self::Colon) - } else if lookahead.peek(Minus) { - input.parse().map(Self::Hyphen) - } else { - Err(lookahead.error()) - } - } -} - -impl ToTokens for HtmlNamePunct { - fn to_tokens(&self, tokens: &mut TokenStream) { - match self { - Self::Colon(token) => token.to_tokens(tokens), - Self::Hyphen(token) => token.to_tokens(tokens), - } - } -} - -impl Display for HtmlNamePunct { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - Self::Colon(_) => f.write_str(":"), - Self::Hyphen(_) => f.write_str("-"), - } - } -} - -#[derive(Debug, Clone)] -pub struct Toggler { - pub bracket_token: Bracket, - pub cond: Expr, -} - -impl DiagnosticParse for Toggler { - fn diagnostic_parse(input: ParseStream, _: &mut Vec) -> syn::Result { - let content; - Ok(Self { - bracket_token: bracketed!(content in input), - cond: content.parse()?, - }) - } -} - -impl ToTokens for Toggler { - fn to_tokens(&self, tokens: &mut TokenStream) { - self.bracket_token.surround(tokens, |tokens| { - self.cond.to_tokens(tokens); - }); - } -} - -#[derive(Debug, Clone)] -pub struct ControlFlow { - pub at_token: At, - pub kind: ControlFlowKind, -} - -impl DiagnosticParse for ControlFlow { - fn diagnostic_parse( - input: ParseStream, - diagnostics: &mut Vec, - ) -> syn::Result { - Ok(Self { - at_token: input.parse()?, - kind: { - let lookahead = input.lookahead1(); - - if lookahead.peek(If) { - ControlFlowKind::If(input.diagnostic_parse(diagnostics)?) - } else if lookahead.peek(For) { - ControlFlowKind::For(input.diagnostic_parse(diagnostics)?) - } else if lookahead.peek(While) { - ControlFlowKind::While(input.diagnostic_parse(diagnostics)?) - } else if lookahead.peek(Match) { - ControlFlowKind::Match(input.diagnostic_parse(diagnostics)?) - } else if lookahead.peek(Let) { - let Stmt::Local(local) = input.parse()? else { - unreachable!() - }; - - ControlFlowKind::Let(local) - } else { - return Err(lookahead.error()); - } - }, - }) - } -} - -impl ToTokens for ControlFlow { - fn to_tokens(&self, tokens: &mut TokenStream) { - self.at_token.to_tokens(tokens); - match &self.kind { - ControlFlowKind::Let(local) => local.to_tokens(tokens), - ControlFlowKind::If(if_) => if_.to_tokens(tokens), - ControlFlowKind::For(for_) => for_.to_tokens(tokens), - ControlFlowKind::While(while_) => while_.to_tokens(tokens), - ControlFlowKind::Match(match_) => match_.to_tokens(tokens), - } - } -} - -#[derive(Debug, Clone)] -pub enum ControlFlowKind { - Let(Local), - If(IfExpr), - For(ForExpr), - While(WhileExpr), - Match(MatchExpr), -} - -#[derive(Debug, Clone)] -pub struct IfExpr { - pub if_token: If, - pub cond: Expr, - pub then_branch: Block, - pub else_branch: Option<(At, Else, Box>)>, -} - -impl DiagnosticParse for IfExpr { - fn diagnostic_parse( - input: ParseStream, - diagnostics: &mut Vec, - ) -> syn::Result { - Ok(Self { - if_token: input.parse()?, - cond: input.call(Expr::parse_without_eager_brace)?, - then_branch: input.diagnostic_parse(diagnostics)?, - else_branch: { - if input.peek(At) && input.peek2(Else) { - Some(( - input.parse()?, - input.parse()?, - input.diagnostic_parse(diagnostics)?, - )) - } else { - None - } - }, - }) - } -} - -impl ToTokens for IfExpr { - fn to_tokens(&self, tokens: &mut TokenStream) { - self.if_token.to_tokens(tokens); - self.cond.to_tokens(tokens); - self.then_branch.to_tokens(tokens); - if let Some((at_token, else_token, else_branch)) = &self.else_branch { - at_token.to_tokens(tokens); - else_token.to_tokens(tokens); - else_branch.to_tokens(tokens); - } - } -} - -#[derive(Debug, Clone)] -pub enum IfOrBlock { - If(IfExpr), - Block(Block), -} - -impl DiagnosticParse for IfOrBlock { - fn diagnostic_parse( - input: ParseStream, - diagnostics: &mut Vec, - ) -> syn::Result { - let lookahead = input.lookahead1(); - - if lookahead.peek(If) { - input.diagnostic_parse(diagnostics).map(Self::If) - } else if lookahead.peek(Brace) { - input.diagnostic_parse(diagnostics).map(Self::Block) - } else { - Err(lookahead.error()) - } - } -} - -impl ToTokens for IfOrBlock { - fn to_tokens(&self, tokens: &mut TokenStream) { - match self { - Self::If(if_) => if_.to_tokens(tokens), - Self::Block(block) => block.to_tokens(tokens), - } - } -} - -#[derive(Debug, Clone)] -pub struct ForExpr { - pub for_token: For, - pub pat: Pat, - pub in_token: In, - pub expr: Expr, - pub body: Block, -} - -impl DiagnosticParse for ForExpr { - fn diagnostic_parse( - input: ParseStream, - diagnostics: &mut Vec, - ) -> syn::Result { - Ok(Self { - for_token: input.parse()?, - pat: input.call(Pat::parse_multi_with_leading_vert)?, - in_token: input.parse()?, - expr: input.call(Expr::parse_without_eager_brace)?, - body: input.diagnostic_parse(diagnostics)?, - }) - } -} - -impl ToTokens for ForExpr { - fn to_tokens(&self, tokens: &mut TokenStream) { - self.for_token.to_tokens(tokens); - self.pat.to_tokens(tokens); - self.in_token.to_tokens(tokens); - self.expr.to_tokens(tokens); - self.body.to_tokens(tokens); - } -} - -#[derive(Debug, Clone)] -pub struct WhileExpr { - pub while_token: While, - pub cond: Expr, - pub body: Block, -} - -impl DiagnosticParse for WhileExpr { - fn diagnostic_parse( - input: ParseStream, - diagnostics: &mut Vec, - ) -> syn::Result { - Ok(Self { - while_token: input.parse()?, - cond: input.call(Expr::parse_without_eager_brace)?, - body: input.diagnostic_parse(diagnostics)?, - }) - } -} - -impl ToTokens for WhileExpr { - fn to_tokens(&self, tokens: &mut TokenStream) { - self.while_token.to_tokens(tokens); - self.cond.to_tokens(tokens); - self.body.to_tokens(tokens); - } -} - -#[derive(Debug, Clone)] -pub struct MatchExpr { - pub match_token: Match, - pub expr: Expr, - pub brace_token: Brace, - pub arms: Vec>, -} - -impl DiagnosticParse for MatchExpr { - fn diagnostic_parse( - input: ParseStream, - diagnostics: &mut Vec, - ) -> syn::Result { - let match_token = input.parse()?; - let expr = input.call(Expr::parse_without_eager_brace)?; - - let content; - let brace_token = braced!(content in input); - - let mut arms = Vec::new(); - while !content.is_empty() { - arms.push(content.diagnostic_parse(diagnostics)?); - } - - Ok(Self { - match_token, - expr, - brace_token, - arms, - }) - } -} - -impl ToTokens for MatchExpr { - fn to_tokens(&self, tokens: &mut TokenStream) { - self.match_token.to_tokens(tokens); - self.expr.to_tokens(tokens); - self.brace_token.surround(tokens, |tokens| { - for arm in &self.arms { - arm.to_tokens(tokens); - } - }); - } -} - -#[derive(Debug, Clone)] -pub struct MatchArm { - pub pat: Pat, - pub guard: Option<(If, Expr)>, - pub fat_arrow_token: FatArrow, - pub body: Markup, - pub comma_token: Option, -} - -impl DiagnosticParse for MatchArm { - fn diagnostic_parse( - input: ParseStream, - diagnostics: &mut Vec, - ) -> syn::Result { - Ok(Self { - pat: Pat::parse_multi_with_leading_vert(input)?, - guard: { - if input.peek(If) { - Some((input.parse()?, input.parse()?)) - } else { - None - } - }, - fat_arrow_token: input.parse()?, - body: Markup::diagnostic_parse_in_block(input, diagnostics)?, - comma_token: if input.peek(Comma) { - Some(input.parse()?) - } else { - None - }, - }) - } -} - -impl ToTokens for MatchArm { - fn to_tokens(&self, tokens: &mut TokenStream) { - self.pat.to_tokens(tokens); - if let Some((if_token, guard)) = &self.guard { - if_token.to_tokens(tokens); - guard.to_tokens(tokens); - } - self.fat_arrow_token.to_tokens(tokens); - self.body.to_tokens(tokens); - if let Some(comma_token) = &self.comma_token { - comma_token.to_tokens(tokens); - } - } -} - -pub trait DiagnosticParse: Sized { - fn diagnostic_parse(input: ParseStream, diagnostics: &mut Vec) - -> syn::Result; -} - -impl DiagnosticParse for Box { - fn diagnostic_parse( - input: ParseStream, - diagnostics: &mut Vec, - ) -> syn::Result { - Ok(Box::new(input.diagnostic_parse(diagnostics)?)) - } -} - -trait DiagonsticParseExt: Sized { - fn diagnostic_parse( - self, - diagnostics: &mut Vec, - ) -> syn::Result; -} - -impl DiagonsticParseExt for ParseStream<'_> { - fn diagnostic_parse(self, diagnostics: &mut Vec) -> syn::Result - where - T: DiagnosticParse, - { - T::diagnostic_parse(self, diagnostics) - } -} diff --git a/helpers/pagetop-macros/src/maud/escape.rs b/helpers/pagetop-macros/src/maud/escape.rs deleted file mode 100644 index 786d8c7..0000000 --- a/helpers/pagetop-macros/src/maud/escape.rs +++ /dev/null @@ -1,27 +0,0 @@ -// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -// !!!!!!!! PLEASE KEEP THIS IN SYNC WITH `maud/src/escape.rs` !!!!!!!!! -// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - -pub fn escape_to_string(input: &str, output: &mut String) { - for b in input.bytes() { - match b { - b'&' => output.push_str("&"), - b'<' => output.push_str("<"), - b'>' => output.push_str(">"), - b'"' => output.push_str("""), - _ => unsafe { output.as_mut_vec().push(b) }, - } - } -} - -#[cfg(test)] -mod test { - use super::escape_to_string; - - #[test] - fn it_works() { - let mut s = String::new(); - escape_to_string("", &mut s); - assert_eq!(s, "<script>launchMissiles()</script>"); - } -} diff --git a/helpers/pagetop-macros/src/maud/generate.rs b/helpers/pagetop-macros/src/maud/generate.rs deleted file mode 100644 index 1f82578..0000000 --- a/helpers/pagetop-macros/src/maud/generate.rs +++ /dev/null @@ -1,392 +0,0 @@ -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: Markups, 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: Ident, -} - -impl Generator { - fn new(output_ident: Ident) -> Generator { - Generator { output_ident } - } - - fn builder(&self) -> Builder { - Builder::new(self.output_ident.clone()) - } - - fn markups>(&self, markups: Markups, build: &mut Builder) { - for markup in markups.markups { - self.markup(markup, build); - } - } - - fn markup>(&self, markup: Markup, build: &mut Builder) { - match markup { - 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(block.markups, build); - } - } - Markup::Lit(lit) => build.push_escaped(&lit.to_string()), - Markup::Splice { expr, .. } => self.splice(expr, build), - Markup::Element(element) => self.element(element.into(), build), - Markup::ControlFlow(control_flow) => self.control_flow(control_flow, build), - Markup::Semi(_) => {} - } - } - - fn block>(&self, block: Block, build: &mut Builder) { - let markups = { - let mut build = self.builder(); - self.markups(block.markups, &mut build); - build.finish() - }; - - build.push_tokens(quote!({ #markups })); - } - - fn splice(&self, expr: Expr, build: &mut Builder) { - let output_ident = &self.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, element: Element, build: &mut Builder) { - let element_name = element.name.clone().unwrap_or_else(|| parse_quote!(div)); - build.push_str("<"); - self.name(element_name.clone(), build); - self.attrs(element.attrs, build); - build.push_str(">"); - if let ElementBody::Block(block) = element.body { - self.markups(block.markups, build); - build.push_str(""); - } - } - - fn name(&self, name: HtmlName, build: &mut Builder) { - build.push_escaped(&name.to_string()); - } - - 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, &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 })); - } - 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, &mut build); - build.finish() - }; - build.push_tokens(quote!(if (#cond) { #body })); - } - } - } - - fn attrs(&self, attrs: Vec, 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>(&self, control_flow: ControlFlow, 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>( - &self, - IfExpr { - if_token, - cond, - then_branch, - else_branch, - }: IfExpr, - 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>( - &self, - if_or_block: IfOrBlock, - 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>( - &self, - ForExpr { - for_token, - pat, - in_token, - expr, - body, - }: ForExpr, - build: &mut Builder, - ) { - build.push_tokens(quote!(#for_token #pat #in_token (#expr))); - self.block(body, build); - } - - fn control_flow_while>( - &self, - WhileExpr { - while_token, - cond, - body, - }: WhileExpr, - build: &mut Builder, - ) { - build.push_tokens(quote!(#while_token #cond)); - self.block(body, build); - } - - fn control_flow_match>( - &self, - MatchExpr { - match_token, - expr, - brace_token, - arms, - }: MatchExpr, - 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)); - } -} - -//////////////////////////////////////////////////////// - -#[allow(clippy::type_complexity)] -fn split_attrs( - attrs: Vec, -) -> ( - Vec<(HtmlNameOrMarkup, Option)>, - Option, - Vec<(HtmlName, AttributeType)>, -) { - let mut classes = vec![]; - let mut id = None; - let mut named_attrs = vec![]; - - for attr in attrs { - match 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)), - } - } - - (classes, id, named_attrs) -} - -//////////////////////////////////////////////////////// - -struct Builder { - output_ident: Ident, - tokens: TokenStream, - tail: String, -} - -impl Builder { - fn new(output_ident: Ident) -> Builder { - Builder { - output_ident, - tokens: TokenStream::new(), - tail: String::new(), - } - } - - fn push_str(&mut self, string: &'static str) { - self.tail.push_str(string); - } - - fn push_escaped(&mut self, string: &str) { - escape::escape_to_string(string, &mut self.tail); - } - - fn push_tokens(&mut self, tokens: TokenStream) { - self.cut(); - self.tokens.extend(tokens); - } - - fn cut(&mut self) { - if self.tail.is_empty() { - return; - } - let push_str_expr = { - let output_ident = self.output_ident.clone(); - let tail = &self.tail; - quote!(#output_ident.push_str(#tail);) - }; - self.tail.clear(); - self.tokens.extend(push_str_expr); - } - - fn finish(mut self) -> TokenStream { - self.cut(); - self.tokens - } -} diff --git a/src/html.rs b/src/html.rs deleted file mode 100644 index 14e72b9..0000000 --- a/src/html.rs +++ /dev/null @@ -1,4 +0,0 @@ -//! HTML en código. - -mod maud; -pub use maud::{display, html, html_private, Escaper, Markup, PreEscaped, Render, DOCTYPE}; diff --git a/src/html/maud.rs b/src/html/maud.rs deleted file mode 100644 index 1942986..0000000 --- a/src/html/maud.rs +++ /dev/null @@ -1,381 +0,0 @@ -// #![no_std] - -//! A macro for writing HTML templates. -//! -//! This documentation only describes the runtime API. For a general -//! guide, check out the [book] instead. -//! -//! [book]: https://maud.lambda.xyz/ - -// #![doc(html_root_url = "https://docs.rs/maud/0.27.0")] - -extern crate alloc; - -use alloc::{borrow::Cow, boxed::Box, string::String, sync::Arc}; -use core::fmt::{self, Arguments, Display, Write}; - -pub use pagetop_macros::html; - -mod escape; - -/// An adapter that escapes HTML special characters. -/// -/// The following characters are escaped: -/// -/// * `&` is escaped as `&` -/// * `<` is escaped as `<` -/// * `>` is escaped as `>` -/// * `"` is escaped as `"` -/// -/// All other characters are passed through unchanged. -/// -/// **Note:** In versions prior to 0.13, the single quote (`'`) was -/// escaped as well. -/// -/// # Example -/// -/// ```rust -/// use pagetop::html::Escaper; -/// use std::fmt::Write; -/// let mut s = String::new(); -/// write!(Escaper::new(&mut s), "").unwrap(); -/// assert_eq!(s, "<script>launchMissiles()</script>"); -/// ``` -pub struct Escaper<'a>(&'a mut String); - -impl<'a> Escaper<'a> { - /// Creates an `Escaper` from a `String`. - pub fn new(buffer: &'a mut String) -> Escaper<'a> { - Escaper(buffer) - } -} - -impl fmt::Write for Escaper<'_> { - fn write_str(&mut self, s: &str) -> fmt::Result { - escape::escape_to_string(s, self.0); - Ok(()) - } -} - -/// Represents a type that can be rendered as HTML. -/// -/// To implement this for your own type, override either the `.render()` -/// or `.render_to()` methods; since each is defined in terms of the -/// other, you only need to implement one of them. See the example below. -/// -/// # Minimal implementation -/// -/// An implementation of this trait must override at least one of -/// `.render()` or `.render_to()`. Since the default definitions of -/// these methods call each other, not doing this will result in -/// infinite recursion. -/// -/// # Example -/// -/// ```rust -/// use pagetop::html::{html, Markup, Render}; -/// -/// /// Provides a shorthand for linking to a CSS stylesheet. -/// pub struct Stylesheet(&'static str); -/// -/// impl Render for Stylesheet { -/// fn render(&self) -> Markup { -/// html! { -/// link rel="stylesheet" type="text/css" href=(self.0); -/// } -/// } -/// } -/// ``` -pub trait Render { - /// Renders `self` as a block of `Markup`. - fn render(&self) -> Markup { - let mut buffer = String::new(); - self.render_to(&mut buffer); - PreEscaped(buffer) - } - - /// Appends a representation of `self` to the given buffer. - /// - /// Its default implementation just calls `.render()`, but you may - /// override it with something more efficient. - /// - /// Note that no further escaping is performed on data written to - /// the buffer. If you override this method, you must make sure that - /// any data written is properly escaped, whether by hand or using - /// the [`Escaper`](struct.Escaper.html) wrapper struct. - fn render_to(&self, buffer: &mut String) { - buffer.push_str(&self.render().into_string()); - } -} - -impl Render for str { - fn render_to(&self, w: &mut String) { - escape::escape_to_string(self, w); - } -} - -impl Render for String { - fn render_to(&self, w: &mut String) { - str::render_to(self, w); - } -} - -impl Render for Cow<'_, str> { - fn render_to(&self, w: &mut String) { - str::render_to(self, w); - } -} - -impl Render for Arguments<'_> { - fn render_to(&self, w: &mut String) { - let _ = Escaper::new(w).write_fmt(*self); - } -} - -impl Render for &T { - fn render_to(&self, w: &mut String) { - T::render_to(self, w); - } -} - -impl Render for &mut T { - fn render_to(&self, w: &mut String) { - T::render_to(self, w); - } -} - -impl Render for Box { - fn render_to(&self, w: &mut String) { - T::render_to(self, w); - } -} - -impl Render for Arc { - fn render_to(&self, w: &mut String) { - T::render_to(self, w); - } -} - -macro_rules! impl_render_with_display { - ($($ty:ty)*) => { - $( - impl Render for $ty { - fn render_to(&self, w: &mut String) { - // TODO: remove the explicit arg when Rust 1.58 is released - format_args!("{self}", self = self).render_to(w); - } - } - )* - }; -} - -impl_render_with_display! { - char f32 f64 -} - -macro_rules! impl_render_with_itoa { - ($($ty:ty)*) => { - $( - impl Render for $ty { - fn render_to(&self, w: &mut String) { - w.push_str(itoa::Buffer::new().format(*self)); - } - } - )* - }; -} - -impl_render_with_itoa! { - i8 i16 i32 i64 i128 isize - u8 u16 u32 u64 u128 usize -} - -/// Renders a value using its [`Display`] impl. -/// -/// # Example -/// -/// ```rust -/// use pagetop::html::{display, html}; -/// use std::net::Ipv4Addr; -/// -/// let ip_address = Ipv4Addr::new(127, 0, 0, 1); -/// -/// let markup = html! { -/// "My IP address is: " -/// (display(ip_address)) -/// }; -/// -/// assert_eq!(markup.into_string(), "My IP address is: 127.0.0.1"); -/// ``` -pub fn display(value: impl Display) -> impl Render { - struct DisplayWrapper(T); - - impl Render for DisplayWrapper { - fn render_to(&self, w: &mut String) { - format_args!("{0}", self.0).render_to(w); - } - } - - DisplayWrapper(value) -} - -/// A wrapper that renders the inner value without escaping. -#[derive(Debug, Clone, Copy)] -pub struct PreEscaped(pub T); - -impl> Render for PreEscaped { - fn render_to(&self, w: &mut String) { - w.push_str(self.0.as_ref()); - } -} - -/// A block of markup is a string that does not need to be escaped. -/// -/// The `html!` macro expands to an expression of this type. -pub type Markup = PreEscaped; - -impl Markup { - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } -} - -impl> PreEscaped { - /// Converts the inner value to a string. - pub fn into_string(self) -> String { - self.0.into() - } -} - -impl> From> for String { - fn from(value: PreEscaped) -> String { - value.into_string() - } -} - -impl Default for PreEscaped { - fn default() -> Self { - Self(Default::default()) - } -} - -/// The literal string ``. -/// -/// # Example -/// -/// A minimal web page: -/// -/// ```rust -/// use pagetop::html::{DOCTYPE, html}; -/// -/// let markup = html! { -/// (DOCTYPE) -/// html { -/// head { -/// meta charset="utf-8"; -/// title { "Test page" } -/// } -/// body { -/// p { "Hello, world!" } -/// } -/// } -/// }; -/// ``` -pub const DOCTYPE: PreEscaped<&'static str> = PreEscaped(""); - -mod actix_support { - extern crate alloc; - - use core::{ - pin::Pin, - task::{Context, Poll}, - }; - - use crate::html::PreEscaped; - use actix_web::{ - body::{BodySize, MessageBody}, - http::header, - web::Bytes, - HttpRequest, HttpResponse, Responder, - }; - use alloc::string::String; - - impl MessageBody for PreEscaped { - type Error = ::Error; - - fn size(&self) -> BodySize { - self.0.size() - } - - fn poll_next( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll>> { - Pin::new(&mut self.0).poll_next(cx) - } - } - - impl Responder for PreEscaped { - type Body = String; - - fn respond_to(self, _req: &HttpRequest) -> HttpResponse { - HttpResponse::Ok() - .content_type(header::ContentType::html()) - .message_body(self.0) - .unwrap() - } - } -} - -#[doc(hidden)] -pub mod html_private { - extern crate alloc; - - use super::{display, Render}; - use alloc::string::String; - use core::fmt::Display; - - #[doc(hidden)] - #[macro_export] - macro_rules! render_to { - ($x:expr, $buffer:expr) => {{ - use $crate::html::html_private::*; - match ChooseRenderOrDisplay($x) { - x => (&&x).implements_render_or_display().render_to(x.0, $buffer), - } - }}; - } - - pub use render_to; - - pub struct ChooseRenderOrDisplay(pub T); - - pub struct ViaRenderTag; - pub struct ViaDisplayTag; - - pub trait ViaRender { - fn implements_render_or_display(&self) -> ViaRenderTag { - ViaRenderTag - } - } - pub trait ViaDisplay { - fn implements_render_or_display(&self) -> ViaDisplayTag { - ViaDisplayTag - } - } - - impl ViaRender for &ChooseRenderOrDisplay {} - impl ViaDisplay for ChooseRenderOrDisplay {} - - impl ViaRenderTag { - pub fn render_to(self, value: &T, buffer: &mut String) { - value.render_to(buffer); - } - } - - impl ViaDisplayTag { - pub fn render_to(self, value: &T, buffer: &mut String) { - display(value).render_to(buffer); - } - } -} diff --git a/src/html/maud/escape.rs b/src/html/maud/escape.rs deleted file mode 100644 index 94cdeec..0000000 --- a/src/html/maud/escape.rs +++ /dev/null @@ -1,34 +0,0 @@ -// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -// !!!!! PLEASE KEEP THIS IN SYNC WITH `maud_macros/src/escape.rs` !!!!! -// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - -extern crate alloc; - -use alloc::string::String; - -pub fn escape_to_string(input: &str, output: &mut String) { - for b in input.bytes() { - match b { - b'&' => output.push_str("&"), - b'<' => output.push_str("<"), - b'>' => output.push_str(">"), - b'"' => output.push_str("""), - _ => unsafe { output.as_mut_vec().push(b) }, - } - } -} - -#[cfg(test)] -mod test { - extern crate alloc; - - use super::escape_to_string; - use alloc::string::String; - - #[test] - fn it_works() { - let mut s = String::new(); - escape_to_string("", &mut s); - assert_eq!(s, "<script>launchMissiles()</script>"); - } -} diff --git a/src/lib.rs b/src/lib.rs index 2f97351..308c163 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,7 +32,7 @@ // RE-EXPORTED ************************************************************************************* -pub use pagetop_macros::{html, main, test}; +pub use pagetop_macros::{main, test}; // API ********************************************************************************************* @@ -42,8 +42,6 @@ pub mod config; pub mod global; // Gestión de trazas y registro de eventos de la aplicación. pub mod trace; -// HTML en código. -pub mod html; // Gestión del servidor y servicios web. pub mod service; // Prepara y ejecuta la aplicación. diff --git a/src/prelude.rs b/src/prelude.rs index eb59c4f..5d5851f 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -2,7 +2,7 @@ // RE-EXPORTED. -pub use crate::{html, main, test}; +pub use crate::{main, test}; // MACROS. @@ -15,8 +15,6 @@ pub use crate::global; pub use crate::trace; -pub use crate::html::*; - pub use crate::service; pub use crate::app::Application; diff --git a/src/service.rs b/src/service.rs index e6904b8..9fe2450 100644 --- a/src/service.rs +++ b/src/service.rs @@ -1,4 +1,4 @@ -//! Gestión del servidor y servicios web ([Actix Web](https://docs.rs/actix-web)). +//! Gestión del servidor y servicios web ([actix-web](https://docs.rs/actix-web)). pub use actix_web::body::BoxBody; pub use actix_web::dev::Server;