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, counts every recorded construct it finds, checks each one has a status, and renders every layout. It detects only constructs already in its table, so a new construct is recorded by adding it there. This page is the published form of that table, plus the constructs below that are not carried over.
Status of every construct#
supported works the way HAML users expect. changed works but not
identically, with the difference stated.
| Construct | Syntax | Status | Notes |
|---|---|---|---|
| element | %tag |
supported | |
| class shorthand | .name |
supported | Combines with a class attribute |
| id shorthand | #name |
changed | An id attribute replaces the shorthand id rather than joining it |
| attribute hash | {key: value} |
supported | |
| attribute rocket | {'k' => v} |
supported | |
| attribute HTML | (key='value') |
changed | One attribute, or several separated by commas. Attributes separated only by spaces are read as one value |
| nested attribute | {data: {a: b}} |
supported | Writes data-a |
| boolean attribute | {disabled: true} |
changed | When the value is an unquoted expression, true writes the bare attribute and false or null omits it. {disabled} alone also writes it |
| 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} |
changed | In text, escapes the value and not the text around it. In an attribute, escapes the whole value. Not interpolated in filters or in an expression string |
| 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 |
changed | The body is written as it is. #{} is not interpolated |
| escaped filter | :escaped |
changed | The body is escaped. #{} is not interpolated |
| JavaScript filter | :javascript |
changed | The body is written as it is. #{} is not interpolated |
| CSS filter | :css |
changed | The body is written as it is. #{} is not interpolated |
| conditional | - if |
supported | |
| else if | - elsif |
supported | |
| else | - else |
changed | Anything after else on the line is ignored, so write - elsif rather than - else if |
| negated conditional | - unless |
supported | |
| loop | - for xs -> $x |
supported | Iterating a non-list is an error |
| partial render | render(...) |
supported | Only as the whole expression of a = or != line |
| 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 |
changed | Only as = yield or != yield, and written raw either way |
| fragment cache | cache-fragment |
changed | The name is advisory. Reuse is decided by what the fragment read. Also spelled cache_fragment. See below |
| boolean literal | True / False |
supported | Accepted alongside true and false. Nil is accepted for null |
Deliberately not carried over#
None of these is interpreted. A host filter is refused with no such filter
when the template renders. The others are not recognized and come out as
literal text, so %p< inner writes <p>< inner</p>.
| 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 |
Two differences in detail#
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.