Repository navigation
feat(sinatra): file-based erb views + layouts - #166
Merged
Merged
Conversation
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>
This was referenced Jul 6, 2026
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Completes the Sinatra ERB templating started in #162. The
:symbol→views/<name>.erbfile 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
erb :viewrendersviews/layout.erbif present, wrapping the view; the layout's<%= yield %>returns the view's rendered String. A missing default layout is silently skipped (MRI'scatch(: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 raisesErrno::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
@ivarsand request helpers resolve), with the same render locals, and itsyieldreturns the view String via a nativeProcthreaded throughsinatraErbEval.Proof — MRI-identical through the rbgo binary
The webapp conformance oracle (
scripts/conformance/webapp) gains a second app with a realviews/dir (card.erb+layout.erb+alt.erb) exercising the default layout,layout: false, a custom layout,layout: true, an inline-String layout, and theErrno::ENOENTa missing named layout / view raises. Its golden is captured byte-for-byte from the realsinatragem 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)
<%= yield %>line: Sinatra 4.x renders via erubi, which keeps the newline after a lone<%= %>line; go-ruby-erb mirrors MRIERB<>trim, which removes it. The fixtures keepyieldinline (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.erb :partinside a view via a helper) and the:erbescape_html/ app-levelset :erb, layout:engine options remain scoped follow-ups.🤖 Generated with Claude Code