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 any kind of document to be rendred in a Web browser.
Haml combines a set of valuable features:
- A notation that mirrors HTML
- Inline scripting and control flow
- Blocks of text can be interpreted using custom filters
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.
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
Other Haml functions may also be used by initializing haml_helpers
module Haml::Filters::Cite include Haml::Filters::Base include Haml::Helpers init_haml_helpers end
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)
Building a DOM can be accomplished using
html_tag()
def cite(author, link, title) capture_haml do haml_tag :cite do haml_tag :span, author + ',' haml_tag :a, :href=>link do haml_tag :em, title end end 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
Haml allows HTML to be mixed in directly. This is a good option whenever raw HTML provides a readable or correct solution to formatting.
Built-in Tests
Haml filters are 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