HAML Compatibility
There is no HAML standard. Ruby HAML is a reference implementation that embeds
Ruby, and every implementation embeds some host language. The structural syntax
is what carries across, and the expression language is what differs. See
Template Expressions for the language inside
=, -, and #{}.
This page is a registry rather than prose, because a compatibility claim nothing checks goes stale. Blogin's test suite walks a corpus of real sites and fails when one of them uses a construct that has no recorded decision, and this page is the published form of that record. Adding a construct to a real layout either passes or says which decision is missing, rather than producing a render bug nobody looks for.
Status of every construct#
supported works the way HAML users expect. changed works but not
identically, with the difference stated. unsupported is refused with an error
naming the file and line.
| Construct | Syntax | Status | Notes |
|---|---|---|---|
| element | %tag |
supported | |
| class shorthand | .name |
supported | Combines with a class attribute |
| id shorthand | #name |
supported | |
| attribute hash | {key: value} |
supported | |
| attribute rocket | {'k' => v} |
supported | |
| attribute HTML | (key='value') |
supported | |
| nested attribute | {data: {a: b}} |
supported | Writes data-a |
| boolean attribute | {disabled: true} |
changed | A false or null value omits the attribute rather than writing it empty |
| self closing | %tag/ |
supported | Void tags close themselves without it |
| doctype | !!! 5 |
supported | Always writes the HTML5 doctype |
| escaped output | = expr |
supported | |
| raw output | != expr |
supported | |
| interpolation | #{expr} |
supported | Escapes the value, not the text around it |
| inline text | %tag text |
supported | Text, whatever it starts with. Only a line of its own is a comment or a control line |
| text escape | \text |
supported | The rest of the line is text, including any character HAML would otherwise read |
| silent comment | -# |
supported | Never reaches the page |
| HTML comment | / |
changed | Treated as a silent comment rather than written as an HTML comment |
| plain filter | :plain |
supported | |
| escaped filter | :escaped |
supported | |
| JavaScript filter | :javascript |
supported | |
| CSS filter | :css |
supported | |
| conditional | - if |
supported | |
| else if | - elsif |
supported | |
| else | - else |
supported | |
| negated conditional | - unless |
supported | |
| loop | - for xs -> $x |
supported | Iterating a non-list is an error |
| partial render | render(...) |
supported | |
| partial name | :partial<name> |
supported | Resolves _name.haml |
| collection | :collection(xs) |
supported | |
| item binding | :as<name> |
supported | |
| locals | :locals(map) |
supported | The map is written {name: value} |
| yield | yield |
supported | |
| fragment cache | cache-fragment |
changed | The name is advisory. Reuse is decided by what the fragment read. See below |
| boolean literal | True / False |
supported | Accepted alongside true and false |
Deliberately not carried over#
Each of these is refused with an error rather than half-supported.
| Construct | Why |
|---|---|
%p< and %p> whitespace control |
Whitespace rules here are this engine's own, so an operator that adjusts Ruby HAML's rules has nothing to adjust |
~ preserve |
Same reason |
[@obj] object reference |
Names a Ruby object's class and id, which has no meaning here |
| multiline |
One expression per line |
:ruby, :erb, and other host filters |
There is no host language to run |
The two real differences#
cache-fragment is advisory#
Naming a fragment normally means asserting by hand that its output does not vary, and a wrong assertion serves one page's markup to another. Here the key is derived from the values the fragment read while rendering. A fragment reading only site-level values renders once and is reused. One reading page state renders per page, whichever name it was given. That is faster, because it finds reuse nobody annotated, and safer, because it cannot serve a stale fragment.
A reused fragment is not rendered again. The names it read the first time are resolved against the next page, which gives that page's key before anything is rendered, and a key already in the cache ends the work there. Where the fragment is written is part of its key too, so two fragments that happen to read the same values are still two fragments.
An HTML comment is silent#
/ writes a comment into the page in Ruby HAML. Here it is dropped, the same as
-#. A template comment that reaches the reader is more often a mistake than an
intent. If you need a literal HTML comment in the page, write one with :plain.