gohaml v2 Coming Soon...
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
Compile
s 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 Ouptut
s 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!