homura-runtime 0.3.7 → 0.3.9
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +17 -0
- data/exe/auto-await +23 -23
- data/exe/compile-assets +68 -61
- data/exe/compile-erb +263 -255
- data/exe/homura-build +74 -21
- data/lib/homura/runtime/ai.rb +104 -85
- data/lib/homura/runtime/async_registry.rb +124 -109
- data/lib/homura/runtime/auto_await/analyzer.rb +28 -15
- data/lib/homura/runtime/auto_await/transformer.rb +1 -0
- data/lib/homura/runtime/build_support.rb +90 -11
- data/lib/homura/runtime/cache.rb +21 -18
- data/lib/homura/runtime/durable_object.rb +27 -17
- data/lib/homura/runtime/email.rb +14 -14
- data/lib/homura/runtime/http.rb +4 -3
- data/lib/homura/runtime/multipart.rb +11 -4
- data/lib/homura/runtime/queue.rb +53 -23
- data/lib/homura/runtime/scheduled.rb +12 -14
- data/lib/homura/runtime/stream.rb +18 -14
- data/lib/homura/runtime/version.rb +1 -1
- data/lib/homura/runtime.rb +148 -91
- data/lib/homura_vendor_tempfile.rb +4 -2
- data/lib/homura_vendor_tilt.rb +5 -3
- data/lib/homura_vendor_zlib.rb +3 -0
- data/lib/opal_patches.rb +83 -66
- metadata +1 -1
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
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
138
|
-
|
|
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
|
-
|
|
145
|
-
|
|
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
|
-
|
|
152
|
+
yield_with_args_fragment?(stripped)
|
|
159
153
|
return
|
|
160
154
|
end
|
|
161
155
|
|
|
162
|
-
raise
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
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
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
+
def registered?(name)
|
|
325
|
+
@templates.key?(name.to_sym)
|
|
326
|
+
end
|
|
324
327
|
|
|
325
|
-
|
|
326
|
-
|
|
328
|
+
def names
|
|
329
|
+
@templates.keys
|
|
330
|
+
end
|
|
327
331
|
end
|
|
328
332
|
end
|
|
329
|
-
|
|
330
|
-
|
|
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
|
|
340
|
-
io.puts
|
|
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
|
|
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
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
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
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
stack
|
|
393
|
-
|
|
394
|
-
|
|
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
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
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
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
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
|
-
|
|
432
|
-
|
|
433
|
-
|
|
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
|
-
|
|
442
|
-
|
|
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
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
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
|
-
|
|
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
|
-
|
|
479
|
-
|
|
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
|
-
|
|
482
|
-
|
|
483
|
-
|
|
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
|
-
|
|
488
|
-
|
|
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
|
|
497
|
-
exit
|
|
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
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
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
|
-
|
|
530
|
-
|
|
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
|
|
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
|
-
|
|
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)
|