tina4ruby 3.13.4 → 3.13.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f850a40f190c65782dd6f995b210335fa8b5c230c241b7d7031e34c0118f5d9e
4
- data.tar.gz: 15a8e7d8cfdadd898917d1604d64cfd3f84dcc83838f5c4bf2a7f2c7ec66df3c
3
+ metadata.gz: f5a305c0c2ee88795cac09fabe5c275165fb83d6da1d83bc301a8097660d711c
4
+ data.tar.gz: 68ee188aef588fd0e09d7779771de3eb47ed7ab5d9cfe426d3840b3015000064
5
5
  SHA512:
6
- metadata.gz: a457acd04c35cfd41a88def3fae8717b054a131fdd674d00eff2eda4bdc5a6084918b5f40622090613f7009eb7007b0135ff0b0cb3df0749d1def6b7ca0834cd
7
- data.tar.gz: 65d449d0775ed2599739c7fceffe4f74fa1b0c488cb85a09e257070a3b6c1aa59c85e4d56a5bf0a84c6ad239b2e729099b29c8b9d5edf1719bf59738fe8852af
6
+ metadata.gz: af2cb8dd1b51f7569d864aa41c4f0c52105696bea48e6c6fb50dd76b1d9bd11cf5e724cfdccb717bf5d42e2e84f032e10af1ada89ce9a3b74e660bc0a6f7c4f0
7
+ data.tar.gz: 75b8d8f25a7efcfb38606414e1457b3bdbf0bedbe3d1ee0ec3b5dea2ee0a58ddb5292a9f1078dc212a9cc43df0625122e3a288d2fb78437479cd15bc26efeecd
data/lib/tina4/frond.rb CHANGED
@@ -21,6 +21,33 @@ module Tina4
21
21
  end
22
22
 
23
23
  class Frond
24
+ # -- Class-level registries ------------------------------------------------
25
+ # Persist globals, filters, and tests across hot-reloads and across module
26
+ # boundaries. When app.rb does ``Tina4::Frond.add_filter("money") { ... }``
27
+ # at startup before any instance exists, the registration sits here. Every
28
+ # subsequent ``Tina4::Frond.new`` drains these into its instance-local
29
+ # registries — so hot-reloads (which re-execute ``frond = Frond.new``) and
30
+ # late-constructed engines automatically inherit prior registrations.
31
+ #
32
+ # The same-name dual-callable (class + instance) methods below let callers
33
+ # write either ``Tina4::Frond.add_filter(...)`` (class-level only) or
34
+ # ``frond.add_filter(...)`` (updates both the class registry and the
35
+ # instance's live filter map). Parity with tina4-python's
36
+ # ``_ClassOrInstanceMethod`` descriptor.
37
+ @@class_filters = {}
38
+ @@class_globals = {}
39
+ @@class_tests = {}
40
+
41
+ # Clear the class-level globals/filters/tests registries.
42
+ #
43
+ # Useful in test fixtures to prevent leaking state between tests. Does
44
+ # NOT affect built-in filters or globals — only user-registered ones.
45
+ def self.clear_registry
46
+ @@class_filters = {}
47
+ @@class_globals = {}
48
+ @@class_tests = {}
49
+ end
50
+
24
51
  # -- Token types ----------------------------------------------------------
25
52
  TEXT = :text
26
53
  VAR = :var # {{ ... }}
@@ -187,6 +214,16 @@ module Tina4
187
214
 
188
215
  # Built-in global functions
189
216
  register_builtin_globals
217
+
218
+ # Drain class-level registries into this instance. Filters and tests
219
+ # registered via ``Tina4::Frond.add_filter`` BEFORE this instance was
220
+ # constructed flow in here. Globals likewise. This is the key to the
221
+ # static-facade: ``app.rb`` registers once at startup, and every
222
+ # Frond instance created later (including those born from hot-reloads)
223
+ # automatically inherits the registration. Parity with tina4-python.
224
+ @filters.merge!(@@class_filters)
225
+ @globals.merge!(@@class_globals)
226
+ @tests.merge!(@@class_tests)
190
227
  end
191
228
 
192
229
  # Render a template file with data. Uses token caching for performance.
@@ -249,19 +286,61 @@ module Tina4
249
286
  @dotted_split_cache.clear
250
287
  end
251
288
 
289
+ # Register a custom filter on the class registry only.
290
+ #
291
+ # Callable as ``Tina4::Frond.add_filter("money") { |v| ... }`` at app
292
+ # startup BEFORE any instance exists. The registration is remembered at
293
+ # class level so every later ``Tina4::Frond.new`` inherits it. To also
294
+ # update a live instance's filter map, use the instance method form.
295
+ def self.add_filter(name, &blk)
296
+ @@class_filters[name.to_s] = blk
297
+ end
298
+
299
+ # Register a custom test on the class registry only.
300
+ #
301
+ # Same dual-callable semantics as ``add_filter`` — see that method for
302
+ # the static-facade pattern.
303
+ def self.add_test(name, &blk)
304
+ @@class_tests[name.to_s] = blk
305
+ end
306
+
307
+ # Register a global variable on the class registry only.
308
+ #
309
+ # Same dual-callable semantics as ``add_filter`` — see that method for
310
+ # the static-facade pattern.
311
+ def self.add_global(name, value)
312
+ @@class_globals[name.to_s] = value
313
+ end
314
+
252
315
  # Register a custom filter.
316
+ #
317
+ # Updates BOTH the class registry (so future ``Tina4::Frond.new`` picks
318
+ # the filter up) AND this instance's live filter map (so the change is
319
+ # visible to subsequent renders on the current engine).
253
320
  def add_filter(name, &blk)
321
+ self.class.add_filter(name, &blk)
254
322
  @filters[name.to_s] = blk
323
+ self
255
324
  end
256
325
 
257
326
  # Register a custom test.
327
+ #
328
+ # Updates BOTH the class registry and this instance's live tests map.
329
+ # See ``add_filter`` for the dual-write semantics.
258
330
  def add_test(name, &blk)
331
+ self.class.add_test(name, &blk)
259
332
  @tests[name.to_s] = blk
333
+ self
260
334
  end
261
335
 
262
336
  # Register a global variable available in all templates.
337
+ #
338
+ # Updates BOTH the class registry and this instance's live globals map.
339
+ # See ``add_filter`` for the dual-write semantics.
263
340
  def add_global(name, value)
341
+ self.class.add_global(name, value)
264
342
  @globals[name.to_s] = value
343
+ self
265
344
  end
266
345
 
267
346
  # Enable sandbox mode.
data/lib/tina4/mcp.rb CHANGED
@@ -612,8 +612,12 @@ module Tina4
612
612
  line = err_lines.first.strip
613
613
  # Strip the absolute project_root prefix so the error reads
614
614
  # as "src/routes/foo.rb:3: syntax error, ..." instead of the
615
- # full /Users/... path.
616
- line.sub("#{project_root}/", "")
615
+ # full /Users/... path. Use gsub because Ruby 3.2's MRI parser
616
+ # double-prints the path — once as the file label prefix and
617
+ # once inside the (SyntaxError) reference — and the test asserts
618
+ # the absolute path appears nowhere in import_error.
619
+ # See: spec/mcp_spec.rb defensive file_write test (CI Ruby 3.2).
620
+ line.gsub("#{project_root}/", "")
617
621
  end
618
622
 
619
623
  redact_env = lambda do |key, value|
@@ -108,22 +108,29 @@ module Tina4
108
108
  else
109
109
  # RFC 9110 conformance — before falling through to 404, check whether
110
110
  # the PATH is known to the router under any OTHER method.
111
- # - OPTIONS request → 204 with Allow header (§9.3.7)
111
+ # - OPTIONS request → 204 with Allow header (§9.3.7). Bare OPTIONS
112
+ # on an unknown path also returns 204 (empty Allow header) —
113
+ # OPTIONS is a discovery method; rejecting unknown probes with
114
+ # 404 confuses link checkers and monitoring tools and breaks
115
+ # CORS preflight that lacks the Origin/ACRM headers our earlier
116
+ # fast-path requires. Matches PHP/Node behaviour. Fixes
117
+ # spec/rack_app_spec.rb OPTIONS preflight.
112
118
  # - Any other method (PUT on GET-only, TRACE, CONNECT, etc.)
113
- # → 405 with Allow header (§15.5.6 + §10.2.1)
119
+ # → 405 with Allow header (§15.5.6 + §10.2.1) when the path
120
+ # exists; → 404 when nothing about the path is known.
114
121
  allowed = Tina4::Router.methods_allowed_for_path(path)
115
- if !allowed.empty?
122
+ if method.to_s.upcase == "OPTIONS"
123
+ allow_header = allowed.empty? ? "" : allowed.join(", ")
124
+ rack_response = [204, { "allow" => allow_header, "content-length" => "0" }, [""]]
125
+ matched_pattern = nil
126
+ elsif !allowed.empty?
116
127
  allow_header = allowed.join(", ")
117
- if method.to_s.upcase == "OPTIONS"
118
- rack_response = [204, { "allow" => allow_header, "content-length" => "0" }, [""]]
119
- else
120
- body = %({"error":"Method Not Allowed","path":"#{path}","method":"#{method}","allow":[#{allowed.map { |m| %("#{m}") }.join(",")}],"status":405})
121
- rack_response = [405, {
122
- "allow" => allow_header,
123
- "content-type" => "application/json",
124
- "content-length" => body.bytesize.to_s
125
- }, [body]]
126
- end
128
+ body = %({"error":"Method Not Allowed","path":"#{path}","method":"#{method}","allow":[#{allowed.map { |m| %("#{m}") }.join(",")}],"status":405})
129
+ rack_response = [405, {
130
+ "allow" => allow_header,
131
+ "content-type" => "application/json",
132
+ "content-length" => body.bytesize.to_s
133
+ }, [body]]
127
134
  matched_pattern = nil
128
135
  else
129
136
  rack_response = handle_404(path)
data/lib/tina4/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Tina4
4
- VERSION = "3.13.4"
4
+ VERSION = "3.13.5"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tina4ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.13.4
4
+ version: 3.13.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tina4 Team
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-06-04 00:00:00.000000000 Z
11
+ date: 2026-06-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack