rhales 0.3.0 → 0.4.0
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/CLAUDE.md +0 -1
- data/README.md +302 -3
- data/lib/rhales/configuration.rb +114 -1
- data/lib/rhales/context.rb +0 -1
- data/lib/rhales/earliest_injection_detector.rb +149 -0
- data/lib/rhales/errors/hydration_collision_error.rb +1 -1
- data/lib/rhales/hydration_data_aggregator.rb +23 -22
- data/lib/rhales/hydration_endpoint.rb +211 -0
- data/lib/rhales/hydration_injector.rb +171 -0
- data/lib/rhales/hydrator.rb +3 -3
- data/lib/rhales/link_based_injection_detector.rb +191 -0
- data/lib/rhales/mount_point_detector.rb +105 -0
- data/lib/rhales/parsers/rue_format_parser.rb +50 -33
- data/lib/rhales/refinements/require_refinements.rb +4 -12
- data/lib/rhales/rue_document.rb +3 -5
- data/lib/rhales/safe_injection_validator.rb +99 -0
- data/lib/rhales/template_engine.rb +47 -7
- data/lib/rhales/tilt.rb +6 -5
- data/lib/rhales/version.rb +3 -1
- data/lib/rhales/view.rb +165 -25
- data/lib/rhales/view_composition.rb +5 -3
- data/lib/rhales.rb +12 -1
- metadata +9 -3
@@ -4,6 +4,7 @@ require 'erb'
|
|
4
4
|
require_relative 'parsers/rue_format_parser'
|
5
5
|
require_relative 'parsers/handlebars_parser'
|
6
6
|
require_relative 'rue_document'
|
7
|
+
require_relative 'hydrator'
|
7
8
|
|
8
9
|
module Rhales
|
9
10
|
# Rhales - Ruby Handlebars-style template engine
|
@@ -214,9 +215,35 @@ module Rhales
|
|
214
215
|
partial_content = @partial_resolver.call(partial_name)
|
215
216
|
raise PartialNotFoundError, "Partial '#{partial_name}' not found" unless partial_content
|
216
217
|
|
217
|
-
#
|
218
|
-
|
219
|
-
|
218
|
+
# Check if this is a .rue document with sections
|
219
|
+
if partial_content.match?(/^<(data|template|logic)\b/)
|
220
|
+
# Parse as RueDocument to handle data sections properly
|
221
|
+
partial_doc = RueDocument.new(partial_content)
|
222
|
+
partial_doc.parse!
|
223
|
+
|
224
|
+
# Extract template section
|
225
|
+
template_content = partial_doc.section('template')
|
226
|
+
raise PartialNotFoundError, "Partial '#{partial_name}' missing template section" unless template_content
|
227
|
+
|
228
|
+
# Process data section if present and merge with parent context
|
229
|
+
merged_context = @context
|
230
|
+
if partial_doc.section('data')
|
231
|
+
# Create hydrator with parent context to process interpolations
|
232
|
+
hydrator = Hydrator.new(partial_doc, @context)
|
233
|
+
local_data = hydrator.processed_data_hash
|
234
|
+
|
235
|
+
# Create merged context (local data takes precedence)
|
236
|
+
merged_context = create_merged_context(@context, local_data)
|
237
|
+
end
|
238
|
+
|
239
|
+
# Render template with merged context
|
240
|
+
engine = self.class.new(template_content, merged_context, partial_resolver: @partial_resolver)
|
241
|
+
engine.render
|
242
|
+
else
|
243
|
+
# Simple template without sections - render as before
|
244
|
+
engine = self.class.new(partial_content, @context, partial_resolver: @partial_resolver)
|
245
|
+
engine.render
|
246
|
+
end
|
220
247
|
end
|
221
248
|
|
222
249
|
# Get variable value from context
|
@@ -234,8 +261,6 @@ module Rhales
|
|
234
261
|
@context.get(variable_name)
|
235
262
|
elsif @context.respond_to?(:[])
|
236
263
|
@context[variable_name] || @context[variable_name.to_sym]
|
237
|
-
else
|
238
|
-
nil
|
239
264
|
end
|
240
265
|
end
|
241
266
|
|
@@ -272,6 +297,23 @@ module Rhales
|
|
272
297
|
ERB::Util.html_escape(string)
|
273
298
|
end
|
274
299
|
|
300
|
+
# Create a new context with merged data
|
301
|
+
def create_merged_context(parent_context, local_data)
|
302
|
+
# Extract all props from parent context and merge with local data
|
303
|
+
# Local data takes precedence over parent props
|
304
|
+
merged_props = parent_context.props.merge(local_data)
|
305
|
+
|
306
|
+
# Create new context with merged props, preserving other context attributes
|
307
|
+
Context.for_view(
|
308
|
+
parent_context.req,
|
309
|
+
parent_context.sess,
|
310
|
+
parent_context.cust,
|
311
|
+
parent_context.locale,
|
312
|
+
config: parent_context.config,
|
313
|
+
**merged_props,
|
314
|
+
)
|
315
|
+
end
|
316
|
+
|
275
317
|
# Context wrapper for {{#each}} iterations
|
276
318
|
class EachContext
|
277
319
|
attr_reader :parent_context, :current_item, :current_index, :items_var
|
@@ -343,8 +385,6 @@ module Rhales
|
|
343
385
|
# Load and parse the partial .rue file
|
344
386
|
document = RueDocument.parse_file(partial_path)
|
345
387
|
document.section('template')
|
346
|
-
else
|
347
|
-
nil
|
348
388
|
end
|
349
389
|
end
|
350
390
|
end
|
data/lib/rhales/tilt.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
#
|
1
|
+
# lib/rhales/tilt.rb
|
2
2
|
|
3
3
|
require 'tilt'
|
4
4
|
require 'rhales'
|
@@ -109,7 +109,8 @@ module Rhales
|
|
109
109
|
framework_env = scope.request.env.merge({
|
110
110
|
'nonce' => shared_nonce,
|
111
111
|
'request_id' => SecureRandom.hex(8),
|
112
|
-
}
|
112
|
+
},
|
113
|
+
)
|
113
114
|
|
114
115
|
# Create wrapper that preserves original but adds our env
|
115
116
|
wrapped_request = Class.new do
|
@@ -118,8 +119,8 @@ module Rhales
|
|
118
119
|
@custom_env = custom_env
|
119
120
|
end
|
120
121
|
|
121
|
-
def method_missing(method,
|
122
|
-
@original.send(method,
|
122
|
+
def method_missing(method, *, &)
|
123
|
+
@original.send(method, *, &)
|
123
124
|
end
|
124
125
|
|
125
126
|
def respond_to_missing?(method, include_private = false)
|
@@ -141,7 +142,7 @@ module Rhales
|
|
141
142
|
env: {
|
142
143
|
'nonce' => shared_nonce,
|
143
144
|
'request_id' => SecureRandom.hex(8),
|
144
|
-
}
|
145
|
+
},
|
145
146
|
)
|
146
147
|
end
|
147
148
|
|
data/lib/rhales/version.rb
CHANGED
data/lib/rhales/view.rb
CHANGED
@@ -87,8 +87,8 @@ module Rhales
|
|
87
87
|
# Set CSP header if enabled
|
88
88
|
set_csp_header_if_enabled
|
89
89
|
|
90
|
-
#
|
91
|
-
|
90
|
+
# Smart hydration injection with mount point detection
|
91
|
+
inject_hydration_with_mount_points(composition, template_name, template_html, hydration_html)
|
92
92
|
rescue StandardError => ex
|
93
93
|
raise RenderError, "Failed to render template '#{template_name}': #{ex.message}"
|
94
94
|
end
|
@@ -102,6 +102,51 @@ module Rhales
|
|
102
102
|
render_template_with_composition(composition, template_name)
|
103
103
|
end
|
104
104
|
|
105
|
+
# Render JSON response for API endpoints (link-based strategies)
|
106
|
+
def render_json_only(template_name = nil, additional_context = {})
|
107
|
+
require_relative 'hydration_endpoint'
|
108
|
+
|
109
|
+
template_name ||= self.class.default_template_name
|
110
|
+
endpoint = HydrationEndpoint.new(@config, @rsfc_context)
|
111
|
+
endpoint.render_json(template_name, additional_context)
|
112
|
+
end
|
113
|
+
|
114
|
+
# Render ES module response for modulepreload strategy
|
115
|
+
def render_module_only(template_name = nil, additional_context = {})
|
116
|
+
require_relative 'hydration_endpoint'
|
117
|
+
|
118
|
+
template_name ||= self.class.default_template_name
|
119
|
+
endpoint = HydrationEndpoint.new(@config, @rsfc_context)
|
120
|
+
endpoint.render_module(template_name, additional_context)
|
121
|
+
end
|
122
|
+
|
123
|
+
# Render JSONP response with callback
|
124
|
+
def render_jsonp_only(template_name = nil, callback_name = 'callback', additional_context = {})
|
125
|
+
require_relative 'hydration_endpoint'
|
126
|
+
|
127
|
+
template_name ||= self.class.default_template_name
|
128
|
+
endpoint = HydrationEndpoint.new(@config, @rsfc_context)
|
129
|
+
endpoint.render_jsonp(template_name, callback_name, additional_context)
|
130
|
+
end
|
131
|
+
|
132
|
+
# Check if template data has changed for caching
|
133
|
+
def data_changed?(template_name = nil, etag = nil, additional_context = {})
|
134
|
+
require_relative 'hydration_endpoint'
|
135
|
+
|
136
|
+
template_name ||= self.class.default_template_name
|
137
|
+
endpoint = HydrationEndpoint.new(@config, @rsfc_context)
|
138
|
+
endpoint.data_changed?(template_name, etag, additional_context)
|
139
|
+
end
|
140
|
+
|
141
|
+
# Calculate ETag for current template data
|
142
|
+
def calculate_etag(template_name = nil, additional_context = {})
|
143
|
+
require_relative 'hydration_endpoint'
|
144
|
+
|
145
|
+
template_name ||= self.class.default_template_name
|
146
|
+
endpoint = HydrationEndpoint.new(@config, @rsfc_context)
|
147
|
+
endpoint.calculate_etag(template_name, additional_context)
|
148
|
+
end
|
149
|
+
|
105
150
|
# Generate only the data hydration HTML
|
106
151
|
def render_hydration_only(template_name = nil)
|
107
152
|
template_name ||= self.class.default_template_name
|
@@ -182,11 +227,7 @@ module Rhales
|
|
182
227
|
|
183
228
|
# Get templates root directory
|
184
229
|
def templates_root
|
185
|
-
boot_root =
|
186
|
-
OT.boot_root
|
187
|
-
else
|
188
|
-
File.expand_path('../../..', __dir__)
|
189
|
-
end
|
230
|
+
boot_root = File.expand_path('../../..', __dir__)
|
190
231
|
File.join(boot_root, 'templates')
|
191
232
|
end
|
192
233
|
|
@@ -220,11 +261,9 @@ module Rhales
|
|
220
261
|
partial_path = File.join(templates_dir, "#{partial_name}.rue")
|
221
262
|
|
222
263
|
if File.exist?(partial_path)
|
223
|
-
#
|
224
|
-
|
225
|
-
|
226
|
-
else
|
227
|
-
nil
|
264
|
+
# Return full partial content so TemplateEngine can process
|
265
|
+
# data sections, otherwise nil.
|
266
|
+
File.read(partial_path)
|
228
267
|
end
|
229
268
|
end
|
230
269
|
end
|
@@ -255,12 +294,32 @@ module Rhales
|
|
255
294
|
hydrator = Hydrator.new(parser, @rsfc_context)
|
256
295
|
hydrator.processed_data_hash
|
257
296
|
rescue JSON::ParserError, Hydrator::JSONSerializationError => ex
|
297
|
+
puts "Error processing data section: #{ex.message}"
|
258
298
|
# If data section isn't valid JSON, return empty hash
|
259
299
|
# This allows templates to work even with malformed data sections
|
260
300
|
{}
|
261
301
|
end
|
262
302
|
|
263
|
-
#
|
303
|
+
# Smart hydration injection with mount point detection on rendered HTML
|
304
|
+
def inject_hydration_with_mount_points(composition, template_name, template_html, hydration_html)
|
305
|
+
injector = HydrationInjector.new(@config.hydration, template_name)
|
306
|
+
|
307
|
+
# Check if using link-based strategy
|
308
|
+
if @config.hydration.link_based_strategy?
|
309
|
+
# For link-based strategies, we need the merged data context
|
310
|
+
aggregator = HydrationDataAggregator.new(@rsfc_context)
|
311
|
+
merged_data = aggregator.aggregate(composition)
|
312
|
+
nonce = @rsfc_context.get('nonce')
|
313
|
+
|
314
|
+
injector.inject_link_based_strategy(template_html, merged_data, nonce)
|
315
|
+
else
|
316
|
+
# Traditional strategies (early, earliest, late)
|
317
|
+
mount_point = detect_mount_point_in_rendered_html(template_html)
|
318
|
+
injector.inject(template_html, hydration_html, mount_point)
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
# Legacy injection method (kept for backwards compatibility)
|
264
323
|
def inject_hydration_into_template(template_html, hydration_html)
|
265
324
|
# Try to inject before closing </body> tag
|
266
325
|
if template_html.include?('</body>')
|
@@ -271,10 +330,19 @@ module Rhales
|
|
271
330
|
end
|
272
331
|
end
|
273
332
|
|
333
|
+
# Detect mount points in fully rendered HTML
|
334
|
+
def detect_mount_point_in_rendered_html(template_html)
|
335
|
+
return nil unless @config&.hydration
|
336
|
+
|
337
|
+
custom_selectors = @config.hydration.mount_point_selectors || []
|
338
|
+
detector = MountPointDetector.new
|
339
|
+
detector.detect(template_html, custom_selectors)
|
340
|
+
end
|
341
|
+
|
274
342
|
# Build view composition for the given template
|
275
343
|
def build_view_composition(template_name)
|
276
344
|
loader = method(:load_template_for_composition)
|
277
|
-
composition = ViewComposition.new(template_name, loader: loader)
|
345
|
+
composition = ViewComposition.new(template_name, loader: loader, config: @config)
|
278
346
|
composition.resolve!
|
279
347
|
end
|
280
348
|
|
@@ -332,7 +400,7 @@ module Rhales
|
|
332
400
|
def create_partial_resolver_from_composition(composition)
|
333
401
|
proc do |partial_name|
|
334
402
|
parser = composition.template(partial_name)
|
335
|
-
parser ? parser.
|
403
|
+
parser ? parser.content : nil
|
336
404
|
end
|
337
405
|
end
|
338
406
|
|
@@ -344,30 +412,102 @@ module Rhales
|
|
344
412
|
# Generate unique ID for this data block
|
345
413
|
unique_id = "rsfc-data-#{SecureRandom.hex(8)}"
|
346
414
|
|
347
|
-
# Create JSON script tag
|
415
|
+
# Create JSON script tag with optional reflection attributes
|
416
|
+
json_attrs = reflection_enabled? ? " data-window=\"#{window_attr}\"" : ""
|
348
417
|
json_script = <<~HTML.strip
|
349
|
-
<script id="#{unique_id}" type="application/json">#{JSON.generate(data)}</script>
|
418
|
+
<script id="#{unique_id}" type="application/json"#{json_attrs}>#{JSON.generate(data)}</script>
|
350
419
|
HTML
|
351
420
|
|
352
|
-
# Create hydration script
|
353
|
-
nonce_attr
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
421
|
+
# Create hydration script with optional reflection attributes
|
422
|
+
nonce_attr = nonce_attribute
|
423
|
+
hydration_attrs = reflection_enabled? ? " data-hydration-target=\"#{window_attr}\"" : ""
|
424
|
+
hydration_script = if reflection_enabled?
|
425
|
+
<<~HTML.strip
|
426
|
+
<script#{nonce_attr}#{hydration_attrs}>
|
427
|
+
var dataScript = document.getElementById('#{unique_id}');
|
428
|
+
var targetName = dataScript.getAttribute('data-window') || '#{window_attr}';
|
429
|
+
window[targetName] = JSON.parse(dataScript.textContent);
|
430
|
+
</script>
|
431
|
+
HTML
|
432
|
+
else
|
433
|
+
<<~HTML.strip
|
434
|
+
<script#{nonce_attr}#{hydration_attrs}>
|
435
|
+
window['#{window_attr}'] = JSON.parse(document.getElementById('#{unique_id}').textContent);
|
436
|
+
</script>
|
437
|
+
HTML
|
438
|
+
end
|
359
439
|
|
360
440
|
hydration_parts << json_script
|
361
441
|
hydration_parts << hydration_script
|
362
442
|
end
|
363
443
|
|
444
|
+
# Add reflection utilities if enabled
|
445
|
+
if reflection_enabled? && !merged_data.empty?
|
446
|
+
hydration_parts << generate_reflection_utilities
|
447
|
+
end
|
448
|
+
|
364
449
|
hydration_parts.join("\n")
|
365
450
|
end
|
366
451
|
|
452
|
+
# Check if reflection system is enabled
|
453
|
+
def reflection_enabled?
|
454
|
+
@config.hydration.reflection_enabled
|
455
|
+
end
|
456
|
+
|
457
|
+
# Generate JavaScript utilities for hydration reflection
|
458
|
+
def generate_reflection_utilities
|
459
|
+
nonce_attr = nonce_attribute
|
460
|
+
|
461
|
+
<<~HTML.strip
|
462
|
+
<script#{nonce_attr}>
|
463
|
+
// Rhales hydration reflection utilities
|
464
|
+
window.__rhales__ = window.__rhales__ || {
|
465
|
+
getHydrationTargets: function() {
|
466
|
+
return Array.from(document.querySelectorAll('[data-hydration-target]'));
|
467
|
+
},
|
468
|
+
getDataForTarget: function(target) {
|
469
|
+
var targetName = target.dataset.hydrationTarget;
|
470
|
+
return targetName ? window[targetName] : undefined;
|
471
|
+
},
|
472
|
+
getWindowAttribute: function(scriptEl) {
|
473
|
+
return scriptEl.dataset.window;
|
474
|
+
},
|
475
|
+
getDataScripts: function() {
|
476
|
+
return Array.from(document.querySelectorAll('script[data-window]'));
|
477
|
+
},
|
478
|
+
refreshData: function(target) {
|
479
|
+
var targetName = target.dataset.hydrationTarget;
|
480
|
+
var dataScript = document.querySelector('script[data-window="' + targetName + '"]');
|
481
|
+
if (dataScript && targetName) {
|
482
|
+
try {
|
483
|
+
window[targetName] = JSON.parse(dataScript.textContent);
|
484
|
+
return true;
|
485
|
+
} catch (e) {
|
486
|
+
console.error('Rhales: Failed to refresh data for ' + targetName, e);
|
487
|
+
return false;
|
488
|
+
}
|
489
|
+
}
|
490
|
+
return false;
|
491
|
+
},
|
492
|
+
getAllHydrationData: function() {
|
493
|
+
var data = {};
|
494
|
+
this.getHydrationTargets().forEach(function(target) {
|
495
|
+
var targetName = target.dataset.hydrationTarget;
|
496
|
+
if (targetName) {
|
497
|
+
data[targetName] = window[targetName];
|
498
|
+
}
|
499
|
+
});
|
500
|
+
return data;
|
501
|
+
}
|
502
|
+
};
|
503
|
+
</script>
|
504
|
+
HTML
|
505
|
+
end
|
506
|
+
|
367
507
|
# Get nonce attribute if available
|
368
508
|
def nonce_attribute
|
369
509
|
nonce = @rsfc_context.get('nonce')
|
370
|
-
nonce ? " nonce=\"#{nonce}\"" : ''
|
510
|
+
nonce ? " nonce=\"#{ERB::Util.html_escape(nonce)}\"" : ''
|
371
511
|
end
|
372
512
|
|
373
513
|
# Set CSP header if enabled
|
@@ -28,9 +28,10 @@ module Rhales
|
|
28
28
|
|
29
29
|
attr_reader :root_template_name, :templates, :dependencies
|
30
30
|
|
31
|
-
def initialize(root_template_name, loader:)
|
31
|
+
def initialize(root_template_name, loader:, config: nil)
|
32
32
|
@root_template_name = root_template_name
|
33
33
|
@loader = loader
|
34
|
+
@config = config
|
34
35
|
@templates = {}
|
35
36
|
@dependencies = {}
|
36
37
|
@loading = Set.new
|
@@ -69,7 +70,7 @@ module Rhales
|
|
69
70
|
end
|
70
71
|
|
71
72
|
# Check if a template exists in the composition
|
72
|
-
def
|
73
|
+
def template?(name)
|
73
74
|
@templates.key?(name)
|
74
75
|
end
|
75
76
|
|
@@ -83,9 +84,10 @@ module Rhales
|
|
83
84
|
@dependencies[template_name] || []
|
84
85
|
end
|
85
86
|
|
87
|
+
|
86
88
|
private
|
87
89
|
|
88
|
-
def load_template_recursive(template_name,
|
90
|
+
def load_template_recursive(template_name, _parent_path = nil)
|
89
91
|
# Check for circular dependencies
|
90
92
|
if @loading.include?(template_name)
|
91
93
|
raise CircularDependencyError, "Circular dependency detected: #{template_name} -> #{@loading.to_a.join(' -> ')}"
|
data/lib/rhales.rb
CHANGED
@@ -13,6 +13,12 @@ require_relative 'rhales/template_engine'
|
|
13
13
|
require_relative 'rhales/hydrator'
|
14
14
|
require_relative 'rhales/view_composition'
|
15
15
|
require_relative 'rhales/hydration_data_aggregator'
|
16
|
+
require_relative 'rhales/mount_point_detector'
|
17
|
+
require_relative 'rhales/safe_injection_validator'
|
18
|
+
require_relative 'rhales/earliest_injection_detector'
|
19
|
+
require_relative 'rhales/link_based_injection_detector'
|
20
|
+
require_relative 'rhales/hydration_injector'
|
21
|
+
require_relative 'rhales/hydration_endpoint'
|
16
22
|
require_relative 'rhales/refinements/require_refinements'
|
17
23
|
require_relative 'rhales/view'
|
18
24
|
|
@@ -30,9 +36,14 @@ require_relative 'rhales/view'
|
|
30
36
|
#
|
31
37
|
# Usage:
|
32
38
|
# Rhales.configure do |config|
|
33
|
-
# config.
|
39
|
+
# config.default_locale = 'en'
|
34
40
|
# config.template_paths = ['app/templates']
|
35
41
|
# config.features = { dark_mode: true }
|
42
|
+
#
|
43
|
+
# # Hydration configuration
|
44
|
+
# config.hydration.injection_strategy = :early # :early or :late (default)
|
45
|
+
# config.hydration.mount_point_selectors = ['#app', '#root', '[data-mount]']
|
46
|
+
# config.hydration.fallback_to_late = true
|
36
47
|
# end
|
37
48
|
#
|
38
49
|
# view = Rhales::View.new(request, session, user)
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rhales
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- delano
|
8
8
|
bindir: exe
|
9
9
|
cert_chain: []
|
10
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
11
11
|
dependencies: []
|
12
12
|
description: |
|
13
13
|
Rhales is a framework for building server-rendered components with
|
@@ -34,16 +34,22 @@ files:
|
|
34
34
|
- lib/rhales/configuration.rb
|
35
35
|
- lib/rhales/context.rb
|
36
36
|
- lib/rhales/csp.rb
|
37
|
+
- lib/rhales/earliest_injection_detector.rb
|
37
38
|
- lib/rhales/errors.rb
|
38
39
|
- lib/rhales/errors/hydration_collision_error.rb
|
39
40
|
- lib/rhales/hydration_data_aggregator.rb
|
41
|
+
- lib/rhales/hydration_endpoint.rb
|
42
|
+
- lib/rhales/hydration_injector.rb
|
40
43
|
- lib/rhales/hydration_registry.rb
|
41
44
|
- lib/rhales/hydrator.rb
|
45
|
+
- lib/rhales/link_based_injection_detector.rb
|
46
|
+
- lib/rhales/mount_point_detector.rb
|
42
47
|
- lib/rhales/parsers/handlebars-grammar-review.txt
|
43
48
|
- lib/rhales/parsers/handlebars_parser.rb
|
44
49
|
- lib/rhales/parsers/rue_format_parser.rb
|
45
50
|
- lib/rhales/refinements/require_refinements.rb
|
46
51
|
- lib/rhales/rue_document.rb
|
52
|
+
- lib/rhales/safe_injection_validator.rb
|
47
53
|
- lib/rhales/template_engine.rb
|
48
54
|
- lib/rhales/tilt.rb
|
49
55
|
- lib/rhales/version.rb
|
@@ -72,7 +78,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
72
78
|
- !ruby/object:Gem::Version
|
73
79
|
version: '0'
|
74
80
|
requirements: []
|
75
|
-
rubygems_version: 3.6.
|
81
|
+
rubygems_version: 3.6.9
|
76
82
|
specification_version: 4
|
77
83
|
summary: Rhales - Server-rendered components with client-side hydration (RSFCs)
|
78
84
|
test_files: []
|