#markup #proc #macro #macros #template #engine

macro markup-proc-macro

A blazing fast, type-safe template engine for Rust

8 releases

✓ Uses Rust 2018 edition

new 0.2.1 Apr 11, 2019
0.2.0 Apr 11, 2019
0.1.5 Apr 4, 2019
0.1.1 Dec 3, 2018

46 downloads per month
Used in 1 crate

MIT/Apache

28KB
567 lines

markup.rs

A blazing fast, type-safe template engine for Rust.

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.

Quick Start

Add the markup crate to your dependencies:

[dependencies]
markup = "0.1"

Define your template using the markup::define! macro:

markup::define! {
    Hello<'a>(name: &'a str) {
        {markup::Doctype}
        html {
            head {
                title { "Hello " {name} }
            }
            body {
                div#main.container {
                    {Greeting { name: "Everyone!" }}
                    br;
                    {Greeting { name: name }}
                }
            }
        }
    }
    Greeting<'a>(name: &'a str) {
        p.greeting.{if *name == "Ferris" { Some("ferris" ) } else { None }} {
            "Hello "
            span.name {
                @if *name == "Ferris" {
                    "FERRIS"
                    @for _ in 0..3 {
                        "!"
                    }
                } else {
                    {name}
                }
            }
        }
    }
}

Render your template by either:

  1. Writing it to any instance of std::io::Write:

    write!(writer, "{}", Hello { name: "Ferris" });
    
  2. Converting it to a string and using it however you like:

    let string = Hello { name: "Ferris" }.to_string();
    

The above template compiles to:

pub struct Hello<'a> {
    pub name: &'a str,
}

impl<'a> std::fmt::Display for Hello<'a> {
    fn fmt(&self, __writer: &mut std::fmt::Formatter) -> std::fmt::Result {
        use std::fmt::Display;
        let Hello { name } = self;
        markup::Render::render(&(markup::Doctype), __writer)?;
        __writer.write_str("<html><head><title>Hello ")?;
        markup::Render::render(&(name), __writer)?;
        __writer.write_str("</title></head><body><div id=\"main\" class=\"container\">")?;
        markup::Render::render(&(Greeting { name: "Everyone!" }), __writer)?;
        __writer.write_str("<br>")?;
        markup::Render::render(&(Greeting { name: name }), __writer)?;
        __writer.write_str("</div></body></html>")?;
        Ok(())
    }
}

pub struct Greeting<'a> {
    pub name: &'a str,
}

impl<'a> std::fmt::Display for Greeting<'a> {
    fn fmt(&self, __writer: &mut std::fmt::Formatter) -> std::fmt::Result {
        use std::fmt::Display;
        let Greeting { name } = self;
        __writer.write_str("<p class=\"greeting ")?;
        markup::Render::render(
            &(if *name == "Ferris" {
                Some("ferris")
            } else {
                None
            }),
            __writer,
        )?;
        __writer.write_str("\">Hello <span class=\"name\">")?;
        if *name == "Ferris" {
            __writer.write_str("FERRIS!")?;
            for _ in 0..3 {
                __writer.write_str("!")?;
            }
        } else {
            markup::Render::render(&(name), __writer)?;
        }
        __writer.write_str("</span></p>")?;
        Ok(())
    }
}

Rendering the template produces (manually prettified):

<!DOCTYPE html>
<html>
  <head>
    <title>Hello Ferris</title>
  </head>
  <body>
    <div id="main" class="container">
      <p class="greeting ">Hello <span class="name">Everyone!</span></p>
      <br>
      <p class="greeting ferris">Hello <span class="name">FERRIS!!!</span></p>
    </div>
  </body>
</html>

Syntax

Define

markup::define! {
    First {
      "First!"
    }
    Second {
      "Second!"
    }
}
println!("{}", First);
println!("{}", Second.to_string());
First!
Second!

Literal Strings and Expressions

markup::define! {
    Hello {
        "Hello,"
        " "
        "world!\n"
        {1 + 2}
        {format!("{}{}", 3, 4)}
        {if true { Some(5) } else { None }}
        {if false { Some(6) } else { None }}
    }
}
println!("{}", Hello {});
Hello, world!
3345

Elements

Normal and Void

markup::define! {
    Hello {
        div {}
        br;
    }
}
println!("{}", Hello {});
<div></div><br>

id and class shorthands

markup::define! {
    Hello {
        .foo {
            .bar {}
        }
        button#go.button."button-blue" {}
        button#"go-back".{1 + 2}.{2 + 3} {}
    }
}
println!("{}", Hello {});
<div class="foo"><div class="bar"></div></div><button id="go" class="button button-blue"></button><button id="go-back" class="3 5"></button>

Attributes with and without values

markup::define! {
    Hello {
        div[a = 1, b = "2", c? = true, d? = false, "e-f" = 3, {"g".to_string() + "-h"} = 4, i = None::<i32>, j = Some(5)] {}
        "\n"
        br[k = 6];
        "\n"
        input[type = "text"];
    }
}
println!("{}", Hello {});
<div a="1" b="2" c e-f="3" g-h="4" j="5"></div>
<br k="6">
<input type="text">

Children

markup::define! {
    Hello {
        .foo[a = 1] {
            "One"
            {0 + 1}
        }
        div {
            "Two"
            {1 + 1}
        }
    }
}
println!("{}", Hello {});
<div class="foo" a="1">One1</div><div>Two2</div>

Disable Automatic HTML Escaping

markup::define! {
    Hello {
        "<&\">"
        {markup::Raw("<span></span>")}
    }
}
println!("{}", Hello {});
&lt;&amp;&quot;&gt;<span></span>

Arguments

markup::define! {
    Hello(foo: u32, bar: u32, string: String) {
        div {
            {foo + bar}
            {string}
        }
    }
}
println!("{}", Hello { foo: 1, bar: 2, string: String::from("hello") });
<div>3hello</div>
markup::define! {
    Hello<'a, T: std::fmt::Debug, U>(arg: T, arg2: U, str: &'a str) where U: std::fmt::Display {
        div {
            {format!("{:?}", arg)}
            {format!("{}", arg2)}
            {str}
        }
    }
}
println!("{}", Hello { arg: (1, 2), arg2: "arg2", str: "str" });
<div>(1, 2)arg2str</div>

Embed Other Templates

markup::define! {
    Add(a: u32, b: u32) {
        span { {a + b} }
    }
    Hello {
        {Add { a: 1, b: 2 }}
        {Add { a: 3, b: 4 }}
    }
}
println!("{}", Hello {});
<span>3</span><span>7</span>

If

markup::define! {
    Classify(value: i32) {
        {value}
        " is "
        @if *value < 0 {
            "negative"
        } else if *value == 0 {
            "zero"
        } else {
            "positive"
        }
        ".\n"
    }
    Main {
        {Classify { value: -42 }}
        " "
        {Classify { value: 0 }}
        " "
        {Classify { value: 42 }}
    }
}
println!("{}", Main {});
-42 is negative.
 0 is zero.
 42 is positive.

If Let

markup::define! {
    Classify(value: Option<i32>) {
        @if let Some(0) = *(value) {
            "Some(ZERO)"
        } else if let Some(value) = *(value) {
            "Some(" {value} ")"
        } else {
            "None"
        }
        "\n"
    }
    Main {
        {Classify { value: None }}
        {Classify { value: Some(0) }}
        {Classify { value: Some(1) }}
    }
}
println!("{}", Main {});
None
Some(ZERO)
Some(1)

For

markup::define! {
    Main {
        @for i in 1..5 {
            {i} " * 2 = " {i * 2} ";\n"
        }
    }
}
println!("{}", Main {});
1 * 2 = 2;
2 * 2 = 4;
3 * 2 = 6;
4 * 2 = 8;

Statements

markup::define! {
    Main {
        {let x = 1;}
        {fn add1(x: i32) -> i32 {
            x + 1
        }}
        {add1(x)}
    }
}
println!("{}", Main {});
2

Dependencies

~784KB
~18K SLoC