Horrorshow: A no-longer POC html template library

Horrorshow is a macro-based HTML templating library. For those of you who saw the last post, horrorshow has become quite a bit more useful.

Links

Project: GitHub, crates.io
Docs: horrorshow - Rust

Features

  • Works on stable.
  • Built-in html escaping (that should be audited by someone who knows what they are doing...).
  • Can render into existing strings and io writers.
  • fast (compiles to assembly).
  • Composable.

Non features:

  • Really unhelpful errors. Unfortunately, there's no way to tell rust: "don't look in this ugly macro code for type errors, assume I meant every word of this macro code and the error is elsewhere."
  • To be able to return a template, it needs to be boxed. This is because templates are actually closures. However, this is only true of returned templates; you don't need to allocate when rendering locally.

Example

#[macro_use]
extern crate horrorshow;

use horrorshow::{RenderBox, Template};

fn render_post(post: Post) -> Box<RenderBox> {
    let Post { title, body, tags } = post;
    box_html! {
        article {
            header(class="post-header") {
                h1 : title;
                ul {
                    |t| tags.iter().fold(t, |t, tag| t << html! {
                        li : tag
                    });
                    /*

                    // You should  be able to write the following but rust doesn't
                    // re-borrow when using binary operators (gh#25753).
                    |t| for tag in tags {
                        t << html! { li : tag };
                    }
                    */
                }
            }
            section(class="post-body") : body;
        }
    }
}

fn render<I: Iterator<Item=Post>>(title: &str, posts: I) -> String {
    (html! {
        : raw!("<!DOCTYPE html>");
        html {
            head {
                title : title
            }
            body {
                main {
                    header { h1 : title }
                    section(id="posts") {
                        |t| posts.fold(t, |t, post| t << render_post(post));
                    }
                }
            }
        }
    }).into_string()
}

struct Post {
    title: String,
    tags: Vec<String>,
    body: String,
}

fn main() {
    let posts = vec![
        Post {
            title: String::from("First Post"),
            tags: vec![String::from("first post")],
            body: String::from("My Test Post"),
        },
        Post {
            title: String::from("Second Post"),
            tags: vec![],
            body: String::from("My Second Test Post"),
        },
    ];
    println!("{}", render("my blog", posts.into_iter()));
}

Output:

<!DOCTYPE html>
<html>
    <head>
        <title>my blog</title>
    </head>
    <body>
        <main>
            <header>
                <h1>my blog</h1>
            </header>
            <section id="posts">
                <article>
                    <header class="post-header">
                        <h1>First Post</h1>
                        <ul>
                            <li>first post</li>
                        </ul>
                    </header>
                    <section class="post-body">My Test Post</section>
                </article>
                <article>
                    <header class="post-header">
                        <h1>Second Post</h1>
                        <ul></ul>
                    </header>
                    <section class="post-body">My Second Test Post</section>
                </article>
            </section>
        </main>
    </body>
</html>

If you have any comments on the current error handling design, please post them here: Error Handling Design in Horrorshow

a no-longer POC

Ahh yes, just like all code: you think it's a throwaway, but then it stays around forever... :smile:

It was too fun to put down. Also, I don't feel like fixing the my patched version of ruhoh so I decided to write a static blogging engine from scratch (TBD)...