markup-0.13.1/.cargo_vcs_info.json0000644000000001440000000000100124140ustar { "git": { "sha1": "674c842b43359f6fbd8d9d6376a99c5da7aa3577" }, "path_in_vcs": "markup" }markup-0.13.1/Cargo.lock0000644000000030160000000000100103700ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "itoa" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" [[package]] name = "markup" version = "0.13.1" dependencies = [ "itoa", "markup-proc-macro", ] [[package]] name = "markup-proc-macro" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a927f0e237dcbdd8c1a8ab03c4e1e8b1999804c448ebf06ff3b5512506c8150" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "proc-macro2" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" dependencies = [ "proc-macro2", ] [[package]] name = "syn" version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "unicode-ident" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" markup-0.13.1/Cargo.toml0000644000000020500000000000100104100ustar # 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" 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" readme = "README.md" keywords = [ "template", "html", "markup", "pug", "haml", ] categories = ["template-engine"] license = "MIT/Apache-2.0" repository = "https://github.com/utkarshkukreti/markup.rs" [dependencies.itoa] version = "1.0.1" [dependencies.markup-proc-macro] version = "0.13.1" markup-0.13.1/Cargo.toml.orig000064400000000000000000000011310072674642500141200ustar 00000000000000[package] name = "markup" 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" readme = "README.md" keywords = ["template", "html", "markup", "pug", "haml"] categories = ["template-engine"] [dependencies] markup-proc-macro = { path = "../markup-proc-macro", version = "0.13.1" } itoa = { version = "1.0.1" } markup-0.13.1/README.md000064400000000000000000000076220072674642500125230ustar 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-0.13.1/examples/attributes.rs000064400000000000000000000010140072674642500156030ustar 00000000000000markup::define! { #[derive(Clone)] Page( /// The page title title: &'static str, /// The page body body: Body, ) { html { head { title { @title } } body { @body } } } } fn main() { let page = Page { title: "Hello", body: markup::new! { p { "Hello!" } }, }; println!("{}", page); } markup-0.13.1/examples/blog.rs000064400000000000000000000023510072674642500143450ustar 00000000000000pub struct User { name: String, } pub struct Post { id: u32, title: String, } markup::define! { Page<'a>(user: &'a User, posts: &'a [Post]) { @markup::doctype() html { head { title { "Hello " @user.name } } body { #main.container { @for post in *posts { div#{format!("post-{}", post.id)}["data-id" = post.id] { .title { @post.title } } } } @Footer { name: &user.name, year: 2020 } } } } Footer<'a>(name: &'a str, year: u32) { "(c) " @year " " @name } } fn main() { let user = User { name: "Ferris".into(), }; let posts = [ Post { id: 1, title: "Road to Rust 1.0".into(), }, Post { id: 2, title: "Stability as a Deliverable".into(), }, Post { id: 3, title: "Cargo: Rust's community crate host".into(), }, ]; println!( "{}", Page { user: &user, posts: &posts } ) } markup-0.13.1/examples/dynamic.rs000064400000000000000000000011670072674642500150520ustar 00000000000000markup::define! { Tabs<'a>(tabs: &'a [Tab<'a>]) { @for tab in tabs.iter() { @tab } } Tab<'a>( title: &'a str, body: markup::DynRender<'a>, ) { h1 { @title } div { @body } } } fn main() { let tabs = [ Tab { title: "Home", body: markup::new! { p { "This is the home page." } }, }, Tab { title: "About", body: markup::new! { p { "This is the about page." } }, }, ]; println!("{}", Tabs { tabs: &tabs }) } markup-0.13.1/examples/fortunes.rs000064400000000000000000000055100072674642500152670ustar 00000000000000pub struct Fortune { pub id: i32, pub message: &'static str, } markup::define! { Define<'a>(fortunes: &'a [Fortune]) { @let fortunes = *fortunes; {markup::doctype()} html { head { title { "Fortunes" } } body { table { tr { th { "id" } th { "message" } } @for item in fortunes { tr { td { @item.id } td { @item.message } } } } } } } } pub fn new(fortunes: &[Fortune]) -> impl std::fmt::Display + '_ { markup::new! { {markup::doctype()} html { head { title { "Fortunes" } } body { table { tr { th { "id" } th { "message" } } @for item in fortunes { tr { td { @item.id } td { @item.message } } } } } } } } pub static FORTUNES: &[Fortune] = &[ Fortune { id: 1, message: "fortune: No such file or directory", }, Fortune { id: 2, message: "A computer scientist is someone who fixes things that aren\'t broken.", }, Fortune { id: 3, message: "After enough decimal places, nobody gives a damn.", }, Fortune { id: 4, message: "A bad random number generator: 1, 1, 1, 1, 1, 4.33e+67, 1, 1, 1", }, Fortune { id: 5, message: "A computer program does what you tell it to do, not what you want it to do.", }, Fortune { id: 6, message: "Emacs is a nice operating system, but I prefer UNIX. — Tom Christaensen", }, Fortune { id: 7, message: "Any program that runs right is obsolete.", }, Fortune { id: 8, message: "A list is only as strong as its weakest link. — Donald Knuth", }, Fortune { id: 9, message: "Feature: A bug with seniority.", }, Fortune { id: 10, message: "Computers make very fast, very accurate mistakes.", }, Fortune { id: 11, message: "", }, Fortune { id: 12, message: "フレームワークのベンチマーク", }, ]; #[allow(dead_code)] pub fn main() { println!("{}", Define { fortunes: FORTUNES }); } #[test] fn t() { let define = Define { fortunes: FORTUNES }.to_string(); let new = new(FORTUNES).to_string(); assert_eq!(define.len(), 1153); assert_eq!(define, new); } markup-0.13.1/examples/layout.rs000064400000000000000000000014250072674642500147400ustar 00000000000000markup::define! { Layout( head: Head, body: Body, ) { @markup::doctype() html { head { @head } body { @body } } } } fn home() -> String { Layout { head: markup::new! { title { "Home" } }, body: markup::new! { "This is the home page." }, } .to_string() } fn contact() -> String { Layout { head: markup::new! { title { "Contact" } }, body: markup::new! { "This is the contact page." }, } .to_string() } fn main() { println!("{}", home()); println!("{}", contact()); } markup-0.13.1/examples/quick.rs000064400000000000000000000023100072674642500145310ustar 00000000000000markup::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" } ) } markup-0.13.1/src/escape.rs000064400000000000000000000046600072674642500136400ustar 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-0.13.1/src/lib.rs000064400000000000000000000117070072674642500131460ustar 00000000000000use std::fmt::Write; pub use markup_proc_macro::{define, new}; mod escape; pub trait Render { fn render(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result; #[doc(hidden)] #[inline] fn is_none(&self) -> bool { false } #[doc(hidden)] #[inline] fn is_true(&self) -> bool { false } #[doc(hidden)] #[inline] fn is_false(&self) -> bool { false } } impl<'a, T: Render + ?Sized> Render for &'a T { #[inline] fn render(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result { T::render(self, writer) } #[doc(hidden)] #[inline] fn is_none(&self) -> bool { T::is_none(self) } #[doc(hidden)] #[inline] fn is_true(&self) -> bool { T::is_true(self) } #[doc(hidden)] #[inline] fn is_false(&self) -> bool { T::is_false(self) } } impl Render for Box { #[inline] fn render(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result { T::render(self, writer) } #[doc(hidden)] #[inline] fn is_none(&self) -> bool { T::is_none(self) } #[doc(hidden)] #[inline] fn is_true(&self) -> bool { T::is_true(self) } #[doc(hidden)] #[inline] fn is_false(&self) -> bool { T::is_false(self) } } impl Render for bool { #[inline] fn render(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result { write!(writer, "{}", self) } #[doc(hidden)] #[inline] fn is_true(&self) -> bool { *self } #[doc(hidden)] #[inline] fn is_false(&self) -> bool { !self } } impl Render for Option { #[inline] fn render(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result { match self { Some(t) => t.render(writer), None => Ok(()), } } #[doc(hidden)] #[inline] fn is_none(&self) -> bool { self.is_none() } } struct Raw(T); impl Render for Raw { #[inline] fn render(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result { write!(writer, "{}", self.0) } } #[inline] pub fn raw(value: impl std::fmt::Display) -> impl Render { Raw(value) } macro_rules! tfor { (for $ty:ident in [$($typ:ident),*] $tt:tt) => { $( const _: () = { type $ty = $typ; tfor! { @extract $tt } }; )* }; (@extract { $($tt:tt)* }) => { $($tt)* }; } tfor! { for Ty in [char, f32, f64] { impl Render for Ty { #[inline] fn render(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result { write!(writer, "{}", self) } } } } tfor! { for Ty in [u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize] { impl Render for Ty { #[inline] fn render(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result { let mut buffer = itoa::Buffer::new(); let str = buffer.format(*self); writer.write_str(str) } } } } impl Render for str { #[inline] fn render(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result { escape::escape(self, writer) } } impl Render for String { #[inline] fn render(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result { self.as_str().render(writer) } } impl Render for std::fmt::Arguments<'_> { #[inline] fn render(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result { escape::Escape(writer).write_fmt(*self) } } macro_rules! tuple_impl { ($($ident:ident)+) => { impl<$($ident: Render,)+> Render for ($($ident,)+) { #[allow(non_snake_case)] #[inline] fn render(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result { let ($(ref $ident,)+) = *self; $($ident.render(writer)?;)+ Ok(()) } } } } tuple_impl! { A } tuple_impl! { A B } tuple_impl! { A B C } tuple_impl! { A B C D } tuple_impl! { A B C D E } tuple_impl! { A B C D E F } tuple_impl! { A B C D E F G } tuple_impl! { A B C D E F G H } tuple_impl! { A B C D E F G H I } tuple_impl! { A B C D E F G H I J } pub struct DynRender<'a> { f: Box std::fmt::Result + 'a>, } pub fn new<'a, F>(f: F) -> DynRender<'a> where F: Fn(&mut dyn std::fmt::Write) -> std::fmt::Result + 'a, { DynRender { f: Box::new(f) } } impl<'a> Render for DynRender<'a> { #[inline] fn render(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result { (self.f)(writer) } } impl<'a> std::fmt::Display for DynRender<'a> { #[inline] fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { Render::render(self, fmt) } } #[inline] pub fn doctype() -> impl Render { raw("") } markup-0.13.1/tests/tests.rs000064400000000000000000000156130072674642500141150ustar 00000000000000macro_rules! t { ($name:ident, {$($define:tt)+}, $($eval:expr => $expect:expr,)+) => { #[test] fn $name() { markup::define! { $($define)+ } $( assert_eq!($eval.to_string(), $expect); )+ } }; } t! { t1, { A { } B { 1 } C { 2 3 } D { 4 "5" 6 } E { 7 " < " 8 } F { 9 " ≤ " 10 } }, A {} => "", B {} => "1", C {} => "23", D {} => "456", E {} => "7 < 8", F {} => "9 ≤ 10", } t! { t2, { A { {1 + 2} {'π'} {if true { Some(3) } else { Some(4) }} {if false { Some(5) } else { Some(6) }} {"<>"} {true} {false} } }, A {} => "3π36<>truefalse", } t! { t3, { A { div {} br; } }, A {} => "

", } t! { t4, { A { 1 .foo { 2 .bar.baz { 3 #quux { 4 } 5 } 6 } 7 } }, A {} => r#"1
2
3
4
5
6
7"#, } t! { t5, { A { foo#bar { baz.quux#"foo".{1}.{2 + 3} {} bar#{4}.{5 - 6} { 7 } } } B { foo "bar" foo#bar "baz" foo.bar[baz = true] "quux" foo.bar[baz = true]; "quux" } C { foo[fn = true, async = false, mod = true, r#move = true] {} } }, A {} => r#"7"#, B {} => r#"barbazquuxquux"#, C {} => r#""#, } t! { t6, { A { div [ a = 1, b = "2", c = true, c2 = &true, c3 = &&true, c4, d = false, d2 = &false, d3 = &&false, "e-f" = 3, {"g".to_string() + "-h"} = 4, i = None::, i2 = &None::, i3 = &&None::, j = Some(5), j2 = &Some(5), j3 = &&Some(5), h = (6, ("7", String::from("8"), true, Some(false), None::)), ] {} br[k = 6]; } }, A {} => r#"

"#, } t! { t7, { A(foo: u32, bar: i32, baz: String) { {foo} {bar} {*foo as i32 + bar} {baz} } }, A { foo: 1, bar: -2, baz: "3".into() } => "1-2-13", } t! { t8, { A<'a, T: std::fmt::Debug, U, V: markup::Render>( arg: T, arg2: U, str: &'a str, box_str: Box, v: V, ) where U: std::fmt::Display { div { {format!("{:?}", arg)} {format!("{}", arg2)} {str} {box_str} {v} } } }, A { arg: (1, 2), arg2: "arg2", str: "str", box_str: "box_str".into(), v: markup::new!(foo {}) } => "
(1, 2)arg2strbox_str
", } t! { t9, { A String>(foo: i32, bar: Bar, baz: B) { @foo @bar(*foo + 1) @baz.foo @format!("{} {} {}", foo, bar(*foo + 2), baz.foo + 3) @B { foo: foo + 4 } } B(foo: i32) { @foo @foo @foo } }, A { foo: 1, bar: |x| (x + 2).to_string(), baz: B { foo: 3 }, } => "1431 5 6555", } t! { t10, { A { @fn foo() -> i32 { 1 } @mod bar { pub fn baz() -> i32 { 2 } } @const QUUX: i32 = 3; @static FOUR: i32 = 4; @foo() @bar::baz() @QUUX @FOUR @#[derive(Debug)] struct Int(i32); @let Int(five) = Int(5); @five @{6} @{6 + 1} @{let eight = Int(8); eight.0} @crate::add(10, -1) } }, A {} => "123456789", } fn add(a: i32, b: i32) -> i32 { a + b } t! { t11, { A { @let foos = [None, Some(1), Some(2), Some(3)]; @for (index, foo) in foos.iter().enumerate() { "index=" @index " :: " @B { foo: *foo } ";" @C { foo: *foo } ";" @D { foo: *foo } ";" @E { foo: *foo } "\n" } } B(foo: Option) { @if foo.is_some() { @let foo = foo.unwrap(); @if foo == 1 { "ONE" } else if foo == 2 { "TWO" } else { "OTHER" } } else { "NONE" } } C(foo: Option) { @if *foo == Some(1) { "ONE" } else if *foo == Some(2) { "TWO" } else if foo.is_some() { "OTHER" } else { "NONE" } } D(foo: Option) { @if let Some(1) = foo { "ONE" } else if let Some(2) = foo { "TWO" } else if let Some(_) = foo { "OTHER" } else { "NONE" } } E(foo: Option) { @match foo { Some(1) => { "ONE" } Some(n) if *n == 2 => { "TWO" } Some(_) => { "OTHER" } None => { "NONE" } } } }, A {} => "\ index=0 :: NONE;NONE;NONE;NONE index=1 :: ONE;ONE;ONE;ONE index=2 :: TWO;TWO;TWO;TWO index=3 :: OTHER;OTHER;OTHER;OTHER ", } t! { t12, { A(b: B) { @b @format!("{:?}", b) } #[derive(Clone, Debug)] B(foo: i32, bar: char) { @foo @bar } }, A { b: B { foo: 1, bar: '?' }.clone() } => "1?B { foo: 1, bar: '?' }", } t! { t13, { A() { $"foo-bar"; $"foo-bar" {} $"foo-bar"[baz = "quux"] {} } B(name: &'static str) { ${name}; ${name} {} } }, A {} => r#""#, B { name: "foo-bar" } => r#""#, }