• Stars
    star
    350
  • Rank 120,443 (Top 3 %)
  • Language
    Rust
  • License
    Apache License 2.0
  • Created almost 6 years ago
  • Updated 7 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

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

markup.rs

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

Build Version Documentation Downloads License

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.
  • Less error-prone and terse syntax inspired by Haml, Slim, and Pug.
  • Zero unsafe code.
  • Zero runtime dependencies.
  • âš¡ Blazing fast. The fastest in this benchmark among the ones which do not use unsafe code, the second fastest overall.

Install

[dependencies]
markup = "0.13.1"

Example

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

<!DOCTYPE html><html><head><title>Example Domain</title><style>body { background: #fafbfc; }#main { padding: 2rem; }</style></head><body><header><h1>Example Domain</h1></header><div id="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><p><a href="https://www.iana.org/domains/example">More information...</a></p></div><footer>(c) 2020</footer></body></html>

Output (manually prettified)

<!DOCTYPE html>
<html>
  <head>
    <title>Example Domain</title>
    <style>
      body {
        background: #fafbfc;
      }
      #main {
        padding: 2rem;
      }
    </style>
  </head>
  <body>
    <header><h1>Example Domain</h1></header>
    <div id="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>
      <p>
        <a href="https://www.iana.org/domains/example">More information...</a>
      </p>
    </div>
    <footer>(c) 2020</footer>
  </body>
</html>

Syntax Reference (WIP)

markup::define! and markup::new!

There are two ways to define templates: markup::define! and markup::new!.

markup::define! defines a template with named arguments. These templates cannot access variables from outer scope. The templates can have generic parameters. Under the hood, markup::define! compiles to a Rust struct that implements markup::Render and std::fmt::Display traits.

Code
markup::define! {
    Hello<'a>(name: &'a str) {
        "Hello, " @name "!"
    }
    HelloGeneric<T: std::fmt::Display>(name: T) {
        "Hello, " @name.to_string() "!"
    }
}

// The template can now be printed directly or written to a stream:
println!("{}", Hello { name: "World" });
writeln!(&mut std::io::stdout(), "{}", HelloGeneric { name: "World 2" }).unwrap();

// The template can also be rendered to a String:
let string = Hello { name: "World 3" }.to_string();
println!("{}", string);
Output
Hello, World!
Hello, World 2!
Hello, World 3!

markup::new! defines a template without any arguments. These can access variables from outer scope.

Code
let name = "World";
let template = markup::new! {
    "Hello, " @name "!"
};

// The template can now be printed directly or written to a stream:
println!("{}", template);
writeln!(&mut std::io::stdout(), "{}", template).unwrap();

// The template can also be rendered to a String:
let string = template.to_string();
println!("{}", string);
Output
Hello, World!
Hello, World!
Hello, World!

Expressions

Templates can have bare literal values, which are rendered as is. They can also have expressions (including function and macro calls) preceded by @ sign. All strings are HTML-escaped unless they are wrapped in markup::raw().

Code
markup::define! {
    Expressions(a: i32, b: i32) {
        1 " + " 2 " = " @{1 + 2} '\n'
        @a " - " @b " = " @{a - b} '\n'
        @format!("{} * {} = {}", a, b, a * b) '\n'
        @a " ^ 4 = " @a.pow(4) '\n'

        // All output is escaped by default.
        "<>\n"
        // Escaping can be disabled using `markup::raw()`.
        @markup::raw("<div></div>")
    }
}

println!("{}", Expressions { a: 5, b: 3 });
Output
1 + 2 = 3
5 - 3 = 2
5 * 3 = 15
5 ^ 4 = 625
&lt;&gt;
<div></div>

Elements

Elements are defined using a CSS selector-like syntax. Elements can contain other nested elements in braces or be followed by a semicolon for self-closing elements.

Code
markup::define! {
    Elements(name: &'static str) {
        // Just a div.
        div {}
        '\n'
        // Three nested elements.
        main {
            aside {
                h3 { "Sidebar" }
            }
        }
        '\n'
        // Self-closing input element.
        input;
        '\n'
        // Element with a name containing dashes.
        $"my-custom-element" {}
        '\n'
        // Element with a dynamic name.
        ${name} {}
    }
}

println!("{}", Elements { name: "span" });
Output
<div></div>
<main><aside><h3>Sidebar</h3></aside></main>
<input>
<my-custom-element></my-custom-element>
<span></span>

Attributes

Attributes are defined after the element name. id and class attributes can be defined using CSS selector-like syntax using # and .. Classes may be specified multiple times using this shorthand syntax. Other attributes are specified in square brackets.

Code
markup::define! {
    Attributes(id: u32, category: String, data: std::collections::BTreeMap<String, String>) {
        // A div with an id and two classes.
        // Note: Starting with Rust 2021, there must be a space between the
        // element name and `#` due to reserved syntax (https://doc.rust-lang.org/edition-guide/rust-2021/reserving-syntax.html).
        div #foo.bar.baz {}
        '\n'
        // A div with a dynamically computed id and one static and one dynamic class.
        div #{format!("post-{}", id)}.post.{format!("category-{}", category)} {}
        '\n'

        // Boolean attributes are only rendered if true. Specifying no value is the same as `true`.
        input[checked = true];
        '\n'
        input[checked = false];
        '\n'
        input[checked];
        '\n'

        // `Option` attributes are rendered only if they're `Some`.
        input[type = Some("text"), minlength = None::<String>];
        '\n'

        // Attribute names can also be expressions wrapped in braces.
        div[{format!("{}{}", "data-", "post-id")} = id] {}
        '\n'

        // Multiple attributes can be added dynamically using the `..` syntax.
        div[..data.iter().map(|(k, v)| (("data-", k), v))] {}
    }
}

println!("{}", Attributes {
    id: 123,
    category: String::from("tutorial"),
    data: [
        (String::from("foo"), String::from("bar")),
        (String::from("baz"), String::from("quux"))
    ].iter().cloned().collect(),
});
Output
<div id="foo" class="bar baz"></div>
<div id="post-123" class="post category-tutorial"></div>
<input checked>
<input>
<input checked>
<input type="text">
<div data-post-id="123"></div>
<div data-baz="quux" data-foo="bar"></div>

@if and @if let

@if and @if let works similar to Rust.

Code
markup::define! {
    If(x: u32, y: Option<u32>) {
        @if *x == 1 {
            "x = 1\n"
        } else if *x == 2 {
            "x = 2\n"
        } else {
            "x is neither 1 nor 2\n"
        }

        @if let Some(y) = y {
            "y = " @y "\n"
        } else {
            "y is None\n"
        }
    }
}

println!("{}", If { x: 2, y: Some(2) });
println!("{}", If { x: 3, y: None });
Output
x = 2
y = 2

x is neither 1 nor 2
y is None

@match

@match works similar to Rust, but the branches must be wrapped in braces.

Code
markup::define! {
    Match(x: Option<u32>) {
        @match x {
            Some(1) | Some(2) => {
                "x is 1 or 2"
            }
            Some(x) if *x == 3 => {
                "x is 3"
            }
            None => {
                "x is None"
            }
            _ => {
                "x is something else"
            }
        }
    }
}

println!("{}", Match { x: None });
println!("{}", Match { x: Some(2) });
println!("{}", Match { x: Some(4) });
Output
x is None
x is 1 or 2
x is something else

@for

@for works similar to Rust.

Code
markup::define! {
    For<'a>(xs: &'a [u32]) {
        @for (i, x) in xs.iter().enumerate() {
            @i ": " @x "\n"
        }
    }
}

println!("{}", For { xs: &[1, 2, 4, 8] });
Output
0: 1
1: 2
2: 4
3: 8

Statements

Templates can have statements preceded by @ sign. The most useful such statement is @let to compute a value for later reuse. @fn can be used to define a function. Also supported are @struct, @mod, @impl, @const, @static and more.

Code
markup::define! {
    Statement(x: i32) {
        @let double_x = x * 2;
        @double_x '\n'

        @fn triple(x: i32) -> i32 { x * 3 }
        @triple(*x)
    }
}

println!("{}", Statement { x: 2 });
Output
4
6

More Repositories

1

select.rs

A Rust library to extract useful data from HTML documents, suitable for web scraping.
Rust
953
star
2

draco

Draco is a Rust library for building client side web applications with Web Assembly.
Rust
301
star
3

ex_top

ExTop is an interactive monitor for the Erlang VM written in Elixir.
Elixir
292
star
4

speculate.rs

An RSpec inspired minimal testing framework for Rust.
Rust
270
star
5

purescript-hedwig

Hedwig is a fast, type safe, declarative PureScript library for building web applications.
PureScript
131
star
6

reaml

A React binding for (OCaml | ReasonML) + BuckleScript with compile time enforcement of the "Rules of Hooks". Live Examples: https://reaml.netlify.com
OCaml
99
star
7

diff.rs

An LCS based slice and string diffing implementation.
Rust
76
star
8

tailwind.run

62
star
9

elm-remotedev

Integration of Elm Application's `update` function with Redux DevTools Extension. An alterative to the official Elm Debugger.
JavaScript
60
star
10

edn.rs

An EDN (Extensible Data Notation) parser in Rust.
Rust
48
star
11

bs-preact

Deprecated in favor of https://github.com/utkarshkukreti/reaml, which has more features, works with the latest BuckleScript, and can be used with both React and Preact.
OCaml
39
star
12

whois.ex

Pure Elixir WHOIS client and parser.
Elixir
27
star
13

munch.rs

Blazing fast, zero-copy parser combinator library for Rust with an elegant API for both strings and bytes.
Rust
21
star
14

purescript-corefn.rs

A parser for PureScript's corefn JSON representation.
Rust
14
star
15

check.c

check.c is a mini testing framework for C that I built to do Test-Driven Development in my C projects.
C
13
star
16

purescript-hertz

PureScript
11
star
17

bootstrap-themes

Open Source, MIT Licensed, Customizable Themes for Bootstrap 4.
HTML
11
star
18

psoc

[WIP] A PureScript to JavaScript compiler focused on increasing performance and decreasing size of the generated code.
Rust
10
star
19

apl-inputrc

Easier way to input APL symbols in GNU APL (or other interactive command line programs).
Ruby
9
star
20

bs-preact-starter

Deprecated in favor of https://github.com/utkarshkukreti/reaml, which has more features, works with the latest BuckleScript, and can be used with both React and Preact.
OCaml
9
star
21

flip-flop.rs

This library implements the [flip-flop operator from Perl and Ruby](https://en.wikipedia.org/wiki/Flip-flop_(programming)) as a Rust macro.
Rust
7
star
22

draco-starter

A starter for https://github.com/utkarshkukreti/draco.
Rust
6
star
23

vite-typescript-library-starter

`mkdir foo && cd foo`; `git clone https://github.com/utkarshkukreti/vite-typescript-library-starter . && rm -rf .git && git init && git add . && git commit -m init && yarn`
TypeScript
6
star
24

wn.rs

Rust
4
star
25

reaml-starter

A starter for [Reaml](https://github.com/utkarshkukreti/reaml) demonstrating a `useReducer` based Counter.
OCaml
4
star
26

catena-r1

A functional, concatenative programming language, inspired by Joy, and implemented in Haskell [Revision 1]
Haskell
4
star
27

zigzag.ex

Zigzag is a fast and flexible parallel processing library for Elixir.
Elixir
3
star
28

parco

A hyper-optimized 1kb library to build fully type-safe parsers in TypeScript.
TypeScript
3
star
29

timingapp.zsh

A simple script to enable directory level tracking for zsh in TimingApp.
Shell
3
star
30

should.c

should.c is a mini testing framework for C, which also happens to be pretty.
C
3
star
31

siphash.rs

Simple and fast implementation of the SipHash hashing algorithm in Rust
Rust
2
star
32

hybrid-core

PHP
2
star
33

rubymotion-breakout

A simple implementation of the Breakout game in RubyMotion.
Ruby
2
star
34

rubymotion-hackernews

Ruby
2
star
35

typescript-preact-tailwind-parcel-starter

TypeScript
2
star
36

phaad

A beautiful way to write PHP code.
Ruby
2
star
37

twitter-angellist-bridge

Major WIP
Ruby
2
star
38

project-euler

Repository of questions of Project Euler that I've solved, in various languages.
Common Lisp
2
star
39

snabbdom-transition

Vue.js inspired transitions component for Snabbdom.
JavaScript
2
star
40

ace_editor-rails

Ace Editor for the Rails pipeline
Ruby
2
star
41

reaml-opinionated-starter

An Opinionated Starter for Reaml which includes Preact, TypeScript, TailwindCSS, Relude, PurgeCSS. Live Demo: https://reaml-opinionated-starter.netlify.com
OCaml
2
star
42

gitstats

Ruby
1
star
43

elm-animate-on-change-poc

https://utkarshkukreti.github.io/elm-animate-on-change-poc/
JavaScript
1
star
44

fropy

Process memory and cpu time benchmarker
Ruby
1
star
45

iff.rs

Rust
1
star
46

gm.ex

Idiomatic GraphicsMagick wrapper for Elixir.
Elixir
1
star
47

bucklescript-minimal-starter

OCaml
1
star
48

rubymotion-chipmunktest

Ruby
1
star
49

nathans-university-pl101

My solutions to Nathan's University's PL101 course, done in both JS and Haskell.
Haskell
1
star
50

ruby-vps

under development
Ruby
1
star
51

rubymotion-snake

Ruby
1
star
52

snipmate-snippets

1
star
53

challenger

Ruby
1
star
54

geoip_server

Ruby
1
star
55

escodegen.rs

Rust
1
star
56

rubymotion-cocos2dbox2dtest

Ruby
1
star
57

select.ex

An Elixir library to extract useful data from HTML documents, suitable for web scraping.
Elixir
1
star
58

elm-inflect

elm-inflect lets you convert a String to its plural, singular, camelCase, and PascalCase forms.
Elm
1
star
59

hapistrano

Ruby
1
star
60

vite-library-starter

TypeScript
1
star
61

qq

A simple utility to store and access key/value pairs from the shell, useful for storing common urls, etc.
Ruby
1
star
62

calvin

Terse, APL/J/K inspired programming language. Major WIP.
Ruby
1
star
63

vite-preact-typescript-tailwind-starter

https://vite-preact-typescript-tailwind-starter.vercel.app
JavaScript
1
star
64

copath_parser

A parser for data from the CoPATH database. Currently on "prostrate" type output can be parsed.
Ruby
1
star
65

purescript-weber

PureScript
1
star
66

putf

Quickly upload files to a remote server, and copy its url to your clipboard.
Ruby
1
star
67

geoip_client

Ruby
1
star
68

sailor

Ruby
1
star
69

euler

Project Euler Solutions in Ruby
Ruby
1
star
70

subtle-lang

Subtle is a Terse, Array based Programming Language, heavily inspired by the K Programming Language, and partly by APL and J.
Ruby
1
star
71

google-codejam-solutions

My Google CodeJam solutions in various languages
1
star
72

typecodec

Under 1kB, fast, type-safe runtime validation of unknown data for TypeScript.
TypeScript
1
star
73

wp

Unofficial git clone of WordPress repository
PHP
1
star
74

ubuntu-scripts

1
star
75

repository_hosting_backup

Backs up all repositories in your RepositoryHosting.com account into a folder.
Ruby
1
star
76

dust-deploy

small server deployment tool for complex environments
Ruby
1
star
77

long_url

Ruby
1
star
78

possible_habtm_bug

Run rake db:migrate && rake db:seed. Code is in db/seeds.rb
Ruby
1
star
79

typescript-react-tailwind-sass-parcel-starter

HTML
1
star
80

gideros-community-libraries

A collection of libraries for Gideros, maintaned by the community.
Shell
1
star
81

ruport_compatible_with_stresser

(Use branch compat) A complete hack to make ruport work with https://github.com/moviepilot/stresser on Ruby 1.9.2
Ruby
1
star
82

elm-css-modules

`elm-css-modules` compiles CSS modules written in CSS, Sass, or SCSS to plain CSS files and generates Elm definitions for every rule.
JavaScript
1
star
83

better_range.rs

An alternative to the std::iter::range* iterators supporting floats and chars, and a fluent interface to construct ranges.
Rust
1
star
84

advent-of-code-2020

Ruby
1
star
85

project-euler-old

Repository of all the questions of ProjectEuler that I've solved, in various languages.
Ruby
1
star