klenod-build 0.0.1
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 +7 -0
- data/README.md +29 -0
- data/lib/klenod/build/asset.rb +235 -0
- data/lib/klenod/build/asset_compression.rb +58 -0
- data/lib/klenod/build/asset_generation_queue.rb +50 -0
- data/lib/klenod/build/cli/application.rb +108 -0
- data/lib/klenod/build/cli.rb +3 -0
- data/lib/klenod/build/config.rb +142 -0
- data/lib/klenod/build/context.rb +370 -0
- data/lib/klenod/build/dependency.rb +21 -0
- data/lib/klenod/build/errors.rb +175 -0
- data/lib/klenod/build/filesystem_resolver.rb +147 -0
- data/lib/klenod/build/graph/invalidator.rb +246 -0
- data/lib/klenod/build/graph.rb +864 -0
- data/lib/klenod/build/graphviz.rb +282 -0
- data/lib/klenod/build/hashing.rb +21 -0
- data/lib/klenod/build/invalidation_result.rb +55 -0
- data/lib/klenod/build/load_result.rb +7 -0
- data/lib/klenod/build/loaded_module.rb +38 -0
- data/lib/klenod/build/module_id.rb +100 -0
- data/lib/klenod/build/module_record.rb +22 -0
- data/lib/klenod/build/plugin.rb +35 -0
- data/lib/klenod/build/plugins/class_names_runtime.rb +125 -0
- data/lib/klenod/build/plugins/component_defaults.rb +12 -0
- data/lib/klenod/build/plugins/data_plugin.rb +117 -0
- data/lib/klenod/build/plugins/gem_import_plugin.rb +125 -0
- data/lib/klenod/build/plugins/google_fonts_plugin/font_metrics.json +17687 -0
- data/lib/klenod/build/plugins/google_fonts_plugin/font_metrics.rb +105 -0
- data/lib/klenod/build/plugins/google_fonts_plugin/font_metrics.txt +31 -0
- data/lib/klenod/build/plugins/google_fonts_plugin.rb +387 -0
- data/lib/klenod/build/plugins/haml_plugin/companions.rb +40 -0
- data/lib/klenod/build/plugins/haml_plugin/errors.rb +114 -0
- data/lib/klenod/build/plugins/haml_plugin/helper_source.rb +123 -0
- data/lib/klenod/build/plugins/haml_plugin/parser.rb +85 -0
- data/lib/klenod/build/plugins/haml_plugin/transformer/ruby_builder.rb +1039 -0
- data/lib/klenod/build/plugins/haml_plugin/transformer.rb +766 -0
- data/lib/klenod/build/plugins/haml_plugin.rb +369 -0
- data/lib/klenod/build/plugins/image_plugin.rb +401 -0
- data/lib/klenod/build/plugins/intl_plugin.rb +34 -0
- data/lib/klenod/build/plugins/markdown_compiler.rb +180 -0
- data/lib/klenod/build/plugins/markdown_plugin.rb +161 -0
- data/lib/klenod/build/plugins/router_plugin.rb +942 -0
- data/lib/klenod/build/plugins/ruby_plugin.rb +34 -0
- data/lib/klenod/build/plugins/svg_plugin.rb +197 -0
- data/lib/klenod/build/plugins.rb +22 -0
- data/lib/klenod/build/profiler.rb +79 -0
- data/lib/klenod/build/resolution_error_formatter.rb +42 -0
- data/lib/klenod/build/resolver.rb +95 -0
- data/lib/klenod/build/ruby_import_rewriter.rb +436 -0
- data/lib/klenod/build/source_map/editor.rb +110 -0
- data/lib/klenod/build/source_map/map.rb +168 -0
- data/lib/klenod/build/source_map/vlq.rb +68 -0
- data/lib/klenod/build/source_map.rb +12 -0
- data/lib/klenod/build/transform_result.rb +12 -0
- data/lib/klenod/build/version.rb +7 -0
- data/lib/klenod/build/watched_pattern.rb +11 -0
- data/lib/klenod/build/watcher.rb +141 -0
- data/lib/klenod/build.rb +15 -0
- metadata +310 -0
|
@@ -0,0 +1,942 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../dependency"
|
|
4
|
+
require_relative "../errors"
|
|
5
|
+
require_relative "../module_id"
|
|
6
|
+
require_relative "../plugin"
|
|
7
|
+
require "klenod/runtime/source_map"
|
|
8
|
+
require_relative "../transform_result"
|
|
9
|
+
require_relative "../watched_pattern"
|
|
10
|
+
|
|
11
|
+
module Klenod
|
|
12
|
+
module Build
|
|
13
|
+
module Plugins
|
|
14
|
+
module RouterPlugin
|
|
15
|
+
def self.new(...)
|
|
16
|
+
Plugin.new(...)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class Plugin < Klenod::Build::Plugin
|
|
20
|
+
RouteSegment = Data.define(:name, :kind, :param_name, :path_part) do
|
|
21
|
+
def self.parse(name)
|
|
22
|
+
case name
|
|
23
|
+
when /\A\[\[\.\.\.(?<param_name>[A-Za-z_]\w*)\]\]\z/
|
|
24
|
+
new(name, :optional_catch_all, $~[:param_name], nil)
|
|
25
|
+
when /\A\[\.\.\.(?<param_name>[A-Za-z_]\w*)\]\z/
|
|
26
|
+
new(name, :catch_all, $~[:param_name], "*#{$~[:param_name]}")
|
|
27
|
+
when /\A\[(?<param_name>[A-Za-z_]\w*)\]\z/
|
|
28
|
+
new(name, :dynamic, $~[:param_name], ":#{$~[:param_name]}")
|
|
29
|
+
when /\A\(\.\)(?<path_part>.+)\z/
|
|
30
|
+
new(name, :intercept_current, nil, $~[:path_part])
|
|
31
|
+
when /\A\(\.\.\)(?<path_part>.+)\z/
|
|
32
|
+
new(name, :intercept_parent, nil, $~[:path_part])
|
|
33
|
+
when /\A\(\.\.\.\)(?<path_part>.+)\z/
|
|
34
|
+
new(name, :intercept_root, nil, $~[:path_part])
|
|
35
|
+
when /\A\((?<group_name>.+)\)\z/
|
|
36
|
+
new(name, :group, nil, nil)
|
|
37
|
+
when /\A@(?<slot_name>.+)\z/
|
|
38
|
+
new(name, :parallel, $~[:slot_name], nil)
|
|
39
|
+
else
|
|
40
|
+
new(name, :static, nil, name)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
RouteParam = Data.define(:name, :kind)
|
|
46
|
+
PARAM_SEGMENT_KINDS = [:dynamic, :catch_all, :optional_catch_all].freeze
|
|
47
|
+
ROUTE_FILE_BASENAMES = ["+page", "+layout", "+route", "+error", "+not-found"].freeze
|
|
48
|
+
|
|
49
|
+
PageRoute = Data.define(:path, :page_module_id, :handler_module_id, :segments, :layout_module_ids, :slot_layout_module_id) do
|
|
50
|
+
def params
|
|
51
|
+
segments
|
|
52
|
+
.select { |segment| segment.param_name && PARAM_SEGMENT_KINDS.include?(segment.kind) }
|
|
53
|
+
.map { |segment| RouteParam.new(segment.param_name, segment.kind) }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def module_id
|
|
57
|
+
page_module_id || handler_module_id
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def kind
|
|
61
|
+
return :page_and_handler if page_module_id && handler_module_id
|
|
62
|
+
return :page if page_module_id
|
|
63
|
+
|
|
64
|
+
:handler
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
SpecialView = Data.define(:kind, :path, :view_module_id, :segments, :layout_module_ids, :status) do
|
|
69
|
+
def module_id
|
|
70
|
+
view_module_id
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
RouteManifest = Data.define(:routes, :special_views) do
|
|
75
|
+
def entrypoints
|
|
76
|
+
[
|
|
77
|
+
*routes.flat_map { |route| [route.page_module_id, route.handler_module_id] },
|
|
78
|
+
*special_views.map(&:view_module_id)
|
|
79
|
+
].compact.map(&:path)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def [](path)
|
|
83
|
+
routes_by_path.fetch(path)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def fetch(path, *fallback, &block)
|
|
87
|
+
routes_by_path.fetch(path, *fallback, &block)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def each_route(&block)
|
|
91
|
+
return enum_for(:each_route) unless block
|
|
92
|
+
|
|
93
|
+
routes.each(&block)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
private
|
|
97
|
+
|
|
98
|
+
def routes_by_path
|
|
99
|
+
routes.to_h { |route| [route.path, route] }
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def initialize(specifier: "virtual:router", pages_dir: "pages", extensions: [".rb", ".haml"], route_base_class: nil)
|
|
104
|
+
@specifier = specifier
|
|
105
|
+
@pages_dir = pages_dir
|
|
106
|
+
@extensions = extensions
|
|
107
|
+
@route_base_class = route_base_class
|
|
108
|
+
@module_id = ModuleId.new("#{specifier}.rb", nil)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
attr_reader :specifier, :pages_dir, :extensions, :route_base_class
|
|
112
|
+
|
|
113
|
+
def resolve(dependency, _context)
|
|
114
|
+
return nil unless dependency.specifier == specifier
|
|
115
|
+
|
|
116
|
+
ResolvedDependency.new(dependency, @module_id, {virtual: true})
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def load(module_id, context)
|
|
120
|
+
return nil unless module_id.scheme == :virtual && module_id == @module_id
|
|
121
|
+
|
|
122
|
+
generate_router_source(discover(source_dir: context.source_dir))
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def transform(module_id, code, context)
|
|
126
|
+
if module_id.scheme == :virtual && module_id == @module_id
|
|
127
|
+
return TransformResult.new(
|
|
128
|
+
code,
|
|
129
|
+
[],
|
|
130
|
+
nil,
|
|
131
|
+
[],
|
|
132
|
+
watched_patterns(module_id),
|
|
133
|
+
{}
|
|
134
|
+
)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
return TransformResult.identity(code) unless route_handler_module_id?(module_id, context)
|
|
138
|
+
|
|
139
|
+
wrapped = route_handler_source(code)
|
|
140
|
+
TransformResult.new(
|
|
141
|
+
wrapped,
|
|
142
|
+
[],
|
|
143
|
+
Klenod::Runtime::SourceMap::SourceMap.parse(code, wrapped),
|
|
144
|
+
[],
|
|
145
|
+
[],
|
|
146
|
+
{router_route_handler: true}
|
|
147
|
+
)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def import_value(_resolved_dependency, record, context)
|
|
151
|
+
return nil unless record.metadata[:router_route_handler]
|
|
152
|
+
|
|
153
|
+
context.mods.fetch(record.id).const_get(:Exports)::Default
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def runtime_import_value(_resolved_dependency, record, _context)
|
|
157
|
+
return nil unless record.metadata[:router_route_handler]
|
|
158
|
+
|
|
159
|
+
Runtime::DefaultImport.new(:Default)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def discover(source_dir:)
|
|
163
|
+
root = Pathname.new(source_dir).expand_path
|
|
164
|
+
validate_plus_files(root)
|
|
165
|
+
RouteManifest.new(routes(source_dir: root), special_views(source_dir: root))
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
private
|
|
169
|
+
|
|
170
|
+
def validate_plus_files(source_dir)
|
|
171
|
+
root = source_dir.join(pages_dir)
|
|
172
|
+
return unless root.directory?
|
|
173
|
+
|
|
174
|
+
unsupported =
|
|
175
|
+
root
|
|
176
|
+
.glob("**/+*")
|
|
177
|
+
.select(&:file?)
|
|
178
|
+
.reject { |path| supported_plus_file?(path) }
|
|
179
|
+
return if unsupported.empty?
|
|
180
|
+
|
|
181
|
+
matches = unsupported.map { |path| relative_path_for(source_dir, path) }.sort.join(", ")
|
|
182
|
+
raise ResolveError, "Unsupported router file #{matches}. Supported router files are +page, +layout, +route, +error, and +not-found."
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def supported_plus_file?(path)
|
|
186
|
+
basename = path.basename.to_s
|
|
187
|
+
return true if basename == "+route.rb"
|
|
188
|
+
|
|
189
|
+
ROUTE_FILE_BASENAMES.any? do |route_basename|
|
|
190
|
+
next false if route_basename == "+route"
|
|
191
|
+
|
|
192
|
+
extensions.any? { |extension| basename == "#{route_basename}#{extension}" } ||
|
|
193
|
+
basename == "#{route_basename}.css" ||
|
|
194
|
+
basename.match?(/\A#{Regexp.escape(route_basename)}\.intl\.[^\/]+\.toml\z/)
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def routes(source_dir:)
|
|
199
|
+
route_files(source_dir)
|
|
200
|
+
.group_by { |path| route_key_for(route_segments_for(source_dir, path)) }
|
|
201
|
+
.map { |_route_key, paths| route_for(source_dir, paths) }
|
|
202
|
+
.sort_by(&:path)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def route_files(source_dir)
|
|
206
|
+
root = source_dir.join(pages_dir)
|
|
207
|
+
return [] unless root.directory?
|
|
208
|
+
|
|
209
|
+
[
|
|
210
|
+
*extensions.flat_map { |extension| root.glob("**/+page#{extension}") },
|
|
211
|
+
*root.glob("**/+route.rb")
|
|
212
|
+
].select(&:file?)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def special_views(source_dir:)
|
|
216
|
+
special_view_files(source_dir)
|
|
217
|
+
.group_by { |path| [special_view_kind_for(path), route_key_for(route_segments_for(source_dir, path))] }
|
|
218
|
+
.map { |(_kind, _route_key), paths| special_view_for(source_dir, paths) }
|
|
219
|
+
.sort_by { |view| [view.kind.to_s, view.path] }
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def special_view_files(source_dir)
|
|
223
|
+
root = source_dir.join(pages_dir)
|
|
224
|
+
return [] unless root.directory?
|
|
225
|
+
|
|
226
|
+
extensions.flat_map do |extension|
|
|
227
|
+
[
|
|
228
|
+
*root.glob("**/+not-found#{extension}"),
|
|
229
|
+
*root.glob("**/+error#{extension}")
|
|
230
|
+
]
|
|
231
|
+
end.select(&:file?).reject { |path| route_segments_for(source_dir, path).any? { |segment| segment.kind == :parallel } }
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def special_view_for(source_dir, paths)
|
|
235
|
+
path = paths.fetch(0)
|
|
236
|
+
kind = special_view_kind_for(path)
|
|
237
|
+
if paths.length > 1
|
|
238
|
+
matches = paths.map { |match| relative_path_for(source_dir, match) }.sort.join(", ")
|
|
239
|
+
raise ResolveError, "Ambiguous #{kind.to_s.tr("_", "-")} route #{route_path_for(route_segments_for(source_dir, path))}; matched #{matches}. Use only one special view file per directory."
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
segments = route_segments_for(source_dir, path)
|
|
243
|
+
SpecialView.new(
|
|
244
|
+
kind,
|
|
245
|
+
route_path_for(segments),
|
|
246
|
+
ModuleId.new(relative_path_for(source_dir, path), nil),
|
|
247
|
+
segments,
|
|
248
|
+
layout_module_ids_for(source_dir, path),
|
|
249
|
+
(kind == :not_found) ? 404 : 500
|
|
250
|
+
)
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def special_view_kind_for(path)
|
|
254
|
+
name = path.basename(path.extname).to_s
|
|
255
|
+
return :not_found if name == "+not-found"
|
|
256
|
+
return :error if name == "+error"
|
|
257
|
+
|
|
258
|
+
raise ResolveError, "Unsupported special view #{path}"
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def watched_patterns(module_id)
|
|
262
|
+
patterns = extensions.flat_map do |extension|
|
|
263
|
+
[
|
|
264
|
+
WatchedPattern.new(module_id, "#{pages_dir}/**/+page#{extension}", :router_page, {}),
|
|
265
|
+
WatchedPattern.new(module_id, "#{pages_dir}/**/+layout#{extension}", :router_layout, {}),
|
|
266
|
+
WatchedPattern.new(module_id, "#{pages_dir}/**/+not-found#{extension}", :router_not_found, {}),
|
|
267
|
+
WatchedPattern.new(module_id, "#{pages_dir}/**/+error#{extension}", :router_error, {})
|
|
268
|
+
]
|
|
269
|
+
end
|
|
270
|
+
patterns + [WatchedPattern.new(module_id, "#{pages_dir}/**/+route.rb", :router_route, {})]
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def route_for(source_dir, paths)
|
|
274
|
+
segments = route_segments_for(source_dir, paths.fetch(0))
|
|
275
|
+
route_path = route_path_for(segments)
|
|
276
|
+
page_paths = paths.reject { |path| route_handler_path?(path) }
|
|
277
|
+
handler_path = paths.find { |path| route_handler_path?(path) }
|
|
278
|
+
if page_paths.length > 1
|
|
279
|
+
matches = page_paths.map { |path| relative_path_for(source_dir, path) }.sort.join(", ")
|
|
280
|
+
raise ResolveError, "Ambiguous route #{route_path}; matched #{matches}. Use only one page file per directory."
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
path = page_paths.fetch(0, handler_path)
|
|
284
|
+
PageRoute.new(
|
|
285
|
+
route_path,
|
|
286
|
+
page_paths.fetch(0, nil)&.then { |page_path| ModuleId.new(relative_path_for(source_dir, page_path), nil) },
|
|
287
|
+
handler_path&.then { |route_path| ModuleId.new(relative_path_for(source_dir, route_path), nil) },
|
|
288
|
+
segments,
|
|
289
|
+
page_paths.empty? ? [] : layout_module_ids_for(source_dir, path),
|
|
290
|
+
page_paths.empty? ? nil : slot_layout_module_id_for(source_dir, path, segments)
|
|
291
|
+
)
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def route_segments_for(source_dir, path)
|
|
295
|
+
relative_dir = path.dirname.relative_path_from(source_dir.join(pages_dir)).to_s
|
|
296
|
+
return [] if relative_dir == "."
|
|
297
|
+
|
|
298
|
+
relative_dir.split("/").map { |name| RouteSegment.parse(name) }
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
def route_path_for(segments)
|
|
302
|
+
path_parts = route_path_parts_for(segments)
|
|
303
|
+
|
|
304
|
+
return "/" if path_parts.empty?
|
|
305
|
+
"/#{path_parts.join("/")}"
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
def route_path_parts_for(segments)
|
|
309
|
+
segments.each_with_object([]) do |segment, path_parts|
|
|
310
|
+
case segment.kind
|
|
311
|
+
when :intercept_current
|
|
312
|
+
path_parts << segment.path_part
|
|
313
|
+
when :intercept_parent
|
|
314
|
+
path_parts.pop
|
|
315
|
+
path_parts << segment.path_part
|
|
316
|
+
when :intercept_root
|
|
317
|
+
path_parts.clear
|
|
318
|
+
path_parts << segment.path_part
|
|
319
|
+
else
|
|
320
|
+
path_parts << segment.path_part if segment.path_part
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
def route_key_for(segments)
|
|
326
|
+
segments.map { |segment| [segment.kind, segment.path_part, segment.param_name] }
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
def layout_module_ids_for(source_dir, path)
|
|
330
|
+
page_root = source_dir.join(pages_dir)
|
|
331
|
+
relative_dir = path.dirname.relative_path_from(page_root).to_s
|
|
332
|
+
dirs =
|
|
333
|
+
if relative_dir == "."
|
|
334
|
+
[page_root]
|
|
335
|
+
else
|
|
336
|
+
parts = relative_dir.split("/")
|
|
337
|
+
[page_root] + parts.each_index.map { |index| page_root.join(*parts[0..index]) }
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
dirs.filter_map do |dir|
|
|
341
|
+
layout_path = layout_path_for(dir)
|
|
342
|
+
ModuleId.new(relative_path_for(source_dir, layout_path), nil) if layout_path
|
|
343
|
+
end
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
def slot_layout_module_id_for(source_dir, path, segments)
|
|
347
|
+
parallel_index = segments.index { |segment| segment.kind == :parallel }
|
|
348
|
+
return nil unless parallel_index
|
|
349
|
+
|
|
350
|
+
page_root = source_dir.join(pages_dir)
|
|
351
|
+
relative_dir = path.dirname.relative_path_from(page_root).to_s
|
|
352
|
+
owner_parts = (relative_dir == ".") ? [] : relative_dir.split("/").first(parallel_index)
|
|
353
|
+
owner_dir = owner_parts.empty? ? page_root : page_root.join(*owner_parts)
|
|
354
|
+
layout_path = layout_path_for(owner_dir)
|
|
355
|
+
|
|
356
|
+
ModuleId.new(relative_path_for(source_dir, layout_path), nil) if layout_path
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
def layout_path_for(dir)
|
|
360
|
+
matches = extensions.filter_map do |extension|
|
|
361
|
+
path = dir.join("+layout#{extension}")
|
|
362
|
+
path if path.file?
|
|
363
|
+
end
|
|
364
|
+
return nil if matches.empty?
|
|
365
|
+
return matches.fetch(0) if matches.length <= 1
|
|
366
|
+
|
|
367
|
+
relative = matches.map(&:to_s).sort.join(", ")
|
|
368
|
+
raise ResolveError, "Ambiguous layout route; matched #{relative}. Use only one layout file per directory."
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
def relative_path_for(source_dir, path)
|
|
372
|
+
path.relative_path_from(source_dir).to_s
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
def generate_router_source(manifest)
|
|
376
|
+
imports = import_refs(manifest)
|
|
377
|
+
<<~RUBY
|
|
378
|
+
Segment = Data.define(:name, :kind, :param_name, :path_part)
|
|
379
|
+
Param = Data.define(:name, :kind)
|
|
380
|
+
#{import_definitions(imports)}
|
|
381
|
+
|
|
382
|
+
module Default
|
|
383
|
+
Route = Data.define(:path, :page_module_id, :handler_module_id, :segments, :match_parts, :layout_module_ids, :slot_layout_module_id, :page_ref, :handler_ref, :layout_refs) do
|
|
384
|
+
def params
|
|
385
|
+
segments
|
|
386
|
+
.select { |segment| segment.param_name && [:dynamic, :catch_all, :optional_catch_all].include?(segment.kind) }
|
|
387
|
+
.map { |segment| Param.new(segment.param_name, segment.kind) }
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
def module_id
|
|
391
|
+
page_module_id || handler_module_id
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
def kind
|
|
395
|
+
return :page_and_handler if page_module_id && handler_module_id
|
|
396
|
+
return :page if page_module_id
|
|
397
|
+
|
|
398
|
+
:handler
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
def page
|
|
402
|
+
return nil unless page_ref
|
|
403
|
+
|
|
404
|
+
Default.resolve_import(page_ref)
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
def handler
|
|
408
|
+
return nil unless handler_ref
|
|
409
|
+
|
|
410
|
+
Default.resolve_import(handler_ref)
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
def layouts
|
|
414
|
+
layout_refs.map { |layout_ref| Default.resolve_import(layout_ref) }
|
|
415
|
+
end
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
SpecialView = Data.define(:kind, :path, :status, :module_id, :segments, :match_parts, :layout_module_ids, :page_ref, :layout_refs) do
|
|
419
|
+
def page
|
|
420
|
+
Default.resolve_import(page_ref)
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
def layouts
|
|
424
|
+
layout_refs.map { |layout_ref| Default.resolve_import(layout_ref) }
|
|
425
|
+
end
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
class RouteNode
|
|
429
|
+
attr_reader :segment, :path, :children, :slots
|
|
430
|
+
attr_accessor :route
|
|
431
|
+
|
|
432
|
+
def initialize(segment:, path:, route: nil)
|
|
433
|
+
@segment = segment
|
|
434
|
+
@path = path
|
|
435
|
+
@route = route
|
|
436
|
+
@children = []
|
|
437
|
+
@slots = {}
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
def root?
|
|
441
|
+
segment.nil?
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
def leaf?
|
|
445
|
+
!route.nil?
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
def child_for(segment)
|
|
449
|
+
children.find { |child| Default.same_segment?(child.segment, segment) }
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
def add_child(segment, path)
|
|
453
|
+
child_for(segment) || children.tap { _1 << RouteNode.new(segment: segment, path: path) }.last
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
def add_slot(segment, path)
|
|
457
|
+
slots[segment.param_name.to_sym] ||= add_child(segment, path)
|
|
458
|
+
end
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
SlotMatch = Data.define(:route, :params, :layout_module_id) do
|
|
462
|
+
def page
|
|
463
|
+
route.page
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
def layouts
|
|
467
|
+
route.layouts
|
|
468
|
+
end
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
Match = Data.define(:route, :params, :slots) do
|
|
472
|
+
def page
|
|
473
|
+
route.page
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
def handler
|
|
477
|
+
route.handler
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
def layouts
|
|
481
|
+
route.layouts
|
|
482
|
+
end
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
SpecialMatch = Data.define(:route, :params) do
|
|
486
|
+
def page
|
|
487
|
+
route.page
|
|
488
|
+
end
|
|
489
|
+
|
|
490
|
+
def layouts
|
|
491
|
+
route.layouts
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
def slots
|
|
495
|
+
{}
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
def status
|
|
499
|
+
route.status
|
|
500
|
+
end
|
|
501
|
+
end
|
|
502
|
+
|
|
503
|
+
ROUTES = [
|
|
504
|
+
#{route_entries(matching_routes(manifest), imports: imports).join(",\n")}
|
|
505
|
+
].freeze
|
|
506
|
+
NOT_FOUND_VIEWS = [
|
|
507
|
+
#{special_view_entries(special_views_for(manifest, :not_found), imports: imports).join(",\n")}
|
|
508
|
+
].freeze
|
|
509
|
+
ERROR_VIEWS = [
|
|
510
|
+
#{special_view_entries(special_views_for(manifest, :error), imports: imports).join(",\n")}
|
|
511
|
+
].freeze
|
|
512
|
+
|
|
513
|
+
def self.routes
|
|
514
|
+
ROUTES
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
def self.not_found_views
|
|
518
|
+
NOT_FOUND_VIEWS
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
def self.error_views
|
|
522
|
+
ERROR_VIEWS
|
|
523
|
+
end
|
|
524
|
+
|
|
525
|
+
def self.tree
|
|
526
|
+
TREE
|
|
527
|
+
end
|
|
528
|
+
|
|
529
|
+
def self.match(path)
|
|
530
|
+
parts = normalize_path(path)
|
|
531
|
+
route = main_route_for(parts)
|
|
532
|
+
return nil unless route
|
|
533
|
+
|
|
534
|
+
Match.new(route, params_for(route, parts), slot_matches_for(route, parts))
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
def self.not_found(path)
|
|
538
|
+
special_match(NOT_FOUND_VIEWS, path)
|
|
539
|
+
end
|
|
540
|
+
|
|
541
|
+
def self.error(path)
|
|
542
|
+
special_match(ERROR_VIEWS, path)
|
|
543
|
+
end
|
|
544
|
+
|
|
545
|
+
def self.resolve_import(value)
|
|
546
|
+
value.respond_to?(:call) && value.class.name == "Klenod::Runtime::LazyImport" ? value.call : value
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
def self.normalize_path(path)
|
|
550
|
+
normalized = path.to_s.split("?", 2).first
|
|
551
|
+
normalized = "/\#{normalized}" unless normalized.start_with?("/")
|
|
552
|
+
normalized.split("/").reject(&:empty?)
|
|
553
|
+
end
|
|
554
|
+
|
|
555
|
+
def self.build_tree(routes)
|
|
556
|
+
root = RouteNode.new(segment: nil, path: "/")
|
|
557
|
+
|
|
558
|
+
routes.each do |route|
|
|
559
|
+
cursor = root
|
|
560
|
+
path_parts = []
|
|
561
|
+
|
|
562
|
+
route.segments.each do |segment|
|
|
563
|
+
update_path_parts(path_parts, segment)
|
|
564
|
+
path = path_parts.empty? ? "/" : "/\#{path_parts.join("/")}"
|
|
565
|
+
cursor = segment.kind == :parallel ? cursor.add_slot(segment, path) : cursor.add_child(segment, path)
|
|
566
|
+
end
|
|
567
|
+
|
|
568
|
+
cursor.route = route
|
|
569
|
+
end
|
|
570
|
+
|
|
571
|
+
root
|
|
572
|
+
end
|
|
573
|
+
|
|
574
|
+
def self.update_path_parts(path_parts, segment)
|
|
575
|
+
case segment.kind
|
|
576
|
+
when :intercept_current
|
|
577
|
+
path_parts << segment.path_part
|
|
578
|
+
when :intercept_parent
|
|
579
|
+
path_parts.pop
|
|
580
|
+
path_parts << segment.path_part
|
|
581
|
+
when :intercept_root
|
|
582
|
+
path_parts.clear
|
|
583
|
+
path_parts << segment.path_part
|
|
584
|
+
else
|
|
585
|
+
path_parts << segment.path_part if segment.path_part
|
|
586
|
+
end
|
|
587
|
+
end
|
|
588
|
+
|
|
589
|
+
def self.same_segment?(left, right)
|
|
590
|
+
left&.name == right&.name &&
|
|
591
|
+
left&.kind == right&.kind &&
|
|
592
|
+
left&.param_name == right&.param_name &&
|
|
593
|
+
left&.path_part == right&.path_part
|
|
594
|
+
end
|
|
595
|
+
|
|
596
|
+
def self.route_matches?(route, parts)
|
|
597
|
+
cursor = 0
|
|
598
|
+
route.match_parts.each do |match_part|
|
|
599
|
+
case match_part[0]
|
|
600
|
+
when :static
|
|
601
|
+
return false unless parts[cursor] == match_part[1]
|
|
602
|
+
cursor += 1
|
|
603
|
+
when :dynamic
|
|
604
|
+
return false unless parts[cursor]
|
|
605
|
+
cursor += 1
|
|
606
|
+
when :catch_all
|
|
607
|
+
return false if parts[cursor..].empty?
|
|
608
|
+
cursor = parts.length
|
|
609
|
+
when :optional_catch_all
|
|
610
|
+
cursor = parts.length
|
|
611
|
+
end
|
|
612
|
+
end
|
|
613
|
+
cursor == parts.length
|
|
614
|
+
end
|
|
615
|
+
|
|
616
|
+
def self.route_prefix_matches?(route, parts)
|
|
617
|
+
cursor = 0
|
|
618
|
+
route.match_parts.each do |match_part|
|
|
619
|
+
case match_part[0]
|
|
620
|
+
when :static
|
|
621
|
+
return false unless parts[cursor] == match_part[1]
|
|
622
|
+
cursor += 1
|
|
623
|
+
when :dynamic
|
|
624
|
+
return false unless parts[cursor]
|
|
625
|
+
cursor += 1
|
|
626
|
+
when :catch_all, :optional_catch_all
|
|
627
|
+
cursor = parts.length
|
|
628
|
+
end
|
|
629
|
+
end
|
|
630
|
+
true
|
|
631
|
+
end
|
|
632
|
+
|
|
633
|
+
def self.special_match(views, path)
|
|
634
|
+
parts = normalize_path(path)
|
|
635
|
+
view = special_view_for(views, parts)
|
|
636
|
+
return nil unless view
|
|
637
|
+
|
|
638
|
+
SpecialMatch.new(view, params_for(view, parts))
|
|
639
|
+
end
|
|
640
|
+
|
|
641
|
+
def self.special_view_for(views, parts)
|
|
642
|
+
views
|
|
643
|
+
.select { |view| special_view_matches?(view, parts) }
|
|
644
|
+
.max_by { |view| [fallback_depth(view), -route_score_for(view)] }
|
|
645
|
+
end
|
|
646
|
+
|
|
647
|
+
def self.special_view_matches?(view, parts)
|
|
648
|
+
cursor = 0
|
|
649
|
+
view.match_parts.each do |match_part|
|
|
650
|
+
case match_part[0]
|
|
651
|
+
when :static
|
|
652
|
+
return false unless parts[cursor] == match_part[1]
|
|
653
|
+
cursor += 1
|
|
654
|
+
when :dynamic
|
|
655
|
+
return false unless parts[cursor]
|
|
656
|
+
cursor += 1
|
|
657
|
+
when :catch_all, :optional_catch_all
|
|
658
|
+
cursor = parts.length
|
|
659
|
+
end
|
|
660
|
+
end
|
|
661
|
+
cursor <= parts.length
|
|
662
|
+
end
|
|
663
|
+
|
|
664
|
+
def self.fallback_depth(view)
|
|
665
|
+
view.match_parts.count { |match_part| [:static, :dynamic, :catch_all, :optional_catch_all].include?(match_part[0]) }
|
|
666
|
+
end
|
|
667
|
+
|
|
668
|
+
def self.route_score_for(route)
|
|
669
|
+
route.match_parts.sum do |match_part|
|
|
670
|
+
case match_part[0]
|
|
671
|
+
when :static
|
|
672
|
+
0
|
|
673
|
+
when :dynamic
|
|
674
|
+
10
|
|
675
|
+
when :catch_all
|
|
676
|
+
100
|
|
677
|
+
when :optional_catch_all
|
|
678
|
+
1_000
|
|
679
|
+
else
|
|
680
|
+
0
|
|
681
|
+
end
|
|
682
|
+
end
|
|
683
|
+
end
|
|
684
|
+
|
|
685
|
+
def self.main_route_for(parts)
|
|
686
|
+
MAIN_ROUTES.find { |candidate| route_matches?(candidate, parts) }
|
|
687
|
+
end
|
|
688
|
+
|
|
689
|
+
def self.slot_matches_for(main_route, parts)
|
|
690
|
+
SLOT_ROUTES.each_with_object({}) do |route, slots|
|
|
691
|
+
next unless route_matches?(route, parts)
|
|
692
|
+
next unless route_prefix_matches?(main_route, normalize_path(route.path))
|
|
693
|
+
|
|
694
|
+
slots[slot_name_for(route)] = SlotMatch.new(route, params_for(route, parts), route.slot_layout_module_id)
|
|
695
|
+
end
|
|
696
|
+
end
|
|
697
|
+
|
|
698
|
+
def self.parallel_route?(route)
|
|
699
|
+
route.segments.any? { |segment| segment.kind == :parallel }
|
|
700
|
+
end
|
|
701
|
+
|
|
702
|
+
def self.slot_name_for(route)
|
|
703
|
+
route.segments.find { |segment| segment.kind == :parallel }.param_name.to_sym
|
|
704
|
+
end
|
|
705
|
+
|
|
706
|
+
def self.params_for(route, parts)
|
|
707
|
+
cursor = 0
|
|
708
|
+
params = {}
|
|
709
|
+
route.match_parts.each do |match_part|
|
|
710
|
+
case match_part[0]
|
|
711
|
+
when :static
|
|
712
|
+
cursor += 1
|
|
713
|
+
when :dynamic
|
|
714
|
+
params[match_part[2].to_sym] = parts[cursor]
|
|
715
|
+
cursor += 1
|
|
716
|
+
when :catch_all
|
|
717
|
+
params[match_part[2].to_sym] = parts[cursor..]
|
|
718
|
+
cursor = parts.length
|
|
719
|
+
when :optional_catch_all
|
|
720
|
+
params[match_part[2].to_sym] = parts[cursor..]
|
|
721
|
+
cursor = parts.length
|
|
722
|
+
end
|
|
723
|
+
end
|
|
724
|
+
params
|
|
725
|
+
end
|
|
726
|
+
|
|
727
|
+
MAIN_ROUTES = ROUTES.reject { |route| parallel_route?(route) }.freeze
|
|
728
|
+
SLOT_ROUTES = ROUTES.select { |route| parallel_route?(route) }.freeze
|
|
729
|
+
TREE = build_tree(ROUTES)
|
|
730
|
+
end
|
|
731
|
+
RUBY
|
|
732
|
+
end
|
|
733
|
+
|
|
734
|
+
def route_entries(routes, imports:)
|
|
735
|
+
routes.map do |route|
|
|
736
|
+
layouts = route.layout_module_ids.map { |module_id| imports.fetch(module_id.to_s) }
|
|
737
|
+
[
|
|
738
|
+
" Route.new(",
|
|
739
|
+
" #{route.path.inspect},",
|
|
740
|
+
" #{route.page_module_id&.path.inspect},",
|
|
741
|
+
" #{route.handler_module_id&.path.inspect},",
|
|
742
|
+
" #{segments_source(route.segments)},",
|
|
743
|
+
" #{match_parts_source(route.segments)},",
|
|
744
|
+
" #{route.layout_module_ids.map(&:path).inspect},",
|
|
745
|
+
" #{route.slot_layout_module_id&.path.inspect},",
|
|
746
|
+
" #{route.page_module_id ? imports.fetch(route.page_module_id.to_s) : "nil"},",
|
|
747
|
+
" #{route.handler_module_id ? imports.fetch(route.handler_module_id.to_s) : "nil"},",
|
|
748
|
+
" #{layouts_source(layouts)}",
|
|
749
|
+
" )"
|
|
750
|
+
].join("\n")
|
|
751
|
+
end
|
|
752
|
+
end
|
|
753
|
+
|
|
754
|
+
def special_view_entries(views, imports:)
|
|
755
|
+
views.map do |view|
|
|
756
|
+
layouts = view.layout_module_ids.map { |module_id| imports.fetch(module_id.to_s) }
|
|
757
|
+
[
|
|
758
|
+
" SpecialView.new(",
|
|
759
|
+
" #{view.kind.inspect},",
|
|
760
|
+
" #{view.path.inspect},",
|
|
761
|
+
" #{view.status.inspect},",
|
|
762
|
+
" #{view.view_module_id.path.inspect},",
|
|
763
|
+
" #{segments_source(view.segments)},",
|
|
764
|
+
" #{match_parts_source(view.segments)},",
|
|
765
|
+
" #{view.layout_module_ids.map(&:path).inspect},",
|
|
766
|
+
" #{imports.fetch(view.view_module_id.to_s)},",
|
|
767
|
+
" #{layouts_source(layouts)}",
|
|
768
|
+
" )"
|
|
769
|
+
].join("\n")
|
|
770
|
+
end
|
|
771
|
+
end
|
|
772
|
+
|
|
773
|
+
def special_views_for(manifest, kind)
|
|
774
|
+
manifest.special_views.select { |view| view.kind == kind }
|
|
775
|
+
end
|
|
776
|
+
|
|
777
|
+
def matching_routes(manifest)
|
|
778
|
+
manifest.routes.sort_by { |route| route_priority(route) }
|
|
779
|
+
end
|
|
780
|
+
|
|
781
|
+
def route_priority(route)
|
|
782
|
+
[
|
|
783
|
+
route_score(route),
|
|
784
|
+
-visible_segments(route).length,
|
|
785
|
+
route.path
|
|
786
|
+
]
|
|
787
|
+
end
|
|
788
|
+
|
|
789
|
+
def route_score(route)
|
|
790
|
+
visible_segments(route).sum { |segment| segment_score(segment) }
|
|
791
|
+
end
|
|
792
|
+
|
|
793
|
+
def visible_segments(route)
|
|
794
|
+
route.segments.reject { |segment| [:group, :parallel].include?(segment.kind) }
|
|
795
|
+
end
|
|
796
|
+
|
|
797
|
+
def segment_score(segment)
|
|
798
|
+
case segment.kind
|
|
799
|
+
when :static, :intercept_current, :intercept_parent, :intercept_root
|
|
800
|
+
0
|
|
801
|
+
when :dynamic
|
|
802
|
+
10
|
|
803
|
+
when :catch_all
|
|
804
|
+
100
|
|
805
|
+
when :optional_catch_all
|
|
806
|
+
1_000
|
|
807
|
+
else
|
|
808
|
+
0
|
|
809
|
+
end
|
|
810
|
+
end
|
|
811
|
+
|
|
812
|
+
def import_refs(manifest)
|
|
813
|
+
specifiers =
|
|
814
|
+
[
|
|
815
|
+
*manifest.routes.flat_map do |route|
|
|
816
|
+
[
|
|
817
|
+
route.page_module_id&.to_s,
|
|
818
|
+
route.handler_module_id&.to_s,
|
|
819
|
+
*route.layout_module_ids.map(&:to_s)
|
|
820
|
+
]
|
|
821
|
+
end,
|
|
822
|
+
*manifest.special_views.flat_map do |view|
|
|
823
|
+
[
|
|
824
|
+
view.view_module_id.to_s,
|
|
825
|
+
*view.layout_module_ids.map(&:to_s)
|
|
826
|
+
]
|
|
827
|
+
end
|
|
828
|
+
].uniq
|
|
829
|
+
specifiers.compact!
|
|
830
|
+
|
|
831
|
+
specifiers.each_with_index.to_h do |specifier, index|
|
|
832
|
+
[specifier, "ROUTER_IMPORT_#{index}"]
|
|
833
|
+
end
|
|
834
|
+
end
|
|
835
|
+
|
|
836
|
+
def import_definitions(imports)
|
|
837
|
+
imports
|
|
838
|
+
.map { |specifier, const_name| "#{const_name} = lazy_import(#{specifier.inspect})" }
|
|
839
|
+
.join("\n")
|
|
840
|
+
end
|
|
841
|
+
|
|
842
|
+
def segments_source(segments)
|
|
843
|
+
"[#{segments.map { |segment| segment_source(segment) }.join(", ")}]"
|
|
844
|
+
end
|
|
845
|
+
|
|
846
|
+
def match_parts_source(segments)
|
|
847
|
+
route_match_parts_for(segments).inspect
|
|
848
|
+
end
|
|
849
|
+
|
|
850
|
+
def segment_source(segment)
|
|
851
|
+
"Segment.new(#{segment.name.inspect}, #{segment.kind.inspect}, #{segment.param_name.inspect}, #{segment.path_part.inspect})"
|
|
852
|
+
end
|
|
853
|
+
|
|
854
|
+
def route_match_parts_for(segments)
|
|
855
|
+
route_segments_for_matching(segments).filter_map do |segment|
|
|
856
|
+
case segment.kind
|
|
857
|
+
when :static, :intercept_current, :intercept_parent, :intercept_root
|
|
858
|
+
[:static, segment.path_part, nil]
|
|
859
|
+
when :dynamic, :catch_all, :optional_catch_all
|
|
860
|
+
[segment.kind, segment.path_part, segment.param_name]
|
|
861
|
+
end
|
|
862
|
+
end
|
|
863
|
+
end
|
|
864
|
+
|
|
865
|
+
def route_segments_for_matching(segments)
|
|
866
|
+
segments.each_with_object([]) do |segment, match_segments|
|
|
867
|
+
case segment.kind
|
|
868
|
+
when :group, :parallel
|
|
869
|
+
next
|
|
870
|
+
when :intercept_current
|
|
871
|
+
match_segments << segment
|
|
872
|
+
when :intercept_parent
|
|
873
|
+
match_segments.pop
|
|
874
|
+
match_segments << segment
|
|
875
|
+
when :intercept_root
|
|
876
|
+
match_segments.clear
|
|
877
|
+
match_segments << segment
|
|
878
|
+
else
|
|
879
|
+
match_segments << segment if segment.path_part || [:dynamic, :catch_all, :optional_catch_all].include?(segment.kind)
|
|
880
|
+
end
|
|
881
|
+
end
|
|
882
|
+
end
|
|
883
|
+
|
|
884
|
+
def layouts_source(layouts)
|
|
885
|
+
"[#{layouts.join(", ")}]"
|
|
886
|
+
end
|
|
887
|
+
|
|
888
|
+
def route_handler_module_id?(module_id, context)
|
|
889
|
+
return false unless module_id.path.start_with?("#{pages_dir}/")
|
|
890
|
+
return false unless module_id.path.end_with?("/+route.rb")
|
|
891
|
+
|
|
892
|
+
route_handler_module_ids(context.source_dir).include?(module_id)
|
|
893
|
+
end
|
|
894
|
+
|
|
895
|
+
def route_handler_module_ids(source_dir)
|
|
896
|
+
discover(source_dir: source_dir).routes.filter_map(&:handler_module_id)
|
|
897
|
+
end
|
|
898
|
+
|
|
899
|
+
def route_handler_path?(path)
|
|
900
|
+
path.basename.to_s == "+route.rb"
|
|
901
|
+
end
|
|
902
|
+
|
|
903
|
+
def route_handler_source(source)
|
|
904
|
+
superclass = route_base_class ? " < #{route_base_class}" : ""
|
|
905
|
+
<<~RUBY
|
|
906
|
+
# frozen_string_literal: true
|
|
907
|
+
KlenodImport = method(:__klenod_import__)
|
|
908
|
+
class Route#{superclass}
|
|
909
|
+
def self.__klenod_import__(dependency_id)
|
|
910
|
+
KlenodImport.call(dependency_id)
|
|
911
|
+
end
|
|
912
|
+
|
|
913
|
+
def __klenod_import__(dependency_id)
|
|
914
|
+
self.class.__klenod_import__(dependency_id)
|
|
915
|
+
end
|
|
916
|
+
|
|
917
|
+
#{marked_route_handler_body(source)}
|
|
918
|
+
end
|
|
919
|
+
Default = Route
|
|
920
|
+
RUBY
|
|
921
|
+
end
|
|
922
|
+
|
|
923
|
+
def marked_route_handler_body(source)
|
|
924
|
+
source.each_line.with_index(1).map do |line, line_no|
|
|
925
|
+
if ruby_magic_comment?(line)
|
|
926
|
+
""
|
|
927
|
+
elsif line.strip.empty?
|
|
928
|
+
line
|
|
929
|
+
else
|
|
930
|
+
" # #{Klenod::Runtime::SourceMap::Mark.new(line_no)}\n #{line}"
|
|
931
|
+
end
|
|
932
|
+
end.join
|
|
933
|
+
end
|
|
934
|
+
|
|
935
|
+
def ruby_magic_comment?(line)
|
|
936
|
+
line.match?(/\A#\s*(?:frozen_string_literal|encoding):/)
|
|
937
|
+
end
|
|
938
|
+
end
|
|
939
|
+
end
|
|
940
|
+
end
|
|
941
|
+
end
|
|
942
|
+
end
|