slim

package module
v0.0.0-...-a7b1ba8 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 27, 2026 License: BSD-3-Clause Imports: 4 Imported by: 0

README

go-ruby-slim/slim

slim — go-ruby-slim

Docs License Go Coverage

A pure-Go (no cgo) reimplementation of the Ruby Slim template engine (the slim gem) — the deterministic, interpreter-independent core that turns an indentation-structured Slim template into the Ruby source that renders it, producing the same HTML the gem produces for the same locals.

It is the Slim backend for go-embedded-ruby, but is a standalone, reusable module with no dependency on the Ruby runtime.

What it is — and isn't. Compiling a template to Ruby source (indentation nesting, tag/.class/#id shorthand, attributes, embedded engines, comments, the doctype) is fully deterministic and needs no interpreter, so it lives here as pure Go. The final eval(compiled_src) that runs any embedded Ruby (= expressions, - control, #{} interpolation, dynamic attribute values) does need a Ruby interpreter and stays in the consumer (e.g. rbgo) — this library compiles, the host evaluates. This mirrors the sibling go-ruby-erb and go-ruby-haml designs exactly.

Unlike Haml, Slim emits no incidental whitespace between tags: its Temple-based backend concatenates fragments tightly, void elements render as <tag ... />, and #{} interpolation in plain text is HTML-escaped by default. Everything static — element structure, literal attributes, .class/#id shorthand, the doctype, /! comments, and the javascript:/ css: embedded engines — is resolved at compile time into literal HTML runs, so a template with no embedded Ruby renders with no interpreter at all.

Features

Validated against the slim gem (5.x) on every supported platform, comparing rendered HTML byte-for-byte:

  • Elements & shorthandtag, .class/#id (div default), tag.c1.c2#id, class-merge (space) between shorthand and attributes; a single id (duplicates are a compile error, matching the gem). Block expansion ul: li: a href="x" Text nests tags on one line, with indent-children attaching to the innermost tag.
  • Attributes — bare a href="x", and every wrapper form a(href="x") / a[href="x"] / a{href="x"}; string/number/true/false/nil literals, boolean attributes (checked=truechecked="", falsy omitted), bare booleans inside wrappers (input(disabled)), class-array ["a","b"], alphabetical ordering, escaped values. Dynamic values href=url, unescaped attr==expr, and *splat hashes are rendered at eval time via ::Slim::Helpers.render_attributes; a static .class shorthand and a dynamic class= are merged, never dropped. Attribute groups may span multiple linesa(href="x"⏎ title="y").
  • Output — inline text (which, with deeper-indented following lines, forms a multi-line text block), = (HTML-escaped Ruby), == (unescaped), - (control, no output), | verbatim text block, ' verbatim + trailing space, text interpolation #{...} (escaped) / #{{...}} (unescaped) / \#{...} (literal). - control and =/== output lines continue onto the next line when they end in , or \.
  • Inline HTML — a line beginning with < is emitted verbatim (with #{} interpolation); indent-nested children render after it, unwrapped.
  • Control flow- if/elsif/else, - case/when, - begin/rescue/ensure and - … do |x| blocks nest correctly and share a single emitted end.
  • Embedded enginesjavascript:<script>…</script>, css:<style>…</style>, ruby: runs the body as code.
  • Comments & doctype — code comment / (discarded with its subtree), HTML comment /! (a text block), conditional comment /[if IE], and doctype html/5/xml/transitional/strict/frameset/1.1/basic/mobile.
  • Whitespace control — tag </> (leading/trailing space), output =</=> and verbatim |</|>/' leading/trailing spaces.
  • Void / self-closing — the HTML5 void set (br, img, input, …) and the explicit tag/ marker render as <tag ... />.

CGO-free, dependency-free, 100% test coverage, gofmt + go vet clean, and green across the six 64-bit Go targets (amd64, arm64, riscv64, loong64, ppc64le, s390x).

Deferred

Text-processing filters that need an external enginemarkdown:, scss:/sass:, coffee:, less: — are not compiled; the header parses and the raw body is emitted verbatim (best-effort) rather than run through the engine (naming them keeps the gap explicit). Slim's optional plugins — logic-less mode, include, Slim::Translator (I18n), and the smart-text :smart option — are likewise out of scope. === is not a Slim indicator (the gem raises a syntax error on it), so it is not implemented.

Everything else in the feature list above matches the gem's rendered HTML byte-for-byte in the test corpus.

Install

go get github.com/go-ruby-slim/slim

Usage

package main

import (
	"fmt"

	"github.com/go-ruby-slim/slim"
)

func main() {
	src, err := slim.Compile("p= name\n", slim.Options{})
	if err != nil {
		panic(err)
	}
	fmt.Println(src)
	// _slimout = ::String.new
	// _slimout << "<p>"
	// _slimout << ::Slim::Helpers.escape_html((name).to_s)
	// _slimout << "</p>"
	// _slimout
	//
	// Hand it to a Ruby interpreter with `name` in scope:
	//   name = "World"; eval(src)  ->  "<p>World</p>"

	// A fully-static template compiles to a single literal append — no interpreter needed:
	src, _ = slim.Compile(".card\n  h1 Title\n  p Body", slim.Options{})
	// _slimout << "<div class=\"card\"><h1>Title</h1><p>Body</p></div>"
}

Render through a pluggable evaluator (the rbgo seam):

out, err := slim.Render("p= greeting",
	map[string]string{"greeting": `"hi"`},
	func(rubySrc string, locals map[string]string) (string, error) {
		// go-embedded-ruby/rbgo binds the locals and eval's rubySrc here.
		return rbgo.Eval(rubySrc, locals)
	})

API

type Options struct {
	BufVar   string // output-buffer var name; default "_slimout"
	EscapeFn string // Ruby escape helper for "="; default "::Slim::Helpers.escape_html"
}

// Compile returns the Ruby source that, when eval'd with the template's locals
// in scope, builds and returns the rendered HTML string, matching the `slim` gem.
func Compile(template string, opts Options) (src string, err error)

// Render = Compile + a pluggable Ruby-eval seam (nil eval returns the source).
func Render(template string, locals map[string]string, eval Evaluator) (string, error)
type Evaluator func(rubySource string, locals map[string]string) (string, error)

func HTMLEscape(s string) string // Temple::Utils.escape_html
What the host (rbgo) provides at eval time

The compiled source references the runtime symbols the host supplies:

  • ::Slim::Helpers.escape_html(s) — the five-character HTML escape (overridable via Options.EscapeFn), also used for interpolated text;
  • ::Slim::Helpers.render_attributes(hash, *splats) — renders dynamic / splat attributes (class merge, single id, boolean handling, alphabetical order, escaping);
  • ::Slim::Helpers.safe(v) — marks an attr==expr value HTML-safe so it is not re-escaped.

The reference implementations used by the differential oracle live in testdata/prelude.rb.

Tests & coverage

The suite includes a differential oracle: a wide template corpus (elements, shorthand, static & dynamic attributes, embedded engines, comments, control flow, interpolation, verbatim blocks, whitespace control) is rendered both by the system slim gem and by eval'ing our compiled source under the reference prelude, comparing the HTML byte-for-byte. The deterministic, ruby-free golden-source tests alone hold coverage at 100%, so the no-ruby lanes still pass the gate.

COVERPKG=$(go list ./... | paste -sd, -)
go test -race -coverpkg="$COVERPKG" -coverprofile=cover.out ./...
go tool cover -func=cover.out | tail -1   # 100.0%

The oracle tests skip themselves where ruby/slim is not available (e.g. the qemu arch lanes), so the cross-arch builds still validate the compiler itself.

License

BSD-3-Clause — see LICENSE. Copyright the go-ruby-slim/slim authors.

WebAssembly

Being pure Go (CGO=0), this library also compiles to WebAssembly — both GOOS=js GOARCH=wasm (browser / Node.js) and GOOS=wasip1 GOARCH=wasm (WASI). CI builds both targets on every push, alongside the six 64-bit native/qemu arches.

GOOS=js     GOARCH=wasm go build ./...   # browser / Node
GOOS=wasip1 GOARCH=wasm go build ./...   # WASI (wasmtime, wasmer, wasmedge, …)

Documentation

Overview

Package slim is a pure-Go (no cgo) reimplementation of the Ruby Slim template engine (the `slim` gem) — the deterministic, interpreter-independent core that turns an indentation-structured Slim template into the Ruby source that, when evaluated against a set of locals, renders the same HTML the gem produces.

It mirrors the go-ruby-erb / go-ruby-haml design: this package COMPILES a template to Ruby source; the final eval that runs any embedded Ruby (`=` expressions, `-` control, `#{}` interpolation, dynamic attribute values) needs a Ruby interpreter and is deliberately left to the consumer (go-embedded-ruby/rbgo).

Everything that is static — element structure, `.class`/`#id` shorthand, literal attributes, the doctype, the `/!` HTML comment, and the `javascript:`/`css:` embedded engines — is resolved at compile time into literal HTML runs, so a template with no embedded Ruby renders with no interpreter at all. That deterministic core is what the ruby-free tests cover to 100%.

Unlike Haml, Slim emits no incidental whitespace between tags: its Temple backend concatenates fragments with no separating newlines, so the compiled buffer is a tight HTML string. Void elements render as "<tag ... />" and interpolation in plain text is HTML-escaped by default.

Index

Constants

View Source
const PreludeMarker = "::Slim::Helpers.escape_html / .render_attribute / .render_attributes"

PreludeMarker documents the runtime symbols the compiled source expects the host to provide: the escape helper (Options.EscapeFn) and, for dynamic attributes, ::Slim::Helpers.render_attribute / .render_attributes. It is part of the public surface so the runtime contract is greppable from Go.

Variables

This section is empty.

Functions

func Compile

func Compile(template string, opts Options) (src string, err error)

Compile compiles a Slim template into the Ruby source that, when eval'd with the template's locals in scope, builds and returns the rendered HTML string — matching the `slim` gem's rendered output. err is non-nil only for genuinely malformed templates; well-formed templates always compile (the compiled Ruby may still raise at eval time, which is the host's concern).

The returned source assigns the buffer to BufVar, appends every fragment, and evaluates to the buffer as its final expression, so a host renders with `eval(src)` after binding the locals.

func HTMLEscape

func HTMLEscape(s string) string

HTMLEscape replaces the five HTML-significant characters with their entity references, matching Temple::Utils.escape_html exactly (note "'" becomes "&#39;"). It is exposed so a host embedding the compiled source can provide the runtime escape helper the emitted Ruby calls.

func Render

func Render(template string, locals map[string]string, eval Evaluator) (string, error)

Render compiles template and renders it via eval, using the supplied evaluator. It is the compile+eval seam that mirrors go-ruby-erb.Render and go-ruby-haml.Render: the pure-Go side compiles to Ruby source and hands it, together with the locals, to eval — a pluggable function a host such as rbgo provides. When eval is nil, Render returns the compiled source unchanged as a convenience for callers that only need the source.

locals maps a local-variable name to the Ruby literal source for its value (e.g. "name" -> `"World"`); the evaluator is expected to bind them before eval'ing the compiled source.

func TrimTrailingNewline

func TrimTrailingNewline(s string) string

TrimTrailingNewline reports whether s ends in a newline and, if so, strips a single one; a small helper for hosts that want to normalise the buffer. Slim itself emits no trailing newline, but this keeps the rendering contract documented and testable alongside the Haml sibling.

Types

type Evaluator

type Evaluator func(rubySource string, locals map[string]string) (string, error)

Evaluator is the Ruby-eval seam: given compiled Ruby source and the locals to bind, it returns the rendered string. go-embedded-ruby/rbgo supplies a real implementation; the pure-Go tests use a tiny deterministic shim for the interpreter-free cases.

type Options

type Options struct {
	// BufVar names the output-buffer local variable the compiled source appends
	// to. When empty it defaults to "_slimout".
	BufVar string

	// EscapeFn names the Ruby method the compiled source calls to HTML-escape an
	// interpolated `=` expression, an escaped attribute value, or interpolated
	// plain text. When empty it defaults to "::Slim::Helpers.escape_html", the
	// helper the host (rbgo) provides at eval time.
	EscapeFn string
}

Options configures Compile.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL