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

Overview

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

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>
Comments
  • optional attributes

    optional attributes

    problem: i am migrating a project from horrorshow currently there are some Option variables for example let css: Option<String> = None; but i don't find a way to specify that if css is None don't create the html attribute div[class=css] creates <div class=""></div> but i want create when css is None <div></div> i know i can do an if conditional like this

    @if let Option(css) = css{
    div[class=css]
    }else
    {
    div{}
    }
    

    but this is not practical when i have a lot of optional attributes

    horrorshow have this sintaxis div(class?=css) and i get what i want <div></div> when css is None

    is there something similar that i missing? or it would be a new feature?

    opened by miguelski 7
  • Question: define! vs new!

    Question: define! vs new!

    Hi team,

    First, thanks for this library. The impressive benchmark result is making me move from Maud to this, although I'm not very experienced with either. Me question is regarding define! vs new! macro use, like in the fortunes.rs example. I tend to prefer new! here, and have functions wrapping the template, so my question is: Is there any disadvantage (in performance or otherwise) of new! vs define!? Any advice on which one will scale better once the number of templates (and the references between them) scales as the application grows?

    Thanks

    opened by diegopy 4
  • Generate element name with hyphen in it

    Generate element name with hyphen in it

    In order to use https://github.com/github/markdown-toolbar-element I need to generate markup that looks like this:

    <markdown-toolbar for="textarea_id">
      <md-bold>bold</md-bold>
      <md-header>header</md-header>
      <md-italic>italic</md-italic>
    </markdown-toolbar>
    

    I tried:

    markdown-toolbar[for="body"] {
    }
    

    and

    "markdown-toolbar"[for="body"] {
    }
    

    but neither worked. Is this possible with markup.rs? ~~If not, do you think the quoted syntax makes sense? If it does I could have a go at implementing it.~~ Edit: I realised the quoted string is a string literal already, so that won't work.

    opened by wezm 3
  • Support dynamic element names

    Support dynamic element names

    Currently there's no way to turn:

    let name = "foo";
    

    into

    <foo></foo>
    

    without messing with markup::raw().

    Looking for suggestions for syntax to use for this; I can't think of any elegant one right now.

    opened by utkarshkukreti 3
  • Hyphenated attributes throw

    Hyphenated attributes throw "error: expected `=`"

    Hyphenated attributes produce the following error. This is important for being able to use Bootstrap.

    button [
        type="button",
        class="btn btn-circle btn-dual-secondary d-lg-none align-v-r",
        data-toggle="layout",
        data-action="sidebar_close"
        ]
        {
            i [class="fa fa-times text-danger"]
        }
    

    Throws the error

    error: expected `=`
       --> src/t_base.rs:115:29
        |
    115 |                         data-toggle="layout",
        |  
    
    opened by niallrr 3
  • Syntax Question

    Syntax Question

    I really like this crate and I want to use it in my projects, but I'm getting a syntax error I'm having trouble debugging. I see that this repo hasn't seen much activity lately but I'm hoping this gets seen.

    My template:

    const BASE_DIR: &str = "/foo/bar";
    Markup::define! {
        Home {
            {markup::doctype()}
            html [lang="en"] {
                body {
                    @match {fs::read_dir(BASE_DIR)} {
                        Ok(dirs) => {
                            @for dir in dirs {
                                @match {dir.ok()} {
                                    Some(item) => if item.path().is_dir() {
                                        {item.file_name()}
                                    }
                                    None => {}
                                }
                            }
                        }
                        Err(error) => {}
                    }
                }
            }
        }
    }
    

    The compiler says it's expecting an identifier at @match {dir.is_ok()} {. I googled around but couldn't find anything about this. Any help would be appreciated.

    opened by AblatedSprocket 3
  • Can't destructure in Markup

    Can't destructure in Markup

    Hey!

    I'm trying to destructure an struct in a markup parameter:

    Foo(Bar {x, y, z}: Bar) {
        p { {x} }
    }
    

    But the compiler's expecting an : where the destructure is occuring.

    For reference, I can do this in a normal function:

    fn Foo(Bar {x, y, z}: Bar) { // ... }
    

    Is this a bug or an unsupported feature?

    opened by edward-shen 3
  • Non-identifier attribute names

    Non-identifier attribute names

    Thanks for this crate - I'm loving its elegant and simple design.

    Is there a way to set attributes that aren't rust identifiers? For example:

    markup::define! {
        Hello {
            // "type" is a keyword, so this fails
            input[type="number"]
            // '-' isn't allowed in an identifier
            br[data-id=1]
        }
    }
    

    Supporting string literals as attribute names might be a clean way to solve this.

    opened by 17dec 3
  • Allow template arguments to be documented

    Allow template arguments to be documented

    Currently, if I attempt to attach a doc comment to a template argument, like this:

            /// Used for CSS/JS cache-busting
            build_timestamp: &'static str,
    

    ...I get the following error:

    error: expected identifier
      --> src/templates.rs:58:9
       |
    58 |         /// Used for CSS/JS cache-busting
       |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    

    I'm left having to manually add an Arguments section containing a bulleted list to the documentation block for the template as a whole, resulting in this needlessly redundant and typo-prone output:

    Screenshot 2022-05-06 at 05-22-16 HttpError

    opened by ssokolow 2
  • Any ideas how to do nested components?

    Any ideas how to do nested components?

    I'd like to build a nested tabs component. Something like

    Tabs {
      tabs: vec!(Tab {
        title: "Tab 1",
        body: markup::new! {
                div {
                  ... more content
                }
            }
      })
    }
    

    So a component, with a list of nested sub components.

    I tried a few different things, something like

    markup::define! {
        Tab<'a>(
            title: &'a str,
            body: Box<dyn markup::Render>
        ) {
            input[type="radio", id="tab2", name="css-tabs"] {}
            div {
                {title}
            }
            div {
                @body
            }
        }
        Tabs<'a>(tabs: &'a [Tab]) {
           ...list all the tabs
        }
    }
    

    But that doesn't compile. I guess I could pass around the Tab elements as a string, but ideally I"d like the type safety.

    Any ideas?

    opened by ianpurton 2
  • Add local images to web

    Add local images to web

    I'm trying to add images to the web loaded directly from the compiled rust code, my question is if this is possible...

    Current not working goes like this:

    {markup::raw("<img id=\"logo\" src=\"images/my-logo.png\">")}

    This works when I replace whats in src with an already uploaded picture on a web (hyperlink) but I would like to have an images dir that I can use.

    Rust struct:

    my-web ├── src/ │ ├── images/ │ │ ├── my-logo.png │ ├── index.rs

    So my question would be if its possible to load an image in markup.rs from a local folder? If so, how would this be done?

    opened by SlowEnter 2
  • Automatically pretty-print in debug builds

    Automatically pretty-print in debug builds

    It occurs to me that it would be nice for developer egonomics if (via feature flag?) markup.rs would output pretty-printed HTML in debug builds, for situations where you've got JavaScript meddling with what the developer tools displays and you need to inspect the original form of the markup.

    (It'd be a nice complement to how useful it is for JS/TS development that, in the default configuration, rust-embed will embed your assets in the binary in release builds but just serve up the editable source files in debug builds.)

    opened by ssokolow 8
  • Provide some means to deduplicate the base template

    Provide some means to deduplicate the base template

    I currently have to manually include something like this in every top-level template I write and it'd be really nice if I could deduplicate it.

            @markup::doctype()
            html[lang="en", class=dark_mode.then(|| "dark_mode")] {
                @Header { title: &format!("Error {status_code}"), robots_meta, build_timestamp }
    
                body {
                    #container {
                        // REST OF THE TEMPLATE HERE
                    }
                    @JsBundle { build_timestamp }
                }
            }
    

    I tries playing around with the where clauses and theRendertrait and making a pair ofFooandFooInner` templates, where the inner template is effectively what an AJAX response would return, but gave up as the need to manually undo the reference-taking for the arguments caused by the outer template calling the inner one made the inner template very cluttered and ugly.

    In a more HTML-styled template language, this'd typically be solved either by "template inheritance", which looks like this in Django/Jinja/Twig-style templates...

    base.tmpl:

    <!DOCTYPE html>
    <html lang="en">
        ...
        <body>
            <div id="container">
                {% block content %}ERROR: No Content{% endblock %}
            </div>
        </body>
    </html>
    

    specific.tmpl:

    {% extends "base.tmpl" %}
    
    {% block content %}
    page body goes here
    {% endblock %}
    

    ...or by abusing the template language's dynamic scoping and runtime evaluation to render "specific.tmpl" by rendering "base.tmpl" and passing in a string argument which is used as the path to include!, relying on how various template languages default to exposing the the template arguments as global variables to what you include!.

    opened by ssokolow 6
  • Syntax documentation

    Syntax documentation

    There used to be a nice syntax reference in the README but it looks like that was removed in 962fe1482f2f7a483fbecbdc3cb58b744500d71b. Was that migrated somewhere else that I can refer to?

    opened by wezm 2
  • SVG Elements causing a (signal: 9, SIGKILL: kill) at compile time

    SVG Elements causing a (signal: 9, SIGKILL: kill) at compile time

    Hello,

    While building a rust UI through markup a code signal 9 appears at compile time. This is due to an over usage of the ram during compilation. Is there any way to fix it? It seems to happen when too many elements are present, but works fine on its nodejs based server.

    Any idea or solution to counter this issue, and if so would it be possible to improve it?

    Thank you very much.

    opened by LucasStill 1
  • Support raw as a default mode

    Support raw as a default mode

    Currently, the define!() macro assumes XML-like rules, and there's likely a bit of overhead in parsing every string in that manner. I have use for templating for a wide range of formats which are not XML-like in nature, such as INI, TOML, or any other custom formats. Peppering the define!() with markup::raw() seems wrong.

    As an example, this is what I've come up with for generating desktop entry files. There are no escape rules, outside of \; for in keys that take multiple values.

    markup::define! {
        DesktopEntry<'a>(
            name: &'a str,
            generic_name: Option<&'a str>,
            icon: &'a str,
            comment: Option<&'a str>,
            hidden: bool,
            no_display: bool,
            kind: DesktopType<'a>
        ) {
            "[Desktop Entry]\n"
            "Type=" {markup::raw(kind.type_str())} "\n"
            "Name=" {markup::raw(name)} "\n"
            
            @if let Some(generic) = (generic_name) {
                "GenericName=" {markup::raw(generic)} "\n"
                
                "X-GNOME-FullName=" {markup::raw(name)} " " {markup::raw(generic)} "\n"
            }
    
            "Icon=" {icon} "\n"
            
            @if let Some(comment) = (comment) {
                "Comment=" {markup::raw(comment)} "\n"
            }
    
            @if *(hidden) {
                "Hidden=true\n"
            }
    
            @if *(no_display) {
                "NoDisplay=true\n"
            }
    
            @if let DesktopType::Application(app) = (kind) {
                "Categories=" {markup::raw(app.categories)} "\n"
                
                @if !app.keywords.is_empty() {
                    "Keywords=" { '\"' }
                    @for keyword in app.keywords.iter() {
                        {markup::raw(keyword)} ";"
                    }
                    { markup::raw("\"\n") }
                }
    
                @if !app.mime_types.is_empty() {
                    "MimeType=" { '\"' }
                    @for mime in app.mime_types {
                        {markup::raw(mime)} ";"
                    }
                    { markup::raw("\"\n") }
                }
    
                @if app.terminal {
                    "Terminal=true\n"
                }
    
                @if app.startup_notify {
                    "StartupNotify=true\n"
                }
    
                "Exec=" {markup::raw(app.exec)} "\n"
                
                @if let Some(path) = (app.path) {
                    "Path=" {markup::raw(path)} "\n"
                }
            } else if let DesktopType::Link { url } = (kind) {
                "Link=" {markup::raw(url)} "\n"
            }
        }
    }
    
    opened by mmstick 2
Owner
Utkarsh Kukreti
Utkarsh Kukreti
A type-safe, K-sortable, globally unique identifier

type-safe-id A type-safe, K-sortable, globally unique identifier. Typed implementation of https://github.com/jetpack-io/typeid in Rust. Examples Stati

Conrad Ludgate 13 Jul 10, 2023
Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.

foundry Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust. Foundry consists of: Forge: Ethe

Georgios Konstantopoulos 5.1k Jan 9, 2023
A low-level assembly language for the Ethereum Virtual Machine built in blazing-fast pure rust.

huff-rs • huff-rs is a Huff compiler built in rust. What is a Huff? Huff is a low-level programming language designed for developing highly optimized

Huff 276 Dec 31, 2022
🥷🩸 Madara is a ⚡ blazing fast ⚡ Starknet sequencer, based on substrate, powered by Rust 🦀

Report a Bug - Request a Feature - Ask a Question ⚡ Madara: Starknet Sequencer on Substrate ?? Welcome to Madara, a blazing fast ⚡ Starknet sequencer

Keep StarkNet Strange 138 Apr 22, 2023
Open sourcing a profitable MEV Arbitrage Bot written in blazing fast Rust.

Dex Arbitrage - MEV Bot Open sourcing a profitable MEV Arbitrage Bot written in blazing fast Rust. Before Starting I am a self-taught programmer, a co

null 4 Sep 18, 2023
Blazing fast Pedersen hash implementation for Node.JS

pedersen-fast Blazing fast Pedersen hash implementation for Node.JS Exposes starknet-crypto's implementation written in Rust as WASM package. Usage np

L2BEAT 7 Mar 10, 2023
Blazing fast toolkit for developing Starknet contracts.

Starknet Foundry Blazingly fast toolkit for developing Starknet contracts designed & developed by ex Protostar team from Software Mansion based on nat

Foundry 149 Aug 1, 2023
Minimal compile-time Rust template engine

boilerplate boilerplate is a minimal compile-time Rust text template engine. Quick Start Add boilerplate to your project's Cargo.toml: [dependencies]

Casey Rodarmor 15 Dec 30, 2022
Safe, fast, small crypto using Rust

THE SOFTWARE IS PROVIDED "AS IS" AND BRIAN SMITH AND THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES

Brian Smith 3k Jan 2, 2023
A blazingly fast and memory safe password cracker with user interface.

HashVat A blazingly fast and memory safe password cracker with user interface. HashVat runs with user interface and is capable of cracking the 1.000.0

JBLDSKY 2 Dec 6, 2022
A small, 8-byte, ID type for use in rust applications that need a pretty unique identifier

TinyId A small, 8-byte, ID type for use in rust applications that need a pretty unique identifier that is not required to be cryptographically secure

Tony B 1 May 4, 2022
Rust compile-time type information experiment

Compile-Time Type Information This crate is an experimental standard library side implementation of potential ctti language feature. The idea is to pr

Auri 12 Jan 20, 2023
A performant, type-1 zkEVM written in Rust & SP1.

SP1 Reth SP1 Reth is a 100% open-source POC that showcases how any rollup can use SP1 to build a performant (type-1, bytecode compatible) zkEVM with l

Succinct 90 Mar 24, 2024
Onlyfans-type web service based on TOR with maximum privacy features.

onionfans Onlyfans-type web service based on TOR with maximum privacy features. Features "Vanishing" single-use feed CDN links Landing page No JavaScr

Dowland Aiello 8 Sep 14, 2022
A "Type 0" zkEVM. Prove validity of Ethereum blocks using RISC Zero's zkVM

zeth NEW: Zeth now supports Optimism blocks! Just pass in --network=optimism! Zeth is an open-source ZK block prover for Ethereum built on the RISC Ze

RISC Zero 222 Oct 26, 2023
Simple template for building smart contract(Rust) and RPC Client(web3.js) on Solana (WIP) ⛏👷🚧⚠️

Solana BPF Boilerplate Simple template for building smart contract(Rust) and RPC Client(web3.js) on Solana This boilerplate provides the following. Si

ono 6 Jan 30, 2022
This is a template to build secret contracts in Rust to run in Secret Network

Secret Contracts Starter Pack This is a template to build secret contracts in Rust to run in Secret Network. To understand the framework better, pleas

Ethan Gallucci 1 Jan 8, 2022
A template to build smart contracts in Rust to run inside a Cosmos SDK module on all chains that enable it.

CosmWasm Starter Pack This is a template to build smart contracts in Rust to run inside a Cosmos SDK module on all chains that enable it. To understan

null 1 Mar 7, 2022
🖨 Template for Rust applications & smart contracts @okp4.

Rust Template Template for Rust projects @okp4. Purpose & Philosophy This repository holds the template for building Rust projects with a consistent s

OKP4 – Open Knowledge Protocol For 6 Nov 17, 2022