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 forms.
Haml 6 introduced many breaking changes in the library and command line. To work around this set the gem install to version 5.2.2.
Haml combines a set of valuable features:
- A notation that mirrors HTML
- Blocks of text can be interpreted using custom filters
- 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
have their own syntax
/ 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: haml template.haml $@ $<
Filters
Perhaps the most important feature of template language is the ability to process and transform blocks of text
# link.rb module Haml::Filters::Link include Haml::Filters::Base def render(text) %q|<a href="#{text}">#{text}</a>| end end
This function processes a block of text as the input
%p Visit my home page at :link http://eradman.com/
And to compile add an include path and a reference to the library
haml -I . -r link my.haml
Utility Functions
Modules may include any kind of functionality: here is a function that find the next date that lands on a Saturday
# utils.rb require 'date' def next_weekend(offset) today = Date.today today = today.next_day(offset) while ! today.saturday? today = today.next_day end today.strftime("%B %d, %Y") end
Relative imports may be used to make functions available
- require_relative 'utils' %p = next_weekend(1)
Built-in Tests
Builtin 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
Whitespace
Nearly every other templating system seems to have difficulty controlling whitespace, and Haml is no exception.
The normal way to add a comma or a period after another tag is to use the
and
succeed
method. (See also
surround
).
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> $ ls *.css *.html | entr reload-browser Firefox
Note that Haml allows HTML to be mixed in directly. This is a wonderful option whenever raw HTML provides a readable or correct solution to formatting.