Making Apache serve HAML and SASS pages dynamically

I'm a big fan of the elegant "haiku" mark-up syntax HAML and it's CSS cousin SASS. But it's been tricky getting these to work independent of the Rails framework for Ruby. I've managed to get pretty far with the apparently abandoned but quite satisfactory phpHaml project, but I wanted something a little more complete and straightforward.

I wanted to be able to just write an /index.haml file that referenced a /stylesheets/index.sass file, upload them to my webroot, and just have them render in any browser.

This weekend I came up with a simple solution that seems to work pretty efficiently. It doesn't do any code caching, so the pages are interpreted from HAML and SASS every time they are loaded. But that's the kind of dynamic access I was looking for. The engines are efficient enough that the processing time doesn't seem to slow down access much.

The basis of the code is a .htaccess file that captures requests for files with the .haml and .sass suffices, and passes them to the appropriate Ruby cgi for processing with the latest and greatest HAML. The rendered HTML and CSS code are sent straight to the browser. You just need a ruby installation with the haml gem installed on your Apache-based web server. This works fine on shared virtual hosts, since there is no modification of any configuration files required.

Here's a basic version of the .htaccess file, stripped down to just the HAML and SASS aspects. I've also defined a DirectoryIndex that gives preference to index.haml over index.html.

DirectoryIndex index.haml index.html

AddHandler hamlpage .haml
Action hamlpage /cgi-bin/haml.cgi

AddHandler sasspage .sass
Action sasspage /cgi-bin/sass.cgi

The haml.cgi to parse the HAML goes into the cgi-bin directory. (Note that the seemingly extra "puts" command on a separate line is required to make the cgi work. Leaving it out will bring you nothing but sorrow and 500 errors.):

#!/usr/bin/ruby
require 'cgi'
cgi = CGI.new
exit if cgi.path_translated.nil?
exit unless File.exists?(cgi.path_translated)
haml_file = cgi.path_translated
require 'rubygems'
require 'haml'
template = File.read(haml_file)
haml_engine = Haml::Engine.new(template, :format =>:html4)
output = haml_engine.to_html()
puts "Content-type: text/html"
puts
puts output

The sass.cgi to parse .sass files looks very similar, and goes in the same place:

#!/usr/bin/ruby
require 'cgi'
cgi = CGI.new
exit if cgi.path_translated.nil?
exit unless File.exists?(cgi.path_translated)
sass_file = cgi.path_translated
require 'rubygems'
require 'sass'
template = File.read(sass_file)
sass_engine = Sass::Engine.new(template, :style=>:compact)
output = sass_engine.to_css()
puts "Content-type: text/css"
puts
puts output

With these files in place, and given executable permissions, you should be able to create a .haml file like this:

!!! strict
%html
  %head
    %link{ :href => 'stylesheets/test.sass', :rel => :stylesheet, :type => 'text/css', :media => 'Screen' }
  %body.test_page
    %h1 Hello World
    %ul
      - (1..3).each do |counter|
        %li
          This is line
          &= counter

With a .sass file that looks like this:

.test_page
  h1
    color: #f00

And get an HTML output file like this:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> 
<html> 
  <head> 
    <link href='stylesheets/test.sass' media='Screen' rel='stylesheet' type='text/css'> 
  </head> 
  <body class='test_page'> 
    <h1>Hello World</h1> 
    <ul> 
      <li> 
        This is line
        1
      </li> 
      <li> 
        This is line
        2
      </li> 
      <li> 
        This is line
        3
      </li> 
    </ul> 
  </body> 
</html> 

With associated CSS that looks like this:

.test_page h1 { color: #f00; }

The SASS engine doesn't seem to be as efficient as the HAML engine, so it would probably be my first candidate for caching. But as an immediate way to explore the flexibility and expressiveness of HAML and SASS, this was a very satisfying little project for me.

If you want to try it locally, here's a zip file with the code above, structured the way you would want it in your web root directory.

3 comments

  1. Emma

    Hey

    I uploaded these files in the appropriate places but I am still getting the 'Index of/' error. Any hints??

  2. Andrew Dvorak

    I also added a handler for .scss:

    AddHandler sasspage .sass .scss

    and then updated the sass.cgi script accordingly to recognize the difference between .sass and .scss syntax, based upon the file extension:

    sass_engine = Sass::Engine.new(template, :style=>:compact, :syntax=>cgi.path_translated.match(/\.(.{4})$/)[1].to_sym)

Post a comment

You may use the following HTML:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>