markup-proc-macro-0.13.1/.cargo_vcs_info.json0000644000000001570000000000100144600ustar { "git": { "sha1": "674c842b43359f6fbd8d9d6376a99c5da7aa3577" }, "path_in_vcs": "markup-proc-macro" }markup-proc-macro-0.13.1/Cargo.toml0000644000000020250000000000100124520ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "markup-proc-macro" version = "0.13.1" authors = ["Utkarsh Kukreti "] description = "A blazing fast, type-safe template engine for Rust." homepage = "https://github.com/utkarshkukreti/markup.rs" documentation = "https://docs.rs/markup" license = "MIT/Apache-2.0" repository = "https://github.com/utkarshkukreti/markup.rs" [lib] proc-macro = true [dependencies.proc-macro2] version = "1.0.3" [dependencies.quote] version = "1.0.2" [dependencies.syn] version = "1.0.5" features = [ "extra-traits", "full", ] markup-proc-macro-0.13.1/Cargo.toml.orig000064400000000000000000000010160072674642500161620ustar 00000000000000[package] name = "markup-proc-macro" version = "0.13.1" authors = ["Utkarsh Kukreti "] edition = "2018" description = "A blazing fast, type-safe template engine for Rust." license = "MIT/Apache-2.0" documentation = "https://docs.rs/markup" homepage = "https://github.com/utkarshkukreti/markup.rs" repository = "https://github.com/utkarshkukreti/markup.rs" [lib] proc-macro = true [dependencies] proc-macro2 = "1.0.3" quote = "1.0.2" syn = { version = "1.0.5", features = ["extra-traits", "full"] } markup-proc-macro-0.13.1/README.md000064400000000000000000000076220072674642500145630ustar 00000000000000
# markup.rs A blazing fast, type-safe template engine for Rust. [![Build](https://img.shields.io/github/workflow/status/utkarshkukreti/markup.rs/Build?style=for-the-badge)](https://github.com/utkarshkukreti/markup.rs/actions/workflows/build.yml) [![Version](https://img.shields.io/crates/v/markup?style=for-the-badge)](https://crates.io/crates/markup) [![Documentation](https://img.shields.io/docsrs/markup?style=for-the-badge)](https://docs.rs/markup) [![Downloads](https://img.shields.io/crates/d/markup?style=for-the-badge)](https://crates.io/crates/markup) [![License](https://img.shields.io/crates/l/markup?style=for-the-badge)](https://crates.io/crates/markup)
`markup.rs` is a template engine for Rust powered by procedural macros which parses the template at compile time and generates optimal Rust code to render the template at run time. The templates may embed Rust code which is type checked by the Rust compiler enabling full type-safety. ## Features * Fully type-safe with inline highlighted errors when using editor extensions like [rust-analyzer](https://github.com/rust-analyzer/rust-analyzer). * Less error-prone and terse syntax inspired by [Haml](https://haml.info/), [Slim](http://slim-lang.com/), and [Pug](https://pugjs.org). * Zero unsafe code. * Zero runtime dependencies. * ⚡ Blazing fast. The fastest in [this](https://github.com/djc/template-benchmarks-rs) benchmark among the ones which do not use unsafe code, the second fastest overall. ## Install ```toml [dependencies] markup = "0.13.1" ``` ## Example ```rust markup::define! { Home<'a>(title: &'a str) { @markup::doctype() html { head { title { @title } style { "body { background: #fafbfc; }" "#main { padding: 2rem; }" } } body { @Header { title } #main { p { "This domain is for use in illustrative examples in documents. You may \ use this domain in literature without prior coordination or asking for \ permission." } p { a[href = "https://www.iana.org/domains/example"] { "More information..." } } } @Footer { year: 2020 } } } } Header<'a>(title: &'a str) { header { h1 { @title } } } Footer(year: u32) { footer { "(c) " @year } } } fn main() { println!( "{}", Home { title: "Example Domain" } ) } ``` ### Output ```html Example Domain

Example Domain

This domain is for use in illustrative examples in documents. You may use this domain in literature without prior coordination or asking for permission.

More information...

(c) 2020
``` ### Output (manually prettified) ```html Example Domain

Example Domain

This domain is for use in illustrative examples in documents. You may use this domain in literature without prior coordination or asking for permission.

More information...

(c) 2020
``` markup-proc-macro-0.13.1/src/ast.rs000064400000000000000000000026420072674642500152250ustar 00000000000000#[derive(Debug)] pub struct Struct { pub name: syn::Ident, pub attributes: Vec, pub generics: syn::Generics, pub where_clause: Option, pub fields: Vec, pub children: Vec, pub size_hint: usize, } #[derive(Debug)] pub struct Template { pub children: Vec, } #[derive(Debug)] pub enum Node { Element(Element), If(If), For(For), Expr(syn::Expr), Stmt(syn::Stmt), Match(Match), } #[derive(Debug)] pub struct Element { pub name: syn::Expr, pub id: Option, pub classes: Vec, pub attributes: Vec, pub children: Vec, pub close: bool, } #[derive(Debug)] pub struct If { pub clauses: Vec, pub default: Option>, } #[derive(Debug)] pub struct IfClause { pub test: IfClauseTest, pub consequent: Vec, } #[derive(Debug)] pub enum IfClauseTest { Expr(syn::Expr), Let(syn::Pat, syn::Expr), } #[derive(Debug)] pub struct Match { pub expr: syn::Expr, pub clauses: Vec, } #[derive(Debug)] pub struct MatchClause { pub pat: syn::Pat, pub guard: Option, pub consequent: Vec, } #[derive(Debug)] pub struct For { pub pat: syn::Pat, pub expr: syn::Expr, pub body: Vec, } #[derive(Debug)] pub struct Attribute { pub name: syn::Expr, pub value: syn::Expr, } markup-proc-macro-0.13.1/src/escape.rs000064400000000000000000000046600072674642500157000ustar 00000000000000pub fn escape(str: &str, writer: &mut impl std::fmt::Write) -> std::fmt::Result { let mut last = 0; for (index, byte) in str.bytes().enumerate() { macro_rules! go { ($expr:expr) => {{ writer.write_str(&str[last..index])?; writer.write_str($expr)?; last = index + 1; }}; } match byte { b'&' => go!("&"), b'<' => go!("<"), b'>' => go!(">"), b'"' => go!("""), _ => {} } } writer.write_str(&str[last..]) } pub struct Escape<'a, W>(pub &'a mut W); impl std::fmt::Write for Escape<'_, W> { #[inline] fn write_str(&mut self, s: &str) -> std::fmt::Result { escape(s, &mut self.0) } } #[test] fn test() { t("", ""); t("<", "<"); t("a<", "a<"); t("b", "a<>b"); t("<>", "<>"); t("≤", "≤"); t("a≤", "a≤"); t("≤b", "≤b"); t("a≤b", "a≤b"); t("a≤≥b", "a≤≥b"); t("≤≥", "≤≥"); t( r#"foo &<>" bar&barbar"bar baz&&<>""baz"#, r#"foo &<>" bar&bar<bar>bar"bar baz&&<<baz>>""baz"#, ); fn t(input: &str, output: &str) { let mut string = String::new(); escape(input, &mut string).unwrap(); assert_eq!(string, output); } } #[test] fn test_arguments() { use std::fmt::Write; t("", """"); t("<", ""<""); t("a<", ""a<""); t("b", ""a<>b""); t("<>", ""<>""); t("≤", ""≤""); t("a≤", ""a≤""); t("≤b", ""≤b""); t("a≤b", ""a≤b""); t("a≤≥b", ""a≤≥b""); t("≤≥", ""≤≥""); t( r#"foo &<>" bar&barbar"bar baz&&<>""baz"#, r#""foo &<>\" bar&bar<bar>bar\"bar baz&&<<baz>>\"\"baz""#, ); t('<', "'<'"); fn t(input: impl std::fmt::Debug, output: &str) { let mut string = String::new(); write!(Escape(&mut string), "{}", format_args!("{:?}", input)).unwrap(); assert_eq!(string, output); } } markup-proc-macro-0.13.1/src/generate.rs000064400000000000000000000202270072674642500162270ustar 00000000000000use crate::ast::{ Attribute, Element, For, If, IfClause, IfClauseTest, Match, MatchClause, Node, Struct, Template, }; use proc_macro2::TokenStream; use proc_macro2::TokenTree; use quote::{quote, ToTokens}; impl ToTokens for Struct { fn to_tokens(&self, tokens: &mut TokenStream) { let Struct { name, attributes, generics, where_clause, fields, children, size_hint, } = self; let mut stream = Stream::default(); children.generate(&mut stream); let built = stream.finish(); let (impl_generics, ty_generics, _) = generics.split_for_impl(); let mut struct_fields = TokenStream::new(); let mut splat_fields = TokenStream::new(); for field in fields { let attrs = &field.attrs; let name = field.ident.as_ref().unwrap(); let ty = &field.ty; struct_fields.extend(quote! { #(#attrs)* pub #name: #ty, }); splat_fields.extend(quote! { #name, }); } tokens.extend(quote! { #(#attributes)* pub struct #name #generics #where_clause { #struct_fields } impl #impl_generics #name #ty_generics #where_clause { #[inline] pub fn to_string(&self) -> String { let mut string = String::with_capacity(#size_hint); // Ignoring the result because writing to a String can't fail. let _ = ::markup::Render::render(self, &mut string); string } } impl #impl_generics ::markup::Render for #name #ty_generics #where_clause { fn render(&self, __writer: &mut impl std::fmt::Write) -> std::fmt::Result { let #name { #splat_fields } = self; #built Ok(()) } } impl #impl_generics std::fmt::Display for #name #ty_generics #where_clause { #[inline] fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { ::markup::Render::render(self, fmt) } } }) } } impl ToTokens for Template { fn to_tokens(&self, tokens: &mut TokenStream) { let Self { children } = self; let mut stream = Stream::default(); children.generate(&mut stream); let built = stream.finish(); tokens.extend(quote! {{ ::markup::new(move |mut __writer| { let mut __writer = &mut __writer; #built Ok(()) }) }}) } } trait Generate { fn generate(&self, stream: &mut Stream); } impl Generate for Vec { fn generate(&self, stream: &mut Stream) { for x in self { x.generate(stream) } } } impl Generate for Node { fn generate(&self, stream: &mut Stream) { match self { Node::Element(element) => element.generate(stream), Node::If(if_) => if_.generate(stream), Node::Match(match_) => match_.generate(stream), Node::For(for_) => for_.generate(stream), Node::Expr(expr) => stream.expr(expr), Node::Stmt(stmt) => stream.extend(stmt.into_token_stream()), } } } impl Generate for Element { fn generate(&self, stream: &mut Stream) { let Element { name, id, classes, attributes, children, close, } = self; stream.raw("<"); stream.expr(name); if let Some(id) = id { stream.raw(" id=\""); stream.expr(id); stream.raw("\""); } if !classes.is_empty() { stream.raw(" class=\""); let mut first = true; for class in classes { if first { first = false; } else { stream.raw(" "); } stream.expr(class); } stream.raw("\""); } for Attribute { name, value } in attributes { stream.extend(quote!(let __value = #value;)); stream.extend(quote!(if ::markup::Render::is_none(&__value) || ::markup::Render::is_false(&__value))); stream.braced(|_| {}); stream.extend(quote!(else if ::markup::Render::is_true(&__value))); stream.braced(|stream| { stream.raw(" "); stream.expr(name); }); stream.extend(quote!(else)); stream.braced(|stream| { stream.raw(" "); stream.expr(name); stream.raw("=\""); stream.expr(&syn::parse_quote!(__value)); stream.raw("\""); }); } stream.raw(">"); children.generate(stream); if *close { stream.raw(""); } } } impl Generate for If { fn generate(&self, stream: &mut Stream) { let mut first = true; for clause in &self.clauses { let IfClause { test, consequent } = clause; if first { first = false; } else { stream.extend(quote!(else)); } match test { IfClauseTest::Expr(expr) => stream.extend(quote!(if #expr)), IfClauseTest::Let(pattern, expr) => stream.extend(quote!(if let #pattern = #expr)), } stream.braced(|stream| { consequent.generate(stream); }); } if let Some(default) = &self.default { stream.extend(quote!(else)); stream.braced(|stream| default.generate(stream)) } } } impl Generate for Match { fn generate(&self, stream: &mut Stream) { let Match { expr, clauses } = &*self; stream.extend(quote!(match #expr)); stream.braced(|stream| { for clause in clauses { let MatchClause { pat, guard, consequent, } = clause; stream.extend(quote!(#pat)); if let Some(guard) = guard { stream.extend(quote!(if #guard)); } stream.extend(quote!(=>)); stream.braced(|stream| { consequent.generate(stream); }) } }); } } impl Generate for For { fn generate(&self, stream: &mut Stream) { let For { pat, expr, body } = self; stream.extend(quote!(for #pat in #expr)); stream.braced(|stream| body.generate(stream)) } } #[derive(Default)] struct Stream { stream: TokenStream, buffer: String, } impl Stream { fn raw(&mut self, str: &str) { self.buffer.push_str(str); } fn escaped(&mut self, str: &str) { let mut string = String::new(); crate::escape::escape(str, &mut string).unwrap(); self.buffer.push_str(&string); } fn expr(&mut self, expr: &syn::Expr) { match expr { syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(lit_str), .. }) => self.escaped(&lit_str.value()), _ => self.extend(quote!(::markup::Render::render(&(#expr), __writer)?;)), } } fn extend>(&mut self, iter: Iter) { if !self.buffer.is_empty() { let buffer = &self.buffer; self.stream.extend(quote! { __writer.write_str(#buffer)?; }); self.buffer.clear(); } self.stream.extend(iter); } fn braced(&mut self, f: impl Fn(&mut Stream)) { let mut stream = Stream::default(); f(&mut stream); let stream = stream.finish(); self.stream.extend(quote!({#stream})); } fn finish(mut self) -> TokenStream { self.extend(None); self.stream } } markup-proc-macro-0.13.1/src/lib.rs000064400000000000000000000007000072674642500151750ustar 00000000000000mod ast; mod escape; mod generate; mod parse; #[proc_macro] pub fn define(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream { let structs = syn::parse_macro_input!(tokens as parse::Many).0; quote::quote!( #(#structs)* ).into() } #[proc_macro] pub fn new(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream { let new = syn::parse_macro_input!(tokens as ast::Template); quote::quote!( #new ).into() } markup-proc-macro-0.13.1/src/parse.rs000064400000000000000000000266570072674642500155640ustar 00000000000000use crate::ast::{ Attribute, Element, For, If, IfClause, IfClauseTest, Match, MatchClause, Node, Struct, Template, }; use syn::parse::{Parse, ParseStream, Result}; use syn::punctuated::Punctuated; impl Parse for Struct { fn parse(input: ParseStream) -> Result { let start_input_len = input.to_string().len(); let attributes = input.call(syn::Attribute::parse_outer)?; let name = input.parse()?; let generics = input.parse()?; let fields = { if input.peek(syn::token::Paren) { let fields; syn::parenthesized!(fields in input); Punctuated::::parse_terminated_with( &fields, syn::Field::parse_named, )? .into_pairs() .map(|pair| pair.into_value()) .collect() } else { Vec::new() } }; let where_clause = if input.peek(syn::token::Where) { Some(input.parse()?) } else { None }; let mut children = Vec::new(); let inner; syn::braced!(inner in input); while !inner.is_empty() { children.push(inner.parse()?); } // We use the length of the tokens that define this template as a rough estimate of the // number of bytes the output of this template will occupy. // Lifted from Maud [1]. // [1]: https://github.com/lfairy/maud/blob/13a5cfcaa31b3f6e2deb015ea49ef87d285cef7c/maud_macros/src/lib.rs#L38-L40 let size_hint = start_input_len - input.to_string().len(); Ok(Struct { name, attributes, generics, where_clause, fields, children, size_hint, }) } } impl Parse for Template { fn parse(input: ParseStream) -> Result { let children = input.parse::>()?.0; Ok(Self { children }) } } impl Parse for Node { fn parse(input: ParseStream) -> Result { let lookahead = input.lookahead1(); if lookahead.peek(syn::Ident) || lookahead.peek(syn::Token![#]) || lookahead.peek(syn::Token![.]) || lookahead.peek(syn::Token![$]) { Ok(Node::Element(input.parse()?)) } else if lookahead.peek(syn::Token![@]) { let _: syn::Token![@] = input.parse()?; let lookahead = input.lookahead1(); if lookahead.peek(syn::token::If) { let _: syn::token::If = input.parse()?; Ok(Node::If(input.parse()?)) } else if lookahead.peek(syn::token::For) { let _: syn::token::For = input.parse()?; Ok(Node::For(input.parse()?)) } else if lookahead.peek(syn::token::Match) { let _: syn::token::Match = input.parse()?; Ok(Node::Match(input.parse()?)) } else if lookahead.peek(syn::Lit) || lookahead.peek(syn::Ident) || lookahead.peek(syn::token::Brace) || lookahead.peek(syn::token::Crate) { Ok(Node::Expr(input.parse()?)) } else if input.fork().parse::().is_ok() { Ok(Node::Stmt(input.parse()?)) } else { Err(lookahead.error()) } } else if lookahead.peek(syn::Lit) { let lit: syn::Lit = input.parse()?; Ok(Node::Expr(syn::parse_quote!(#lit))) } else if lookahead.peek(syn::token::Brace) { Ok(Node::Expr(input.parse()?)) } else { Err(lookahead.error()) } } } impl Parse for Element { fn parse(input: ParseStream) -> Result { let (name, mut id, mut classes) = { let lookahead = input.lookahead1(); if lookahead.peek(syn::Token![$]) { let _: syn::Token![$] = input.parse()?; let name = identifier_or_string_literal_or_expression(input)?; (name, None, Vec::new()) } else if lookahead.peek(syn::Ident) { let name = identifier_or_string_literal_or_expression(input)?; (name, None, Vec::new()) } else if lookahead.peek(syn::Token![#]) { let _: syn::Token![#] = input.parse()?; ( syn::parse_quote!("div"), Some(identifier_or_string_literal_or_expression(input)?), Vec::new(), ) } else if lookahead.peek(syn::Token![.]) { let _: syn::Token![.] = input.parse()?; ( syn::parse_quote!("div"), None, vec![identifier_or_string_literal_or_expression(input)?], ) } else { return Err(lookahead.error()); } }; loop { if input.peek(syn::Token![#]) { let _: syn::Token![#] = input.parse()?; id = Some(identifier_or_string_literal_or_expression(input)?); } else if input.peek(syn::Token![.]) { let _: syn::Token![.] = input.parse()?; classes.push(identifier_or_string_literal_or_expression(input)?); } else { break; } } let attributes = { if input.peek(syn::token::Bracket) { let attributes; syn::bracketed!(attributes in input); Punctuated::::parse_terminated(&attributes)? .into_pairs() .map(|a| a.into_value()) .collect() } else { Vec::new() } }; let (children, close) = { let lookahead = input.lookahead1(); if lookahead.peek(syn::token::Semi) { let _: syn::Token![;] = input.parse()?; (Vec::new(), false) } else if lookahead.peek(syn::token::Brace) { let children; syn::braced!(children in input); (children.parse::>()?.0, true) } else if lookahead.peek(syn::LitStr) { let string = input.parse::()?.value(); (vec![Node::Expr(syn::parse_quote!(#string))], true) } else { return Err(lookahead.error()); } }; Ok(Element { name, id, classes, attributes, children, close, }) } } impl Parse for If { fn parse(input: ParseStream) -> Result { let mut clauses = vec![input.parse()?]; let mut default = None; loop { if input.peek(syn::token::Else) { let _: syn::token::Else = input.parse()?; if input.peek(syn::token::If) { let _: syn::token::If = input.parse()?; clauses.push(input.parse()?); } else { default = { let default; syn::braced!(default in input); Some(default.parse::>()?.0) }; break; } } else { break; } } Ok(If { clauses, default }) } } impl Parse for IfClause { fn parse(input: ParseStream) -> Result { let test = input.parse()?; let consequent = { let consequent; syn::braced!(consequent in input); consequent.parse::>()?.0 }; Ok(IfClause { test, consequent }) } } impl Parse for IfClauseTest { fn parse(input: ParseStream) -> Result { if input.peek(syn::token::Let) { let _: syn::token::Let = input.parse()?; let pattern = input.parse()?; let _: syn::Token![=] = input.parse()?; let expr = syn::Expr::parse_without_eager_brace(input)?; Ok(IfClauseTest::Let(pattern, expr)) } else { Ok(IfClauseTest::Expr(syn::Expr::parse_without_eager_brace( input, )?)) } } } impl Parse for Match { fn parse(input: ParseStream) -> Result { let expr = syn::Expr::parse_without_eager_brace(input)?; let inner; syn::braced!(inner in input); let clauses = inner.parse::>()?.0; Ok(Match { expr, clauses }) } } impl Parse for MatchClause { fn parse(input: ParseStream) -> Result { let leading_vert: Option = input.parse()?; let pat: syn::Pat = input.parse()?; let pat = if leading_vert.is_some() || input.peek(syn::Token![|]) { let mut cases = Punctuated::new(); cases.push_value(pat); while input.peek(syn::Token![|]) { let punct = input.parse()?; cases.push_punct(punct); let pat: syn::Pat = input.parse()?; cases.push_value(pat); } syn::Pat::Or(syn::PatOr { attrs: Vec::new(), leading_vert, cases, }) } else { pat }; let guard = if input.peek(syn::Token![if]) { let _: syn::Token![if] = input.parse()?; Some(input.parse()?) } else { None }; let _: syn::Token![=>] = input.parse()?; let inner; syn::braced!(inner in input); let consequent = inner.parse::>()?.0; Ok(MatchClause { pat, guard, consequent, }) } } impl Parse for For { fn parse(input: ParseStream) -> Result { let pat = input.parse()?; let _: syn::token::In = input.parse()?; let expr = syn::Expr::parse_without_eager_brace(input)?; let body; syn::braced!(body in input); let body = body.parse::>()?.0; Ok(For { pat, expr, body }) } } impl Parse for Attribute { fn parse(input: ParseStream) -> Result { let name = identifier_or_string_literal_or_expression(input)?; let value = if input.peek(syn::Token![=]) { let _: syn::Token![=] = input.parse()?; input.parse()? } else { syn::parse_quote!(true) }; Ok(Attribute { name, value }) } } #[derive(Debug)] pub struct Many

(pub Vec

); impl Parse for Many

{ fn parse(input: ParseStream) -> Result { let mut items = Vec::new(); while !input.is_empty() { items.push(input.parse()?); } Ok(Many(items)) } } fn identifier_or_string_literal_or_expression(input: ParseStream) -> Result { use syn::ext::IdentExt; let lookahead = input.lookahead1(); if lookahead.peek(syn::Ident::peek_any) { let ident = syn::Ident::parse_any(input)?; let string = ident.unraw().to_string(); Ok(syn::parse_quote!(#string)) } else if lookahead.peek(syn::LitStr) { let string = input.parse::()?.value(); Ok(syn::parse_quote!(#string)) } else if lookahead.peek(syn::token::Brace) { let inner; syn::braced!(inner in input); Ok(inner.parse()?) } else { Err(lookahead.error()) } }