homura-runtime 0.3.8 → 0.3.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/exe/compile-erb CHANGED
@@ -75,22 +75,21 @@ module HomuraERB
75
75
  # comments are vanishingly rare in real templates. Anything more
76
76
  # ambitious would need a Ruby parser, which is overkill for the
77
77
  # `@@ivar` shape we actually have to handle here.
78
- CVAR_OP_ASSIGN_RE =
79
- %r{(?<![@\w])@@([A-Za-z_]\w*)\s*(\|\||&&|\+|-|\*|/|%|\*\*|<<|>>|\||&|\^)=\s*([^;\n]+)}.freeze
78
+ CVAR_OP_ASSIGN_RE = %r{(?<![@\w])@@([A-Za-z_]\w*)\s*(\|\||&&|\+|-|\*|/|%|\*\*|<<|>>|\||&|\^)=\s*([^;\n]+)}.freeze
80
79
  CVAR_ASSIGN_RE = /(?<![@\w])@@([A-Za-z_]\w*)\s*=(?!=)\s*([^;\n]+)/.freeze
81
80
  # Read pattern excludes `@@name` that appears on the left-hand side
82
81
  # of an assignment (`@@name =`, `@@name +=`, `@@name ||=`, etc.) so
83
82
  # the assignment passes still see the raw token to rewrite. The
84
83
  # `=(?!=)` guard keeps `==` (comparison) treated as a read.
85
- CVAR_READ_RE =
86
- %r{
84
+ CVAR_READ_RE = %r{
87
85
  (?<![@\w])@@([A-Za-z_]\w*)\b
88
86
  (?!
89
87
  \s*
90
88
  (?:\|\||&&|\+|-|\*|/|%|\*\*|<<|>>|\||&|\^)?
91
89
  =(?!=)
92
90
  )
93
- }x.freeze
91
+ }x
92
+ .freeze
94
93
 
95
94
  CVAR_PLACEHOLDER_PREFIX = "\x01HOMURA_CVAR\x01".freeze
96
95
  CVAR_PLACEHOLDER_SUFFIX = "\x01".freeze
@@ -99,11 +98,10 @@ module HomuraERB
99
98
  return fragment unless fragment.include?("@@")
100
99
 
101
100
  placeholders = []
102
- record =
103
- lambda do |replacement|
104
- placeholders << replacement
105
- "#{CVAR_PLACEHOLDER_PREFIX}#{placeholders.length - 1}#{CVAR_PLACEHOLDER_SUFFIX}"
106
- end
101
+ record = lambda do |replacement|
102
+ placeholders << replacement
103
+ "#{CVAR_PLACEHOLDER_PREFIX}#{placeholders.length - 1}#{CVAR_PLACEHOLDER_SUFFIX}"
104
+ end
107
105
 
108
106
  # Reads first: rewrite every `@@name` that is *not* the target of
109
107
  # an assignment, including occurrences on the right-hand side of
@@ -111,68 +109,70 @@ module HomuraERB
111
109
  # later assignment passes see the original `@@name = ...` shape
112
110
  # (without their RHS reads being clobbered) and so the final
113
111
  # output never re-enters this same rewrite loop.
114
- work =
115
- fragment.gsub(CVAR_READ_RE) do
116
- name = Regexp.last_match(1)
117
- record.call("self.class.class_variable_get(:@@#{name})")
118
- end
112
+ work = fragment.gsub(CVAR_READ_RE) do
113
+ name = Regexp.last_match(1)
114
+ record.call("self.class.class_variable_get(:@@#{name})")
115
+ end
119
116
 
120
- work =
121
- work.gsub(CVAR_OP_ASSIGN_RE) do
122
- name = Regexp.last_match(1)
123
- op = Regexp.last_match(2)
124
- value = Regexp.last_match(3)
125
- replacement =
126
- case op
127
- when "||"
128
- "self.class.class_variable_set(:@@#{name}, (self.class.class_variable_defined?(:@@#{name}) ? self.class.class_variable_get(:@@#{name}) : nil) || (#{value}))"
129
- when "&&"
130
- "self.class.class_variable_set(:@@#{name}, (self.class.class_variable_defined?(:@@#{name}) ? self.class.class_variable_get(:@@#{name}) : nil) && (#{value}))"
131
- else
132
- "self.class.class_variable_set(:@@#{name}, self.class.class_variable_get(:@@#{name}) #{op} (#{value}))"
133
- end
134
- record.call(replacement)
117
+ work = work.gsub(CVAR_OP_ASSIGN_RE) do
118
+ name = Regexp.last_match(1)
119
+ op = Regexp.last_match(2)
120
+ value = Regexp.last_match(3)
121
+ replacement = case op
122
+ when "||"
123
+ "self.class.class_variable_set(:@@#{name}, (self.class.class_variable_defined?(:@@#{name}) ? self.class.class_variable_get(:@@#{name}) : nil) || (#{value}))"
124
+ when "&&"
125
+ "self.class.class_variable_set(:@@#{name}, (self.class.class_variable_defined?(:@@#{name}) ? self.class.class_variable_get(:@@#{name}) : nil) && (#{value}))"
126
+ else
127
+ "self.class.class_variable_set(:@@#{name}, self.class.class_variable_get(:@@#{name}) #{op} (#{value}))"
135
128
  end
136
129
 
137
- work =
138
- work.gsub(CVAR_ASSIGN_RE) do
139
- name = Regexp.last_match(1)
140
- value = Regexp.last_match(2)
141
- record.call("self.class.class_variable_set(:@@#{name}, (#{value}))")
142
- end
130
+ record.call(replacement)
131
+ end
143
132
 
144
- placeholder_re =
145
- /#{Regexp.escape(CVAR_PLACEHOLDER_PREFIX)}(\d+)#{Regexp.escape(CVAR_PLACEHOLDER_SUFFIX)}/
133
+ work = work.gsub(CVAR_ASSIGN_RE) do
134
+ name = Regexp.last_match(1)
135
+ value = Regexp.last_match(2)
136
+ record.call("self.class.class_variable_set(:@@#{name}, (#{value}))")
137
+ end
138
+
139
+ placeholder_re = /#{Regexp.escape(CVAR_PLACEHOLDER_PREFIX)}(\d+)#{Regexp.escape(CVAR_PLACEHOLDER_SUFFIX)}/
146
140
  loop do
147
- replaced =
148
- work.gsub(placeholder_re) { placeholders[Regexp.last_match(1).to_i] }
141
+ replaced = work.gsub(placeholder_re) { placeholders[Regexp.last_match(1).to_i] }
149
142
  break if replaced == work
150
143
  work = replaced
151
144
  end
145
+
152
146
  work
153
147
  end
154
148
 
155
149
  def validate_code_fragment!(fragment)
156
150
  stripped = fragment.strip
157
151
  unless supported_yield_fragment?(stripped) ||
158
- yield_with_args_fragment?(stripped)
152
+ yield_with_args_fragment?(stripped)
159
153
  return
160
154
  end
161
155
 
162
- raise <<~MSG.strip
163
- Unsupported ERB yield form `<% #{stripped} %>`.
164
- Use `<%= yield %>` or `<%== yield %>` in layout templates.
165
- MSG
156
+ raise(
157
+ <<~MSG
158
+ Unsupported ERB yield form `<% #{stripped} %>`.
159
+ Use `<%= yield %>` or `<%== yield %>` in layout templates.
160
+ MSG
161
+ .strip
162
+ )
166
163
  end
167
164
 
168
165
  def validate_expression_fragment!(fragment)
169
166
  stripped = fragment.strip
170
167
  return unless yield_with_args_fragment?(stripped)
171
168
 
172
- raise <<~MSG.strip
173
- Unsupported ERB yield form `<%= #{stripped} %>`.
174
- `yield(arg)` is not supported; use `<%= yield %>` or `<%== yield %>`, or pass data via ivars/locals instead.
175
- MSG
169
+ raise(
170
+ <<~MSG
171
+ Unsupported ERB yield form `<%= #{stripped} %>`.
172
+ `yield(arg)` is not supported; use `<%= yield %>` or `<%== yield %>`, or pass data via ivars/locals instead.
173
+ MSG
174
+ .strip
175
+ )
176
176
  end
177
177
 
178
178
  # Compile an ERB source string to a Ruby method body that assembles
@@ -231,6 +231,7 @@ module HomuraERB
231
231
 
232
232
  cursor = close_idx + 2
233
233
  end
234
+
234
235
  ruby << "_out\n"
235
236
  ruby
236
237
  end
@@ -265,69 +266,72 @@ def template_name_for(path, root)
265
266
  root_with_sep = root.end_with?("/") ? root : "#{root}/"
266
267
  rel = rel[root_with_sep.length..] if rel.start_with?(root_with_sep)
267
268
  end
269
+
268
270
  rel = File.basename(rel) unless rel.include?("/")
269
271
  rel.delete_suffix(".erb").to_sym
270
272
  end
271
273
 
272
274
  def emit_header(io, namespace)
273
- io.puts <<~RUBY
274
- # frozen_string_literal: true
275
- #
276
- # Auto-generated by bin/compile-erb — DO NOT EDIT BY HAND.
277
- #
278
- # Run `bin/compile-erb` after modifying anything under views/ to
279
- # regenerate. The Opal build command picks this file up via
280
- # `-I build -r homura_templates` (or your chosen `--output` basename).
281
- #
282
- # Each registered template is a Proc that returns an HTML String.
283
- # The Proc is `instance_exec`'d on the current Sinatra instance so
284
- # `@ivars`, `params`, `request`, and any Sinatra helper remain
285
- # accessible exactly as they would in a stock Sinatra route.
286
- #
287
- # Rendering is wired to `Sinatra::Templates#erb` further down so
288
- # `erb :index` in user code dispatches here transparently. No
289
- # runtime eval, no `new Function`, no Workers sandbox complaints.
290
-
291
- module #{namespace}
292
- @templates = {}
293
-
294
- class << self
295
- def register(name, &body)
296
- @templates[name.to_sym] = body
297
- end
275
+ io.puts(
276
+ <<~RUBY
277
+ # frozen_string_literal: true
278
+ #
279
+ # Auto-generated by bin/compile-erb — DO NOT EDIT BY HAND.
280
+ #
281
+ # Run `bin/compile-erb` after modifying anything under views/ to
282
+ # regenerate. The Opal build command picks this file up via
283
+ # `-I build -r homura_templates` (or your chosen `--output` basename).
284
+ #
285
+ # Each registered template is a Proc that returns an HTML String.
286
+ # The Proc is `instance_exec`'d on the current Sinatra instance so
287
+ # `@ivars`, `params`, `request`, and any Sinatra helper remain
288
+ # accessible exactly as they would in a stock Sinatra route.
289
+ #
290
+ # Rendering is wired to `Sinatra::Templates#erb` further down so
291
+ # `erb :index` in user code dispatches here transparently. No
292
+ # runtime eval, no `new Function`, no Workers sandbox complaints.
293
+
294
+ module #{namespace}
295
+ @templates = {}
296
+
297
+ class << self
298
+ def register(name, &body)
299
+ @templates[name.to_sym] = body
300
+ end
298
301
 
299
- def render(name, instance, locals = {}, &block)
300
- body = @templates[name.to_sym]
301
- raise "#{namespace}: no template registered for \#{name.inspect}" unless body
302
- previous_block = instance.instance_variable_get(:@__homura_template_block__)
303
- previous_locals = instance.instance_variable_get(:@__homura_template_locals__)
304
- begin
305
- instance.instance_variable_set(:@__homura_template_block__, block)
306
- # Stack locals so nested `erb :_partial, locals: {...}` calls
307
- # restore the outer scope on the way out — Sinatra's standard
308
- # `locals[:t]` -> bare `t` lookup is implemented via
309
- # method_missing on the Sinatra instance (see Sinatra::Base
310
- # patch installed by emit_sinatra_patch).
311
- stack = previous_locals.is_a?(::Array) ? previous_locals.dup : []
312
- stack.push(locals || {})
313
- instance.instance_variable_set(:@__homura_template_locals__, stack)
314
- instance.instance_exec(locals, &body)
315
- ensure
316
- instance.instance_variable_set(:@__homura_template_block__, previous_block)
317
- instance.instance_variable_set(:@__homura_template_locals__, previous_locals)
302
+ def render(name, instance, locals = {}, &block)
303
+ body = @templates[name.to_sym]
304
+ raise "#{namespace}: no template registered for \#{name.inspect}" unless body
305
+ previous_block = instance.instance_variable_get(:@__homura_template_block__)
306
+ previous_locals = instance.instance_variable_get(:@__homura_template_locals__)
307
+ begin
308
+ instance.instance_variable_set(:@__homura_template_block__, block)
309
+ # Stack locals so nested `erb :_partial, locals: {...}` calls
310
+ # restore the outer scope on the way out — Sinatra's standard
311
+ # `locals[:t]` -> bare `t` lookup is implemented via
312
+ # method_missing on the Sinatra instance (see Sinatra::Base
313
+ # patch installed by emit_sinatra_patch).
314
+ stack = previous_locals.is_a?(::Array) ? previous_locals.dup : []
315
+ stack.push(locals || {})
316
+ instance.instance_variable_set(:@__homura_template_locals__, stack)
317
+ instance.instance_exec(locals, &body)
318
+ ensure
319
+ instance.instance_variable_set(:@__homura_template_block__, previous_block)
320
+ instance.instance_variable_set(:@__homura_template_locals__, previous_locals)
321
+ end
318
322
  end
319
- end
320
323
 
321
- def registered?(name)
322
- @templates.key?(name.to_sym)
323
- end
324
+ def registered?(name)
325
+ @templates.key?(name.to_sym)
326
+ end
324
327
 
325
- def names
326
- @templates.keys
328
+ def names
329
+ @templates.keys
330
+ end
327
331
  end
328
332
  end
329
- end
330
- RUBY
333
+ RUBY
334
+ )
331
335
  end
332
336
 
333
337
  def emit_templates(io, inputs, namespace, root)
@@ -336,156 +340,158 @@ def emit_templates(io, inputs, namespace, root)
336
340
  source = File.read(path)
337
341
  compiled = HomuraERB.compile(source)
338
342
  io.puts
339
- io.puts "# ---- #{path} -> :#{name.inspect} --------------------------------------"
340
- io.puts HomuraERB.wrap(name, compiled, namespace)
343
+ io.puts("# ---- #{path} -> :#{name.inspect} --------------------------------------")
344
+ io.puts(HomuraERB.wrap(name, compiled, namespace))
341
345
  end
342
346
  end
343
347
 
344
348
  def emit_sinatra_patch(io, namespace)
345
- io.puts <<~RUBY
346
-
347
- # ---------------------------------------------------------------------
348
- # Sinatra hook: `erb :name` dispatches to #{namespace}.render.
349
- # ---------------------------------------------------------------------
350
- #
351
- # This file is loaded by Opal via `-r homura_templates` before
352
- # `app/hello.rb`, so Sinatra is not yet loaded at require time.
353
- # Pull it in explicitly and reopen `Sinatra::Templates` to install
354
- # our ERB dispatcher. User code's own `require 'sinatra/base'`
355
- # later becomes a no-op (already cached).
356
- require 'sinatra/base'
357
-
358
- module ::Sinatra
359
- module Templates
360
- def __homura_template_yield__
361
- block = @__homura_template_block__
362
- raise LocalJumpError, 'no block given' unless block
363
-
364
- block.call
365
- end
349
+ io.puts(
350
+ <<~RUBY
366
351
 
367
- # Resolve a Sinatra-style local variable reference.
368
- # `erb :_partial, locals: { t: row }` causes the precompiled body
369
- # to reference a bare `t`. Stock Sinatra uses Ruby's `binding`
370
- # eval to install locals into the template's scope; we don't have
371
- # `binding` under Opal/Workers, so we hand-roll the lookup
372
- # through method_missing against the locals stack maintained by
373
- # the renderer.
374
- def __homura_template_local__(name)
375
- stack = @__homura_template_locals__
376
- return nil unless stack.is_a?(::Array)
377
- key = name.to_sym
378
- stack.reverse_each do |frame|
379
- next unless frame.is_a?(::Hash)
380
- return frame[key] if frame.key?(key)
381
- sk = name.to_s
382
- return frame[sk] if frame.key?(sk)
352
+ # ---------------------------------------------------------------------
353
+ # Sinatra hook: `erb :name` dispatches to #{namespace}.render.
354
+ # ---------------------------------------------------------------------
355
+ #
356
+ # This file is loaded by Opal via `-r homura_templates` before
357
+ # `app/hello.rb`, so Sinatra is not yet loaded at require time.
358
+ # Pull it in explicitly and reopen `Sinatra::Templates` to install
359
+ # our ERB dispatcher. User code's own `require 'sinatra/base'`
360
+ # later becomes a no-op (already cached).
361
+ require 'sinatra/base'
362
+
363
+ module ::Sinatra
364
+ module Templates
365
+ def __homura_template_yield__
366
+ block = @__homura_template_block__
367
+ raise LocalJumpError, 'no block given' unless block
368
+
369
+ block.call
383
370
  end
384
- nil
385
- end
386
371
 
387
- def __homura_template_local_defined?(name)
388
- stack = @__homura_template_locals__
389
- return false unless stack.is_a?(::Array)
390
- key = name.to_sym
391
- sk = name.to_s
392
- stack.reverse_each do |frame|
393
- next unless frame.is_a?(::Hash)
394
- return true if frame.key?(key) || frame.key?(sk)
372
+ # Resolve a Sinatra-style local variable reference.
373
+ # `erb :_partial, locals: { t: row }` causes the precompiled body
374
+ # to reference a bare `t`. Stock Sinatra uses Ruby's `binding`
375
+ # eval to install locals into the template's scope; we don't have
376
+ # `binding` under Opal/Workers, so we hand-roll the lookup
377
+ # through method_missing against the locals stack maintained by
378
+ # the renderer.
379
+ def __homura_template_local__(name)
380
+ stack = @__homura_template_locals__
381
+ return nil unless stack.is_a?(::Array)
382
+ key = name.to_sym
383
+ stack.reverse_each do |frame|
384
+ next unless frame.is_a?(::Hash)
385
+ return frame[key] if frame.key?(key)
386
+ sk = name.to_s
387
+ return frame[sk] if frame.key?(sk)
388
+ end
389
+ nil
395
390
  end
396
- false
397
- end
398
- end
399
- end
400
391
 
401
- # Method-missing fallback that surfaces template locals as bare names.
402
- # Installed on Sinatra::Base so `<%= t['title'] %>` inside a
403
- # precompiled partial Just Works without the user having to write
404
- # `<%= locals[:t]['title'] %>`.
405
- class ::Sinatra::Base
406
- def method_missing(name, *args, &block)
407
- if args.empty? && block.nil? && __homura_template_local_defined?(name)
408
- return __homura_template_local__(name)
392
+ def __homura_template_local_defined?(name)
393
+ stack = @__homura_template_locals__
394
+ return false unless stack.is_a?(::Array)
395
+ key = name.to_sym
396
+ sk = name.to_s
397
+ stack.reverse_each do |frame|
398
+ next unless frame.is_a?(::Hash)
399
+ return true if frame.key?(key) || frame.key?(sk)
400
+ end
401
+ false
402
+ end
409
403
  end
410
- super
411
404
  end
412
405
 
413
- def respond_to_missing?(name, include_private = false)
414
- return true if __homura_template_local_defined?(name)
415
- super
416
- end
417
- end
418
-
419
- module ::Sinatra
420
- module Templates
421
-
422
- def __homura_default_template_block_for__(template)
423
- case template.to_sym
424
- when :layout
425
- proc { @content } if defined?(@content)
426
- when :layout_docs
427
- proc { @docs_inner } if defined?(@docs_inner)
406
+ # Method-missing fallback that surfaces template locals as bare names.
407
+ # Installed on Sinatra::Base so `<%= t['title'] %>` inside a
408
+ # precompiled partial Just Works without the user having to write
409
+ # `<%= locals[:t]['title'] %>`.
410
+ class ::Sinatra::Base
411
+ def method_missing(name, *args, &block)
412
+ if args.empty? && block.nil? && __homura_template_local_defined?(name)
413
+ return __homura_template_local__(name)
428
414
  end
415
+ super
429
416
  end
430
417
 
431
- # A template is "layout-like" if its name is `:layout` or starts
432
- # with `layout_` (e.g. `:layout_docs`). The auto-layout pass in
433
- # `erb` skips wrapping such templates in `:layout` so that
434
- # rendering `:layout_docs` doesn't end up nested inside `:layout`,
435
- # which would otherwise produce a duplicate header / nav.
436
- def __homura_layout_template?(template)
437
- name = template.to_sym
438
- name == :layout || name.to_s.start_with?('layout_')
418
+ def respond_to_missing?(name, include_private = false)
419
+ return true if __homura_template_local_defined?(name)
420
+ super
439
421
  end
422
+ end
440
423
 
441
- # Rails-/Sinatra-flavoured partial convention: a template name
442
- # starting with `_` is treated as a partial (e.g. `_docs_nav`),
443
- # so the auto-layout pass leaves it alone. Without this, calling
444
- # `<%= erb :_docs_nav %>` from inside a layout template would
445
- # rewrap the partial in `:layout`, doubling header/nav output.
446
- def __homura_partial_template?(template)
447
- template.to_s.start_with?('_')
448
- end
424
+ module ::Sinatra
425
+ module Templates
449
426
 
450
- # homura patch: dispatch to precompiled templates when we
451
- # have one. Unknown symbols raise a clear message instead of
452
- # wandering into upstream Tilt, which would blow up on
453
- # Workers with "Code generation from strings disallowed".
454
- #
455
- # Sinatra's idiom `erb :foo, locals: { x: 1 }` passes locals via
456
- # `options[:locals]` rather than the third positional argument;
457
- # merge them so both forms work.
458
- def erb(template, options = {}, locals = {}, &block)
459
- template = template.to_sym if template.is_a?(::String)
460
- if template.is_a?(::Symbol) && ::#{namespace}.registered?(template)
461
- locals = (options[:locals] || {}).merge(locals || {})
462
- block ||= __homura_default_template_block_for__(template)
463
- output = ::#{namespace}.render(template, self, locals, &block)
464
-
465
- layout = options[:layout]
466
- layout = false if layout.nil? && options.include?(:layout)
467
- if layout.nil? &&
468
- !__homura_layout_template?(template) &&
469
- !__homura_partial_template?(template) &&
470
- ::#{namespace}.registered?(:layout)
471
- layout = :layout
427
+ def __homura_default_template_block_for__(template)
428
+ case template.to_sym
429
+ when :layout
430
+ proc { @content } if defined?(@content)
431
+ when :layout_docs
432
+ proc { @docs_inner } if defined?(@docs_inner)
472
433
  end
473
- if layout
474
- layout = :layout if layout == true
475
- layout = layout.to_sym
476
- raise "homura: layout \#{layout.inspect} not precompiled; run bin/compile-erb" unless ::#{namespace}.registered?(layout)
434
+ end
477
435
 
478
- return ::#{namespace}.render(layout, self, locals) { output }
479
- end
436
+ # A template is "layout-like" if its name is `:layout` or starts
437
+ # with `layout_` (e.g. `:layout_docs`). The auto-layout pass in
438
+ # `erb` skips wrapping such templates in `:layout` so that
439
+ # rendering `:layout_docs` doesn't end up nested inside `:layout`,
440
+ # which would otherwise produce a duplicate header / nav.
441
+ def __homura_layout_template?(template)
442
+ name = template.to_sym
443
+ name == :layout || name.to_s.start_with?('layout_')
444
+ end
445
+
446
+ # Rails-/Sinatra-flavoured partial convention: a template name
447
+ # starting with `_` is treated as a partial (e.g. `_docs_nav`),
448
+ # so the auto-layout pass leaves it alone. Without this, calling
449
+ # `<%= erb :_docs_nav %>` from inside a layout template would
450
+ # rewrap the partial in `:layout`, doubling header/nav output.
451
+ def __homura_partial_template?(template)
452
+ template.to_s.start_with?('_')
453
+ end
480
454
 
481
- output
482
- else
483
- raise "homura: erb \#{template.inspect} not precompiled; run bin/compile-erb"
455
+ # homura patch: dispatch to precompiled templates when we
456
+ # have one. Unknown symbols raise a clear message instead of
457
+ # wandering into upstream Tilt, which would blow up on
458
+ # Workers with "Code generation from strings disallowed".
459
+ #
460
+ # Sinatra's idiom `erb :foo, locals: { x: 1 }` passes locals via
461
+ # `options[:locals]` rather than the third positional argument;
462
+ # merge them so both forms work.
463
+ def erb(template, options = {}, locals = {}, &block)
464
+ template = template.to_sym if template.is_a?(::String)
465
+ if template.is_a?(::Symbol) && ::#{namespace}.registered?(template)
466
+ locals = (options[:locals] || {}).merge(locals || {})
467
+ block ||= __homura_default_template_block_for__(template)
468
+ output = ::#{namespace}.render(template, self, locals, &block)
469
+
470
+ layout = options[:layout]
471
+ layout = false if layout.nil? && options.include?(:layout)
472
+ if layout.nil? &&
473
+ !__homura_layout_template?(template) &&
474
+ !__homura_partial_template?(template) &&
475
+ ::#{namespace}.registered?(:layout)
476
+ layout = :layout
477
+ end
478
+ if layout
479
+ layout = :layout if layout == true
480
+ layout = layout.to_sym
481
+ raise "homura: layout \#{layout.inspect} not precompiled; run bin/compile-erb" unless ::#{namespace}.registered?(layout)
482
+
483
+ return ::#{namespace}.render(layout, self, locals) { output }
484
+ end
485
+
486
+ output
487
+ else
488
+ raise "homura: erb \#{template.inspect} not precompiled; run bin/compile-erb"
489
+ end
484
490
  end
485
491
  end
486
492
  end
487
- end
488
- RUBY
493
+ RUBY
494
+ )
489
495
  end
490
496
 
491
497
  # ---------------------------------------------------------------------------
@@ -493,8 +499,8 @@ end
493
499
  # ---------------------------------------------------------------------------
494
500
 
495
501
  if ARGV.include?("-h") || ARGV.include?("--help")
496
- puts HELP
497
- exit 0
502
+ puts(HELP)
503
+ exit(0)
498
504
  end
499
505
 
500
506
  options = {
@@ -504,38 +510,39 @@ options = {
504
510
  stdout: false
505
511
  }
506
512
 
507
- parser =
508
- OptionParser.new do |op|
509
- op.banner = "Usage: bin/compile-erb [options] [--] [files...]"
510
- op.on("--input DIR", "Directory containing *.erb templates") do |d|
511
- options[:input_dir] = d
512
- end
513
- op.on("--output PATH", "Write generated Ruby to PATH") do |p|
514
- options[:output] = p
515
- end
516
- op.on("--namespace NAME", "Ruby module name for the registry") do |n|
517
- options[:namespace] = n
518
- end
519
- op.on("--stdout", "Write generated Ruby to stdout") do
520
- options[:stdout] = true
521
- end
513
+ parser = OptionParser.new do |op|
514
+ op.banner = "Usage: bin/compile-erb [options] [--] [files...]"
515
+ op.on("--input DIR", "Directory containing *.erb templates") do |d|
516
+ options[:input_dir] = d
517
+ end
518
+
519
+ op.on("--output PATH", "Write generated Ruby to PATH") do |p|
520
+ options[:output] = p
521
+ end
522
+
523
+ op.on("--namespace NAME", "Ruby module name for the registry") do |n|
524
+ options[:namespace] = n
522
525
  end
526
+
527
+ op.on("--stdout", "Write generated Ruby to stdout") do
528
+ options[:stdout] = true
529
+ end
530
+ end
531
+
523
532
  parser.parse!
524
533
 
525
534
  positional = ARGV.dup
526
535
  namespace = options[:namespace]
527
536
 
528
- inputs =
529
- if positional.any?
530
- positional.flat_map do |a|
531
- File.directory?(a) ? Dir.glob(File.join(a, "**", "*.erb")).sort : [a]
532
- end
533
- else
534
- default_inputs_for(options[:input_dir])
537
+ inputs = if positional.any?
538
+ positional.flat_map do |a|
539
+ File.directory?(a) ? Dir.glob(File.join(a, "**", "*.erb")).sort : [a]
535
540
  end
541
+ else
542
+ default_inputs_for(options[:input_dir])
543
+ end
536
544
 
537
- template_root =
538
- positional.find { |a| File.directory?(a) } || options[:input_dir]
545
+ template_root = positional.find { |a| File.directory?(a) } || options[:input_dir]
539
546
 
540
547
  write_file = !options[:stdout] && (positional.empty? || options[:output])
541
548
  out_path = options[:output]
@@ -544,7 +551,7 @@ out_path = options[:output]
544
551
  # that do not ship sinatra-homura at all. Emit the empty registry, but
545
552
  # skip the Sinatra hook so `homura-runtime` remains Sinatra-free.
546
553
  if inputs.empty?
547
- warn "compile-erb: no .erb files under #{options[:input_dir]}/ — writing empty registry"
554
+ warn("compile-erb: no .erb files under #{options[:input_dir]}/ — writing empty registry")
548
555
  end
549
556
 
550
557
  if write_file
@@ -554,7 +561,8 @@ if write_file
554
561
  emit_templates(io, inputs, namespace, template_root)
555
562
  emit_sinatra_patch(io, namespace) unless inputs.empty?
556
563
  end
557
- warn "compile-erb: wrote #{out_path} (#{inputs.size} templates)"
564
+
565
+ warn("compile-erb: wrote #{out_path} (#{inputs.size} templates)")
558
566
  else
559
567
  emit_header($stdout, namespace)
560
568
  emit_templates($stdout, inputs, namespace, template_root)