A macro-based html builder for rust

Overview

Horrorshow

Build Status Documentation crates.io

A macro-based html templating library, compatible with stable rust (currently requires rust >= 1.37).

Features

This crate will degrade gracefully when compiled without std (disable the "std" feature) and even without alloc (disable the "alloc" feature).

When compiled with alloc but without std:

  • Template::write_to_io() is not defined.
  • Templates may only emit errors implementing ToString and all such errors are immediately converted to strings.

When compiled with just core:

  • RenderBox is no longer defined (no allocation).
  • The Template::into_string() and Template::write_to_string() are no longer defined. The only template rendering method available is Template::write_to_fmt().
  • Templates may only emit static &str errors, and only the first is recorded.

Example:

#[macro_use]
extern crate horrorshow;
use horrorshow::prelude::*;
use horrorshow::helper::doctype;

fn main() {
    let actual = format!("{}", html! {
        : doctype::HTML;
        html {
            head {
                title : "Hello world!";
            }
            body {
                // attributes
                h1(id="heading") {
                    // Insert escaped text
                    : "Hello! This is <html />"
                }
                p {
                    // Insert raw text (unescaped)
                    : Raw("Let's <i>count</i> to 10!")
                }
                ol(id="count") {
                    // You can embed for loops, while loops, and if statements.
                    @ for i in 0..10 {
                        li(first? = (i == 0)) {
                            // Format some text.
                            : format_args!("{}", i+1)
                        }
                    }
                }
                // You need semi-colons for tags without children.
                br; br;
                p {
                    // You can also embed closures.
                    |tmpl| {
                        tmpl << "Easy!";
                    }
                }
            }
        }
    });

    let expected = "\
    <!DOCTYPE html>\
    <html>\
      <head>\
        <title>Hello world!</title>\
      </head>\
      <body>\
        <h1 id=\"heading\">Hello! This is &lt;html /&gt;</h1>\
        <p>Let's <i>count</i> to 10!</p>\
        <ol id=\"count\">\
          <li first>1</li>\
          <li>2</li>\
          <li>3</li>\
          <li>4</li>\
          <li>5</li>\
          <li>6</li>\
          <li>7</li>\
          <li>8</li>\
          <li>9</li>\
          <li>10</li>\
        </ol>\
        <br /><br />\
        <p>Easy!</p>\
      </body>\
    </html>";
    assert_eq!(expected, actual);
}
Comments
  • improving example

    improving example

    thanks for your library! Unfortunately I am struggling to find out how to pass a simple variable to the template e.g.

    let actual = html! {
        : doctype::HTML;
        html {
            head {
                title : "Hello world!";
            }
        }
    }
    

    how would I pass the variable title using your example ? I also think web templates are mostly used to display data so perhaps you can add that to the example..not sure why the calculations are important in this scenario! sorry if I misunderstood something... am I also right to assume that normal HTML has to be translated into the rust tags inside html! to work right ?

    opened by gurugeek 17
  • Add labels! and labels_sep_by! utility macros.

    Add labels! and labels_sep_by! utility macros.

    Closes https://github.com/Stebalien/horrorshow-rs/issues/7

    I use suggestion from https://github.com/Stebalien/horrorshow-rs/issues/7#issuecomment-223713500


    I added labels_sep_by! because I wanted to be able to customize delimiters. I use it to generate the style attribute of the element.


    @Stebalien I couldn't get this to work labels!("active" if false) since:

    expr and stmt variables may only be followed by one of: => , ;

    according to https://doc.rust-lang.org/book/macros.html

    Any suggestions?

    opened by dashed 10
  • error: no rules expected the token `tmpl`

    error: no rules expected the token `tmpl`

    As a prenote I only have a general idea of how rust works and are making a web site/server to learn it.

    I have multiple functions that return Box<RenderBox> that are later used in a function that returns the rendered string.

    Version: 0.6.2

    Log

    error: no rules expected the token `tmpl`
       --> src\resources\partials\element.rs:131:5
        |
    131 | /     box_html! {
    132 | |         nav(class="nav nav_top full_width") {
    133 | |             ul(class="nav__menu flex flex--nowrap flex__center wrapper") {
    134 | |                 li(class="nav__items--container") {
    ...   |
    151 | |         }
    152 | |     }
        | |_____^
        |
        = note: this error originates in a macro outside of the current crate
    

    Code

        box_html! {
            nav(class="nav nav_top full_width") {
                ul(class="nav__menu flex flex--nowrap flex__center wrapper") {
                    li(class="nav__items--container") {
                        ul(class="nav__items--list flex flex--nowrap") {
                            li(class="flex__item") {
                                a(href="/", class="nav__link") {
                                    : "Story";
                                    b : "Archive";
                                }
                            }
                            : navbar_item(item_grow)
                            @ if navbar_drawer {
                                : navbar_drawer(drawer);
                            } else {
                                : navbar_item(item_login);
                            }
                        }
                    }
                }
            }
        }
    
    opened by Txuritan 6
  • Utility for conditionally joining classNames together

    Utility for conditionally joining classNames together

    I had a need for something like https://github.com/JedWatson/classnames

    I came up with the following macro:

    macro_rules! classnames {
    
        // base cases
    
        ($name: expr) => (
            format!("{}", $name)
        );
    
        ($name:expr => $should_include:expr) => (
            if $should_include {
                format!("{}", $name)
            } else {
                format!("")
            }
        );
    
        // expansions
    
        ($name:expr, $($tail:tt)+) => {
            format!("{} {}", $name, classnames!($($tail)*))
    
        };
    
        ($name:expr => $should_include:expr, $($tail:tt)+) => {
            if $should_include {
                format!("{} {}", $name, classnames!($($tail)*))
            } else {
                classnames!($($tail)*)
            };
        };
    
    }
    
    // usage:
    html!{
    
        div(class = classnames!("active" => true, "button-style")) {
            : "this is a button"
        }
    
        div(class = classnames!("active" => false, "button-style")) {
            : "this is a button"
        }
    }
    

    rust playground with examples: https://is.gd/e6Jahb

    I just learned rust macros to make this; it can probably be refactored into something more nicer.


    I'm wondering if this utility macro would be something worth adding to horrorshow. I can try to PR.

    opened by dashed 6
  • fix `Box<RenderBox + Send>` not implementing `RenderOnce`

    fix `Box` not implementing `RenderOnce`

    edited on github, let's see what travis says :)

    right now if I have a Box<RenderBox + Send> i need to coerce it to a Box<RenderBox> before it becomes useful. Not that that's a blocker, but it's annoying.

    opened by oli-obk 5
  • Empty div not closing properly

    Empty div not closing properly

    The code that is causing this error is below:

    fn make_div() -> Box<dyn RenderBox> {
        box_html! {
            div(id="renders-incorrectly");
        }
    }
    

    The final div with id renders-incorrectly doesn't render <div id="renders-incorrectly"></div>. Instead, it renders <div id="renders-incorrectly"> and then appends a closing </div> at the end of the entire document. Replacing that div with:

    :Raw("<div id=\"renders-correctly\"></div>");
    

    fixes the problem, although I think they should be the same thing.

    I'd like to look into this problem and try to fix it myself when I get the time.

    opened by sezna 4
  • Add a way to create a move closure inside

    Add a way to create a move closure inside

    I had some code fail on me with borrow errors that I found impossible to resolve without jumping through hoops like wrapper types and other hacks

    fn foo() -> impl RenderOnce {
        let b = true;
        html!(
            @if !b {
                : "hello";
            }
        )
    }
    
    opened by oli-obk 4
  • Feature request: render to `Bytes`

    Feature request: render to `Bytes`

    It's common in Rust web server frameworks to manage instances of bytes::Bytes, which is essentially a reference counted Vec. It's sufficiently ubiquitous that I think there'd be value in adding write_to_bytes or into_bytes to Template (perhaps behind a feature flag).

    opened by Lucretiel 3
  • How to make a layout that accepts content?

    How to make a layout that accepts content?

    Say I wanted to make a layout that I can inject different content into based on a route. How could I go about doing so?

    fn layout(page_title: String, content: ???) -> String {
      format!("{}",html!{
        head {
          title : page_title;
        }
        body {
          :content
        }
      })
    }
    
    fn home_content() -> Box<RenderOnce> {
    
      box_html! {
        h1 { :"Home Page" }
      }
    }
    
    fn about_content() -> Box<RenderOnce> {
    
      box_html! {
        h1 { :"About Us" }
      }
    }
    
    fn contact_content() -> Box<RenderOnce> {
    
      box_html! {
        h1 { :"Contact Us" }
      }
    }
    
    //and i could call the layout like this
    
    layout(String::from("Home"),home_content());
    layout(String::from("About"),about_content());
    layout(String::from("Contact"),contact_content());
    
    opened by decapo01 3
  • Combining templates with `fold`/`match` -> no two closures have the same type.

    Combining templates with `fold`/`match` -> no two closures have the same type.

    I want to fold some Horrorshow templates, I'm using Horrorshow to make directory listings. However, I'm having a hard time understanding how to make this work, because I can't match on the output of html! and I can't fold over them either.

    I'm sorry for the glorified support request, I'll try to rework your advice into a pull-request which adds examples to the documentation if you like.

    Here's the code I want to write, but the only way to make it work I have found is passing around the rendered HTML by calling .into_string().unwrap() on the html! output every time.

    (Sorry for the large blurb of code, the first map is largely irrelevant but I thought it would be useful for context.)

      fn list_directories_recursive(&self, path: &Path) -> Option<String> {
            let listings = path
            .read_dir().unwrap()
            .filter_map(|de| de.ok())         // remove invalid paths
            .map(|de| de.path())              // DirectoryEntry -> Path
            .filter(|p| self.visible_path(p)) // remove invisible paths
            .map(|path| {
                if let &Some(href) = &path.as_os_str().to_str() {
                    if let &Some(file_name) = &path.file_name() {
                        if let Some(name) = file_name.to_str() {
                            let href = href.replacen(".", "", 1); // ./thing -> /thing
                            let name = name.replacen("/", "", 1); // ./dir/thing -> thing
    
                            return Some((path, href, name));
                        }
                    }
                }
    
                None
            })
            .filter_map(|s| s)               // remove empty/non-UTF directories
            .map(|(path, href, name)| {      // Path -> String
                html! {
                    @if self.list_recursively && path.is_dir() {
                        @if let Some(sub) = self.list_directories_recursive(&path) {
                            : sub
                        }
                    }
                    tr {
                        td {
                            a(href=&href) {
                                : name
                            }
                        }
                    }
                }
            })
            .fold(html! {}, |acc, html| html! {
                : acc;
                : html;
            })
            .into_string().unwrap();
    
            if listings.len() != 0 {
                Some(listings)
            } else {
                None
            }
    
    opened by WillemMali 3
  • Context-aware escaping

    Context-aware escaping

    This is necessary to be able to safely e.g., have colors for elements that are determined at runtime. It is also necessary to support proper URL handling.

    opened by DemiMarie 3
  • wishlist: Explore applicability to DOM creation

    wishlist: Explore applicability to DOM creation

    The data structure created by horrorshow might easily be suitable to create a DOM from it as well, in parallel to serialization to text-encoded HTML. Some features of horrorshow might be unsupported (I'm unsure as to how much raw text can be used with modern DOM APIs), but that might be acceptable for use cases.

    The typed-html crate provides something some steps in that direction (I haven't seen an example of actual shared HTML, but it can be used to render either to HTML or to dodrio), but is not actively maintained any more, and horrorshow's syntax seems nicer to me.

    The application I see for it would be to use the same code to render things server side, but (with a vastly different backend) to also perform client-side updates from WASM.

    There was one previous issue in that direction (#29) where DOM was described as out-of-scope, so this is more about exploring DOM creation based on horrorshow, for which I'd like to gather input here:

    • Has creating a DOM from RenderOnce objects been attempted?
    • Are there any major known showstoppers?
    • Would the project be open to (possibly under a feature flag) exposing internals that would allow other crates to implement DOM rendering based on horrorshow's templates? (I'm far from understanding its internals yet, but that could for example be a trait around TemplateBuffer that can be implemented by a DOM builder.)
    opened by chrysn 3
  • Indenting / nesting content macro

    Indenting / nesting content macro

    Hi, I think I have a (partial) solution to the nesting problem: a function to format content as-if it is a nested and indented HTML element.

    For instance, if I generate some HTML like so:

    let content = html! {
        ul {
            li { : "toot" }
        }
    }
    

    I get the following HTML:

    <ul><li>toot</li></ul>
    

    As an aside, this puzzles me, is there anything I can do to get indented output? It'd be a great feature to me, I would expect output like this

    <ul>
        <li>
            toot
        </li>
    </ul>
    

    Anyway, now I want to insert it into an HTML page:

    let page = html! {
        html {
            body {
                : Raw(content)
            }
        }
    }
    

    And this gets me:

    <html><body><ul><li>toot</li></ul></body></html>
    

    Now, what I'm suggesting is a function or character to insert raw text with newlines and an indent, like so:

    let page = html! {
        html {
            body {
                > content
            }
        }
    }
    

    Or perhaps: > Raw(content), or : Block(content), or some other variant.

    For the result:

    <html><body>
        <ul><li>toot</li></ul>
    </body></html>
    

    It would take the indent level from the parent's indent level and add one to it. If the raw content is multiline, it would indent all lines separately, like so:

    <html><body>
        <ul><li>toot</li></ul>
        <ul><li>toot</li></ul>
    </body></html>
    

    What do you think? Would this be hard to implement? It'd really be a boon to getting nice formatted HTML output out of my Markdown server project (https://gitlab.com/willemmali-rust/markbrowse).

    opened by WillemMali 7
Hiccup html templating in rust

Hiccup A Clojure's Hiccup inspired macro. At the moment support for inline code execution is not guaranteed. The main objective of this lib is to prev

Julia Naomi 13 May 28, 2022
Balsa is a delightfully simple HTML template engine

?? balsa Balsa is a delightfully simple HTML template engine. It is designed to be used in user interfaces such as a CMS where a user needs to be able

Tyler Lafayette 1 Jan 19, 2022
A template engine for Rust based on Jinja2/Django

Tera Tera is a template engine inspired by Jinja2 and the Django template language. <title>{% block title %}{% endblock title %}</title> <ul> {% for u

Vincent Prouillet 2.5k Jan 1, 2023
Compiler for Jade-like template language to cito.js-based virtual dom

Marafet A very experimental DSL for creating (mostly) single page applications in HTML. It's mostly a Jade-like (or Haml-like) templating language wit

Paul Colomiets 11 Jun 25, 2020
Template for Cargo based SysY compiler projects.

基于 Cargo 的 SysY 编译器项目模板 该仓库中存放了一个基于 Cargo 的 SysY 编译器项目的模板, 你可以在该模板的基础上进行进一步的开发. 该仓库中的 Rust 代码实现仅作为演示, 不代表你的编译器必须以此方式实现. 如你需要使用该模板, 建议你删掉所有 Rust 源文件, 仅

PKU Compiler Course 1 Nov 1, 2021
Rust templating with Handlebars

handlebars-rust Handlebars templating language implemented in Rust and for Rust. Handlebars-rust is the template engine that renders the official Rust

Ning Sun 922 Dec 27, 2022
Yarte stands for Yet Another Rust Template Engine

Should we start to worry? bytes-buf feature can produce SIGILL. avx and sse flags are in almost all cpus of x86 and x86_64 architectures. More details

Juan Aguilar 249 Dec 19, 2022
Rust Compiled Templates with static-file handling

Rust Compiled Templates — ructe This is my attempt at writing a HTML template system for Rust. Some inspiration comes from the scala template system u

Rasmus Kaj 337 Jan 8, 2023
Type-safe, compiled Jinja-like templates for Rust

Askama Askama implements a template rendering engine based on Jinja. It generates Rust code from your templates at compile time based on a user-define

Dirkjan Ochtman 2k Jan 5, 2023
A flexible template engine for Rust

Rustache Rustache is a Rust implementation of the Mustache spec. Documentation The different Mustache tags are documented at the mustache(5) man page.

rustache 208 May 10, 2022
A minimalist Rust WebAssembly project template

MiniWASM - A minimalist Rust WebAssembly project template This is a minimal Rust-powered WebAssembly application template. It was designed to showcase

Emil Loer 160 Jul 26, 2022
A template for a Rust-powered static-page Try Online interface

rust-tio-template A template for a Rust-powered static-page Try Online interface What is included This is an example setup that enables all of the fol

null 2 Dec 13, 2021
MiniJinja is a powerful but minimal dependency template engine for Rust

MiniJinja: a powerful template engine for Rust with minimal dependencies MiniJinja is a powerful but minimal dependency template engine for Rust which

Armin Ronacher 686 Jan 5, 2023
Templates for creating rust projects with a GitHub-managed lifecycle with cargo-generate 🏗️📃

rust-templates Templates for creating rust projects with a GitHub-managed lifecycle with cargo-generate. ??️ ?? What you get: PR build validation usin

Ben Greenier 1 Oct 30, 2021
A "Hello, world!" template of a Rust binary crate for the ESP-IDF framework.

Rust on ESP-IDF "Hello, World" template A "Hello, world!" template of a Rust binary crate for the ESP-IDF framework. This is the crate you get when ru

Ivan Markov 140 Jan 4, 2023
A fast & simple boilerplate generator, built with Rust. 🦀

Boom ?? A fast & simple boilerplate generator, built with Rust. Installing boom This package is not yet downloadable on Brew or other package managers

Tristan Edwards 4 Apr 20, 2022
A template for creating services in Rust using Axum and Prisma.

A template for creating services in Rust using Axum and Prisma. This uses the super cool Prisma Rust Client.

Aaron Leopold 6 Oct 19, 2022
✨ A perfect template for a binary rust project.

Rust Template A project template for Rust, helping to structure your projects blazingly fast ⚡ . Features ?? Code-ready for binary projects. Add amazi

bwtecode 3 Aug 21, 2022
Easy c̵̰͠r̵̛̠ö̴̪s̶̩̒s̵̭̀-t̶̲͝h̶̯̚r̵̺͐e̷̖̽ḁ̴̍d̶̖̔ ȓ̵͙ė̶͎ḟ̴͙e̸̖͛r̶̖͗ë̶̱́ṉ̵̒ĉ̷̥e̷͚̍ s̷̹͌h̷̲̉a̵̭͋r̷̫̊ḭ̵̊n̷̬͂g̵̦̃ f̶̻̊ơ̵̜ṟ̸̈́ R̵̞̋ù̵̺s̷̖̅ţ̸͗!̸̼͋

Rust S̵̓i̸̓n̵̉ I̴n̴f̶e̸r̵n̷a̴l mutability! Howdy, friendly Rust developer! Ever had a value get m̵̯̅ð̶͊v̴̮̾ê̴̼͘d away right under your nose just when

null 294 Dec 23, 2022
mail-builder is a flexible e-mail builder library written in Rust that generates RFC5322 compliant e-mail messages

mail-builder mail-builder is a flexible e-mail builder library written in Rust that generates RFC5322 compliant e-mail messages. The library has full

Stalwart Labs 37 Dec 19, 2022