erb

package module
v0.0.0-...-1e3bc08 Latest Latest
Warning

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

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

README

go-ruby-erb/erb

erb — go-ruby-erb

Docs License Go Coverage

A pure-Go (no cgo) reimplementation of Ruby's ERB template compiler — the deterministic, interpreter-independent core of MRI's ERB::Compiler. It turns an ERB template string into the Ruby source that renders it, matching MRI 4.0.5 byte-for-byte, and provides ERB::Util's html_escape / url_encode helpers.

It is the ERB 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 (tag scanning, trim modes, the <%% %%> literals, magic-comment detection, the String#dump text encoding) is fully deterministic and needs no interpreter, so it lives here as pure Go. The final eval(compiled_src, binding) that produces the rendered string does need a Ruby interpreter and stays in the consumer (e.g. rbgo) — this library compiles, the host evaluates.

Features

Faithful port of MRI's lib/erb/compiler.rb, validated against the ruby binary on every supported platform:

  • All tag kinds<% code %>, <%= expression %>, <%# comment %>.
  • Literals<%%<% and the %%> escape, exactly as MRI scans them.
  • Every trim mode — the MRI trim_mode string and its combinations:
    • --%> strips the immediately-following newline (explicit trim);
    • > — strips the newline after a tag that ends its line;
    • <> — strips the newline only when the tag both starts and ends the line;
    • %%-prefixed lines are code lines, with %% an escaped literal %.
  • Magic comments — a leading <%# coding: … %> / frozen_string_literal: … (including the emacs -*- … -*- form) is detected and reflected in the emitted #coding: / #frozen-string-literal: prefix.
  • Binary-exact text encoding — literal runs are emitted via Ruby's String#dump on the binary string, so embedded quotes, newlines, control bytes and multi-byte UTF-8 round-trip byte-for-byte (héllo"h\xC3\xA9llo").
  • ERB::UtilHTMLEscape (&<>"' → entities, '&#39;) and URLEncode (percent-encode all but A-Za-z0-9-_.~, upper-case hex).
  • erubi dialect (Mode: ModeErubi) — reproduces the erubi gem's Erubi::Engine whitespace/trim semantics, so consumers that render through erubi (Sinatra, Rails) get byte-identical output. erubi matches no classic trim_mode: a standalone <%= x %>\n keeps its newline (whereas <> trims it), a line holding only a <% … %> code or <%# … %> comment tag is trimmed automatically (whereas the default keeps it and - needs explicit <%- … -%>), -%> / =%> chomps an expression's newline, and <%== HTML-escapes. Additive: the default ModeERB is unchanged, so require "erb" consumers are unaffected.

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).

Install

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

Usage

package main

import (
	"fmt"

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

func main() {
	src, magic, err := erb.Compile("Hello <%= name %>!\n", erb.Options{})
	if err != nil {
		panic(err)
	}
	fmt.Print(magic) // #coding:UTF-8
	fmt.Println(src)
	// #coding:UTF-8
	// _erbout = +''; _erbout.<< "Hello ".freeze; _erbout.<<(( name ).to_s); _erbout.<< "!\n".freeze
	// ; _erbout
	//
	// src already carries the #coding prefix; hand it to a Ruby interpreter:
	//   eval(src, binding)  ->  "Hello World!\n"

	fmt.Println(erb.HTMLEscape(`<a href="x">it's</a>`))
	// &lt;a href=&quot;x&quot;&gt;it&#39;s&lt;/a&gt;
	fmt.Println(erb.URLEncode("100% & more"))
	// 100%25%20%26%20more
}

With a trim mode and a custom buffer variable:

src, _, _ := erb.Compile("<ul>\n<% items.each do |i| -%>\n  <li><%= i %></li>\n<% end -%>\n</ul>\n",
	erb.Options{TrimMode: "-", EOutVar: "buf"})

In erubi-compatible mode (for Sinatra/Rails-style rendering) the standalone <%= … %> newline is kept and standalone <% … %> lines are trimmed:

// "<%= yield %>\n"  ->  renders "…\n" (newline kept), matching erubi — not "…"
src, _, _ := erb.Compile("<%= yield %>\n<% items.each do |i| %>\n<%= i %>\n<% end %>\n",
	erb.Options{Mode: erb.ModeErubi})

API

type Options struct {
	Mode     Mode   // ModeERB (default, classic MRI ERB) or ModeErubi (erubi-compatible)
	TrimMode string // MRI trim_mode: "", "-", ">", "<>", "%" and combinations (ModeERB only)
	EOutVar  string // output buffer var name; default "_erbout"
}

// Mode selects the ERB dialect Compile targets.
type Mode int
const (
	ModeERB   Mode = iota // classic MRI ERB (honours TrimMode)
	ModeErubi             // erubi gem's Erubi::Engine semantics (TrimMode ignored)
)

// Compile returns the Ruby source that, when eval'd against a binding, renders
// the template, plus the magic-encoding comment line — matching MRI's
// ERB::Compiler#compile two-value contract.
func Compile(template string, opts Options) (src, magicComment string, err error)

func HTMLEscape(s string) string // ERB::Util.html_escape / .h
func URLEncode(s string) string  // ERB::Util.url_encode / .u

// Compiler mirrors MRI's ERB::Compiler for hosts that need the put/insert/
// pre/post command wiring directly (most callers use Compile).
type Compiler struct { PutCmd, InsertCmd string; PreCmd, PostCmd []string; EOutVar string }
func NewCompiler(trimMode string) *Compiler
func (c *Compiler) Compile(s string) (src, magicComment string, err error)

Tests & coverage

The suite includes a differential oracle: a wide template corpus (every tag kind, the literals, all trim modes, multiline/quoted/unicode bodies) is compiled both here and by the system ruby, comparing the emitted Ruby source and the rendered result byte-for-byte, plus ERB::Util against MRI. A parallel oracle renders the erubi-mode corpus against the installed erubi gem (Erubi::Engine) for byte-identical parity.

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 is not on PATH (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-erb/erb 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 erb is a pure-Go (no cgo) reimplementation of Ruby's ERB template compiler — the deterministic, interpreter-independent core of MRI's ERB::Compiler. It turns a template string into the Ruby source that, when evaluated against a binding, renders the template, matching MRI 4.0.5 byte-for-byte. It also provides ERB::Util's html_escape / url_encode helpers.

What it is NOT: the final eval(compiled_src, binding) that produces the rendered string needs a Ruby interpreter and is deliberately left to the consumer (e.g. go-embedded-ruby/rbgo). This package compiles; the host evaluates.

The package faithfully ports MRI's lib/erb/compiler.rb: the SimpleScanner (default), TrimScanner (">", "<>") and ExplicitScanner ("-") behaviours, the "%"-line percent mode, the <%% / %%> literals, the magic-comment detection, and the String#dump text encoding (binary path) that MRI emits.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Compile

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

Compile compiles template into the Ruby source that, when eval'd against a binding, builds and returns the rendered string. It mirrors MRI's ERB::Compiler#compile contract, returning the source and the magic-encoding comment line (the "#coding:UTF-8\n" prefix MRI prepends). The returned src already includes that prefix, so callers normally eval src directly; the separate magicComment is returned for parity with MRI's two-value contract and so a host can inspect the detected encoding.

err is non-nil only for genuinely malformed options; well-formed templates never fail to compile (matching MRI, which never raises on template syntax — the compiled Ruby may raise at eval time, but that is the host's concern).

func HTMLEscape

func HTMLEscape(s string) string

HTMLEscape replaces the five HTML-significant characters with their entity references, matching ERB::Util.html_escape / ERB::Util.h exactly (note "'" becomes "&#39;").

func URLEncode

func URLEncode(s string) string

URLEncode percent-encodes every byte except the RFC-3986 unreserved set (A-Z a-z 0-9 - _ . ~), upper-casing the hex digits, matching ERB::Util.url_encode / ERB::Util.u exactly. It operates on the raw bytes of s (as MRI does via String#b), so each byte of a multibyte UTF-8 character is percent-encoded individually.

Types

type Compiler

type Compiler struct {
	// PutCmd handles literal text that is appended to the buffer (MRI put_cmd).
	PutCmd string
	// InsertCmd handles a <%= ... %> expression (MRI insert_cmd).
	InsertCmd string
	// PreCmd are statements prepended to the compiled code (MRI pre_cmd).
	PreCmd []string
	// PostCmd are statements appended to the compiled code (MRI post_cmd).
	PostCmd []string
	// EOutVar is the output-buffer variable name (informational; the actual
	// buffer wiring is carried by PutCmd/InsertCmd/PreCmd/PostCmd).
	EOutVar string
	// contains filtered or unexported fields
}

Compiler is the porcelain mirror of MRI's ERB::Compiler. It is exposed (in addition to the headline Compile function) so a host such as rbgo can drive the put/insert/pre/post command wiring exactly as MRI's ERB.new does. Most callers should use Compile.

func NewCompiler

func NewCompiler(trimMode string) *Compiler

NewCompiler constructs a Compiler for the given raw trim_mode string, normalising it the way MRI's ERB::Compiler#prepare_trim_mode does. The command fields default to MRI's "print"-based defaults; Compile (the headline API) overrides them with buffer-appending commands.

func (*Compiler) Compile

func (c *Compiler) Compile(s string) (src string, magicComment string, err error)

Compile compiles the template, returning the Ruby source (including the "#coding:UTF-8\n" magic prefix) and that magic-comment line separately, in parity with MRI's ERB::Compiler#compile two-value return.

type Mode

type Mode int

Mode selects which ERB dialect Compile targets.

const (
	// ModeERB is classic MRI ERB (the default): Compile reproduces
	// ERB.new(str, trim_mode:, eoutvar:) byte-for-byte, honouring TrimMode.
	ModeERB Mode = iota

	// ModeErubi reproduces the erubi gem's Erubi::Engine whitespace/trim
	// semantics (default engine options: trim on, escape off), so consumers that
	// render through erubi — Sinatra, Rails — get byte-identical output. It
	// differs from every classic ERB trim_mode: a standalone "<%= x %>\n" keeps
	// its trailing newline (unlike trim_mode "<>", which trims it), while a line
	// holding only a "<% ... %>" code (or "<%# ... %>" comment) tag is trimmed
	// automatically (unlike the default, and unlike trim_mode "-", which needs
	// the explicit "<%- ... -%>" form). "-%>" / "=%>" chomps the trailing newline
	// on an expression, and "<%==" HTML-escapes. In this mode TrimMode is ignored
	// (erubi has no trim_mode); EOutVar still selects the buffer variable name.
	ModeErubi
)

type Options

type Options struct {
	// Mode selects the ERB dialect. The zero value, ModeERB, is classic MRI ERB
	// and leaves every existing consumer unchanged; ModeErubi opts in to
	// erubi-compatible output.
	Mode Mode

	// TrimMode is MRI's trim_mode string. The recognised characters are "-",
	// ">", "<>" and "%", and the one- or two-character combinations MRI accepts
	// (e.g. "%-", "%>", "%<>"). An empty string means no trimming. Of the
	// newline-trimming modes only one applies, in MRI's priority order
	// "-" > "<>" > ">"; "%" (percent-line mode) is independent and may combine
	// with any of them.
	TrimMode string

	// EOutVar names the output-buffer local variable used by the compiled
	// source. When empty it defaults to "_erbout", matching MRI.
	EOutVar string
}

Options configures Compile, mirroring the keyword arguments of MRI's ERB.new(str, trim_mode:, eoutvar:).

Jump to

Keyboard shortcuts

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