Skip to content

feat(sinatra): file-based erb views + layouts - #166

Merged
tannevaled merged 1 commit into
mainfrom
feat/sinatra-erb-layouts
Jul 6, 2026
Merged

tannevaled merged 1 commit into
mainfrom
feat/sinatra-erb-layouts

Conversation

@tannevaled

Copy link
Copy Markdown
Contributor

Completes the Sinatra ERB templating started in #162. The :symbol → views/<name>.erb file view path already landed in #162; this PR adds layouts, the other half real Sinatra apps depend on, rendered exactly as MRI Sinatra + Tilt does.

What lands

  • Default layout — erb :view renders views/layout.erb if present, wrapping the view; the layout's <%= yield %> returns the view's rendered String. A missing default layout is silently skipped (MRI's catch(:layout_missing)).
  • layout: false / nil — render the view with no layout.
  • layout: :name — a custom layout file; layout: true — the default layout explicitly. A missing named layout raises Errno::ENOENT (MRI eats the error only for the implicit default).
  • layout: "src" — an inline-String layout.

The layout is evaluated in the same handler binding as the view (so @ivars and request helpers resolve), with the same render locals, and its yield returns the view String via a native Proc threaded through sinatraErbEval.

Proof — MRI-identical through the rbgo binary

The webapp conformance oracle (scripts/conformance/webapp) gains a second app with a real views/ dir (card.erb + layout.erb + alt.erb) exercising the default layout, layout: false, a custom layout, layout: true, an inline-String layout, and the Errno::ENOENT a missing named layout / view raises. Its golden is captured byte-for-byte from the real sinatra gem 4.2.1 + erubi (ruby 4.0.5) and reproduced exactly by rbgo (TestSinatraErbGemOracle).

Internal unit tests cover every new binding branch at 100% (TestSinatraErbLayout, TestSinatraErbLayoutMissing). GOWORK=off go build ./... && go vet ./... && go test ./... all green.

Deferred (precisely)

  • Trim parity of a standalone <%= yield %> line: Sinatra 4.x renders via erubi, which keeps the newline after a lone <%= %> line; go-ruby-erb mirrors MRI ERB <> trim, which removes it. The fixtures keep yield inline (the documented agreeing subset) so the diff is exact. Full erubi trim parity is a go-ruby-erb library follow-up, not a binding gap.
  • Partials (composing erb :part inside a view via a helper) and the :erb escape_html / app-level set :erb, layout: engine options remain scoped follow-ups.
  • Other template engines (haml/slim/etc.) are out of scope.

🤖 Generated with Claude Code

Complete the Sinatra ERB templating begun in #162. The :symbol -> file
view path (views/<name>.erb) already landed; this adds the layout rule
real Sinatra apps depend on, exactly as MRI Sinatra + Tilt render it:

  - `erb :view` renders views/layout.erb (if present) with the view's
    rendered String exposed as `yield`, so the layout's `<%= yield %>`
    interpolates the inner content;
  - a missing *default* layout is silently skipped (MRI's
    catch(:layout_missing));
  - `layout: false`/`nil` renders the view with no layout;
  - `layout: :name` selects a custom layout file (and `layout: true` the
    default) — a missing *named* layout raises Errno::ENOENT, since MRI
    eats the error only for the implicit default;
  - `layout: "src"` uses an inline-String layout.

The layout is evaluated in the same handler binding as the view, with the
same render locals, and its `yield` returns the view String via a native
Proc block threaded through sinatraErbEval.

Proven MRI-identical through the rbgo binary: the webapp conformance
oracle (scripts/conformance/webapp) gains a second app with a real
views/ dir (card.erb + layout.erb + alt.erb) exercising the default
layout, layout: false, a custom layout, layout: true, an inline-String
layout, and the Errno::ENOENT a missing named layout / view raises; its
golden is captured byte-for-byte from the real sinatra gem 4.2.1 +
erubi (ruby 4.0.5) and reproduced exactly by rbgo. Internal unit tests
cover every new binding branch at 100%.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@tannevaled
tannevaled merged commit 9c5401a into main Jul 6, 2026
9 checks passed
@tannevaled
tannevaled deleted the feat/sinatra-erb-layouts branch July 6, 2026 14:52
tannevaled added a commit that referenced this pull request Jul 6, 2026
Sinatra 4.x maps :erb to Tilt::ErubiTemplate, so real Sinatra renders
views through the erubi gem's Erubi::Engine, not classic MRI ERB. rbgo's
Sinatra `erb` helper compiled through go-ruby-erb's default (classic ERB)
mode, so a standalone `<%= yield %>` line dropped its trailing newline
(classic trim "<>") where erubi keeps it — the fidelity gap flagged in the
Sinatra file-views work (#166).

Select go-ruby-erb's new erb.ModeErubi (byte-identical to Erubi::Engine)
for the Sinatra view render only (internal/vm/sinatra_erb.go). The
standalone `require "erb"` / classic-ERB binding (internal/vm/erb.go)
stays in the default ModeERB, unchanged.

Prove it through the rbgo binary: the Sinatra+ERB webapp oracle gains a
layout whose `<%= yield %>` stands alone on its own line (previously kept
inline to dodge the classic-ERB gap). The golden was regenerated from the
real sinatra gem 4.2.1 + erubi 1.13.1; rbgo now reproduces it byte-for-byte.
Reverting to classic "<>" mode fails that request, confirming the fix is
load-bearing. Classic ERB conformance is unchanged; sinatra_erb.go holds
100% coverage.

- go-ruby-erb: v0.0.0-20260629074717 -> v0.0.0-20260706152133 (ModeErubi)

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant