Popularity
2.6
Growing
Activity
6.5
Declining
50
5
4

Programming language: Rust
Tags: Web     Web Programming     HTML     Template engine    
Latest version: v0.4.1

markup.rs alternatives and similar packages

Based on the "HTML" category

Do you think we are missing an alternative of markup.rs or a related project?

Add another 'HTML' Package

README

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.4.1"

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

markup::define! {
    Hello<'a>(name: &'a str) {
        {markup::doctype()}
        html {
            head {
                title { "Hello " {name} }
            }
            body {
                #main.container {
                    {Greeting { name: "Everyone!" }}
                    br;
                    {Greeting { name: name }}
                }
            }
        }
    }
    Greeting<'a>(name: &'a str) {
        p.greeting {
            "Hello " {name} "!"
        }
    }
}

fn main() {
    println!("{}", Hello { name: "Ferris" });
}

Render your template by either:

  1. Writing it to any instance of std::io::Write:
   write!(writer, "{}", Hello { name: "Ferris" });
  1. Converting it to a string and using it however you like:
   let string = Hello { name: "Ferris" }.to_string();

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 Everyone!</p>
      <br>
      <p class="greeting">Hello Ferris!</p>
    </div>
  </body>
</html>

Note

Due to a limitation in syn, the crate this crate uses to parse templates, it is necessary to wrap identifiers which precede an opening brace to be put in parentheses.

Example:

// Wrong
@if let Some(foo) = bar {
  ...
}
@match foo {
  ...
}

// Correct
@if let Some(foo) = {bar} {
  ...
}
@match {foo} {
  ...
}

// Also works
@if let Some(foo) = *&(bar) {
  ...
}
@match *&(foo) {
  ...
}

Syntax

<!-- Syntax -->

You can define multiple templates in a define! block.

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

A template can have bare literal strings and arbitrary expressions in braces.

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!
3π345

Elements can either have children inside {} or be a void tag, ending with ;.

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

An id and multiple classes can be applied to an element using CSS like selectors. The value after # and . can be either an identifier, a literal string, or an expression inside braces.

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 can either be normal or boolean (ends with ?). The name can be an identifier, a literal string, or an expression inside braces. Boolean attributes are printed without value if true and omitted if false. The value can be an Option, where None values are omitted and Some are unwrapped.

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">

An element can have zero or more children inside braces.

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>

Automatic HTML escaping can be disabled using the markup::raw function. This function accepts any type implementing std::fmt::Display.

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

A template can accept simple arguments as well as generic arguments with where clauses. The arguments may have an optional trailing comma.

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>

Other templates can be embedded by simply putting them in braces.

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)

@match

markup::define! {
    Classify(value: Option<i32>) {
        @match *(value) {
          Some(1) | Some(2) => {
            "1"
            " or 2"
          }
          Some(n) if n == 3 => {
            {n} {n}
          }
          Some(_) => {
            "Other"
          }
          None => {
            "None"
          }
        }
        "\n"
    }
    Main {
        {Classify { value: None }}
        {Classify { value: Some(0) }}
        {Classify { value: Some(1) }}
        {Classify { value: Some(2) }}
        {Classify { value: Some(3) }}
    }
}
println!("{}", Main {});
None
Other
1 or 2
1 or 2
33

@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;

Curly braces also accept single statements and items and outputs it as-is in the generated code.

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

<!-- /Syntax -->