Eric Radman : a Journal

Programming Documents with Haml

Haml is an templating language for writing HTML. Over the past decade the capabilities of HTML+CSS have become good enough for nearly everything: letters, my resume, pages on this site, even printed forms.

Haml combines a set of valuable features:

  1. A notation that mirrors HTML
  2. Blocks of text can be interpreted using custom filters
  3. Ad-hoc scripting

Mirroring HTML

Notwithstanding the name, Haml is not very abstract. It is mostly a 1:1 representation of the HTML it generates

!!!
%html
  %head
    %title A Letter
    %link{:href=>"main.css", :rel=>"stylesheet", :type=>"text/css"}/
  %body
    = Haml::Engine.new('home.haml').render
    %hr/
    %p.timestamp
      - require 'date'
      = "Last updated on " + Time.now().strftime("%B %d, %Y")

Setting a class and id properties has a nice syntax

::haml
/ class="first"
%p.first
/ id="content"
%p#content

Ruby code can be run directly, and Haml::Engine can be used directly to process another template.

Building

In Haml, Ruby itself is also not abstracted, which gives us the ability to invent new mechanisms. One simple scheme I have used is to read extra arguments on the command line

= Haml::Engine.new(File.read(ARGV[-1])).render

This means Makefile can easily communicate a source file to read

.SUFFIXES: .haml .html

SOURCES != ls posts/*.haml | awk 'sub("\.haml$$", ".html")'

haml.html:
    haml25 template.haml $@ $<

Filters

Perhaps the most important feature of template language is the ability to process and transform blocks of text

module Haml::Filters::Link
  include Haml::Filters::Base

  def render(text)
    %q|<a href="#{text}">#{text}</a>|
  end
end

This can be incorporated thusly

%p
  Visit my home page at
  :link
    http://eradman.com/

And to compile

haml25 -I $PWD -r link my.haml

Built-in Tests

I filed this post under the category TDD. Indeed built-in self tests are an excellent means of writing filters. Haml filters are simple Ruby modules, so we can build simple self-tests without having to instantiate an object

require 'haml'

module Haml::Filters::Link
    def render(text)
        # ...
    end
end

if __FILE__ == $0
    include Haml::Filters::Link

    out = render("http://eradman.com")
    expected = %q|<a href="http://eradman.com">http://eradman.com</a>|

    if expected == out
        puts "PASS"
    else
        puts <<~MESSAGE
          FAIL
          expected: #{expected}
          got     : #{out}
        MESSAGE
    end
end

Stumbling Blocks

There are some features of Haml which cause difficulty. The first is that control over whitespace is alkward. Often the most strait-forward method is to use the special functions surround and suceed. In this example I want a period immediately following the hyperlink

Send comments to
= succeed '.' do
  %a{:href=>"mailto:ericshane@eradman.com"} ericshane@eradman.com

Another technique for controlling whitespace is to append > to trim whitespace and then add non-breaking spaces where needed

%pre
  %span.Prompt> $&nbsp;
  ls *.css *.html | entr reload-browser Firefox

Sometimes it's easier to simply generate and/or write HTML. This is no problem—Haml doesn't prevent you, and sometimes this provides a readable and correct solution to formatting.

Last updated on October 18, 2018