Stebalien/horrorshow-rs

Indenting / nesting content macro

Opened this issue · 7 comments

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).

Unfortunately, whitespace can have meaning in HTML and horrorshow isn't smart enough (it's a macro) to know when it has meaning and when it doesn't. That means I can't really do this properly in the horrorshow macro without introducing weird edge cases (and significantly complicating it). If you really want to nicely format your HTML, you might consider running it through a post-processor (I'm sure you could cobble one together with some components from servo). If you end-up building a nice solution, I'd welcome a PR 😄.

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

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

FYI, templates are composable. Instead of converting to a string and then using Raw, the better way to do this is to just write:

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

let page = html! {
    html {
        body {
            : content
        }
    }
};

...Markdown server project...

Nice!

FYI, I've actually written a markdown renderer for horrorshow in my static blog generator if you're interested. It uses pulldown-cmark under the covers but does the actual HTML rendering with horrorshow. It's kind of gratuitous (you can just have pulldown-cmark render the HTML) but its a bit more flexible.

Could you give me an educated guess as to how hard it would be to count the indent level inside the macro? I've never really worked with Rust macros so it'd help me decide if I want to pursue implementing that on top of Horrorshow and preparing a PR.

Not that hard. Basically, every time you increase /decrease the indent level, you'd call an indent/dedent function on the template.

The tricky part is knowing when whitespace is significant. That would mean horrorshow would actually need to understand what different HTML tags mean.

The simpler solution would be to add a post-processor that takes in rendered HTML and cleans it up (although you can also just do this in your editor with the proper plugins).

Thanks, I'll look into that.

I think the whitespace problem can largely be worked around by having a quick way to explicitly control indent. A way for the user to say "don't indent this bit at all", "usual indent", "indent one less" etc.

I don't like the post-processor solution, if I wanted that why wouldn't I use macros to quickly construct AST's for the post-processor that actually understands HTML?

(Of course I'm a macro noob right now, but I'd rather learn macros than complicate my build/do work twice.)

I think the whitespace problem can largely be worked around by having a quick way to explicitly control indent. A way for the user to say "don't indent this bit at all", "usual indent", "indent one less" etc.

Doing that properly would require quite a bit of design work and would significantly complicate the macro.


To avoid going down the wrong rabbit hole, why do you want the HTML to be pretty-formatted? Usually, the only reason to do this is for debugging; that's why I suggested a post-processor. Most framworks will intentionally remove extra whitespace from formatted HTML to make it smaller.

Note that it's impossible in general to tell whether whitespace is significant or not, as you can control whitespace behavior in CSS.

I actually strongly prefer the non-indented behavior. My technique for getting nicely-formatted HTML out of a renderer like horrorshow is simply to pipe it through prettier; this has the very nice property of ensuring separation of concerns, and it means that instead of horrorshow having to own their own indentation rules, you can use the prettier rules, which are well maintained and rapidly becoming industry standard.

Or, even if you don't want to use prettier, the same logic applies in general. Let horrorshow be good at the thing horrorshow is good at (safely and very quickly generating correct HTML) and use a separate tool that's good at turning raw HTML into nicely-indented.