go / haml renderer

I ported the ruby / haml renderer to Go as the haml package. It allows Go web routes and background jobs (eds-jobs) to render the exact same .haml files as the Ruby web application without invoking a Ruby process.

Both renderers accept the same "dumb Haml" subset and produce the same HTML for the same inputs.

The API

The Go API is minimal, exposing Parse and Render:

import "eds/haml"

// Parse template once at startup
tmpl, err := haml.Parse(sourceCode, "views/companies/index.haml")
if err != nil {
    log.Fatal(err)
}

// Render with a context map and optional partial resolver
locals := map[string]any{
    "name": "Acme Corp",
    "score": 9.4,
}

html, err := tmpl.Render(locals, func(name string, partialLocals map[string]any) (string, error) {
    // Resolve = render "partial" calls
    return renderPartial(name, partialLocals)
})

Structure-Aware AST

Like the Ruby version, the parser constructs an Abstract Syntax Tree (AST) rather than concatenating strings:

type Template struct {
	path  string
	nodes []node
}

type node struct {
	kind     nodeKind
	indent   int
	text     string   // static text, output expressions
	tag      string   // %tag
	classes  []string // .class
	id       string   // #id
	children []node
}

This prevents invalid HTML nesting. Since indentation defines nesting, tag open/close pairings are mathematically guaranteed.

Security Model

The Go port enforces the same strict safety invariants:

← All articles