I have a relatively difficult-to-understand and, therefore, difficult-to-maintain implementation of HAML over at gohaml. I’ve kept it “up-to-date” as Go has matured to version 1.0. Now that it has, it’s time for me to make good on something better for the open-source community.

First off, the new gohaml will pass all tests in the excellent haml-spec fork that I’ve created to run gohaml against it. As soon as I get those passing, I’ll submit the pull request to the real haml-spec for inclusion in their repository.

Secondly, gohaml has a new structure. Hopefully it will make the whole of the library easier to understand and conform more to the structure of the Ruby implementation of HAML from which all good HAML-ness flows.

gohaml v1 Blech

The current production-ready implementation takes HAML source and parses it into an abstract syntax tree representing the content of the document. Then, on each call to Render, it performs a left-most, depth-first walk of the abstract syntax tree and outputs the values of each inode and icodenode to the output buffer. That’s not too efficient.

Also, I had some kind of mental affliction when I came up with these names in the current implementation. I mean, what are these names?

  • inode
  • icodenode
  • res
  • resPair
  • rangenode
  • node
  • declassnode
  • vdeclassnode

And, then, as if that’s not bad enough, I wrote a line-based, top-down parser to create the abstract syntax tree. It has method names like

  • parseLeadingSpace
  • parseCode
  • parseTag
  • parseId
  • parseClass
  • parseKey
  • parseRemainder

And, it requires the use of go tool yacc to compile the lang.y file into a lang.go file for use in the parseCode block.

This is frankensoftware.

gohaml v2 Goodness

Version 2 of gohaml tries to align the engine with the Ruby implementation in terms of its public options. Then, it goes for a more structured attempt at converting the HAML source into an optimized HTML-generating object.

Engine options in gohaml v2

Attribute Wrapper
The rune that should wrap element attributes
Autoclose
A list of tag names that should be automatically self-closed if they have no content
Cdata
Whether to use CDATA sections around JS and CSS blocks
CompilerFactory
A func that returns an object that implements the HamlCompiler interface
Encoding
The encoding to use for the HTML output
EscapeAttributes
Sets whether or not to escape HTML-sensitive characters in attributes
EscapeHtml
Sets whether or not to escape HTML-sensitive characters in script
Format
Determines the output format
HyphenateDataAttributes
If set to true, gohaml will convert underscores to hyphens in all Custom Data Attributes
ParserFactory
A func that returns an object that implements the HamlParser interface
RemoveWhitespace
If set to true, all tags are treated as if both whitespace removal options were present
SuppressEval
Whether or not attribute hashes and Go scripts designated by = or ~ should be evaluated
Ugly
If set to true, gohaml makes no attempt to properly indent or format the HTML output.

The parser

The gohaml/parser package declares the HamlParser interface that has a Parse method that returns a ParsedDoc structure.

The ParsedDoc has a slice of Node implementations that represent the abstract syntax tree of the document. Unlike the version 1 implementation, this implementation has names that make sense.

  • DoctypeNode
  • TagNode
  • StaticNode
  • StaticLineNode
  • InterpretedNode (or something like that; I haven’t decided)

Finally, the package also exports the NodeVisitor interface that allows some dynamic dispatch for the compiler.

The compiler

The gohaml/compiler package declares the HamlCompiler interface that Compiles a gohaml/parser.ParsedDoc into a CompiledDoc. A CompiledDoc has a Render method that takes a map[string]interface{} for value binding in the doc.

A CompiledDoc has a slice of Ouptuts of which two types exist:

  • StaticOutput
  • InterpretedOutput (or something like that; I haven’t decided)

More importantly, the last step of the default compiler calls a method that compresses the output of the compilation into as few string writes as possible to a buffer. That means that the method concatenates the values of all contiguous StaticOuptut objects into a single StaticOutput. If you had a HAML template that had no dynamic output, then the call to Render on the CompiledDoc executes just one write to the output buffer instead of walking the entire abstract syntax tree like in version 1.

Where am I?

I’ve made it through about half of haml-spec. So, that’s pretty good. Because this is my free-time project, I don’t get the kind of time that I’d like to devote to it. However, I want to complete it by the end of the month because I want to write a “real” Go-based application and I want gohaml for the templating library.

As a matter of fact, I’m pretty keen on it.

I’ll probably also add the ability to render the output using a channel because, in some cases, we can execute the resolution of independent blocks concurrently. I don’t know how much love that would actually give the system… only time and benchmarks will tell.

Where are you?

Anyway, if you want to participate, just head over to the gohaml repository, fork it, implement one of the haml-spec tests that currently doesn’t pass, and submit a pull request.

I look forward to your contributions!