tina4ruby 3.13.4 → 3.13.6

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: 4cfc3ef750c6d75e22a7c26c3695e5e9a50c841d868b663dbbd0f0b3aaf58987
4
+ data.tar.gz: 2e6d763e7662cb24e13e3dbdb7147709b5d69a5e1d5fca893f68a36a0bde9f76
5
5
  SHA512:
6
- metadata.gz: a457acd04c35cfd41a88def3fae8717b054a131fdd674d00eff2eda4bdc5a6084918b5f40622090613f7009eb7007b0135ff0b0cb3df0749d1def6b7ca0834cd
7
- data.tar.gz: 65d449d0775ed2599739c7fceffe4f74fa1b0c488cb85a09e257070a3b6c1aa59c85e4d56a5bf0a84c6ad239b2e729099b29c8b9d5edf1719bf59738fe8852af
6
+ metadata.gz: f340ff33f11ec9e0c3c4cb577fb6eff61d5d674a69ca714fb7adf2e610e08f37201ec582c8e1e854ea31e52700195a7ed6308a4c102b0ff99ea1ebe362f0d6b0
7
+ data.tar.gz: 5f0d2ab921df913639914dfdb0799691353201b8bb12997524322181cbd22266ac537e409045212cf7c56a8cfb5db1d4a350f90adc3cf3dc53d817213c2a0b2d
@@ -118,7 +118,10 @@ module Tina4
118
118
 
119
119
  open_connection
120
120
  rescue LoadError
121
- raise "Firebird driver requires the 'fb' gem. Install it with: gem install fb"
121
+ raise LoadError,
122
+ "The 'fb' gem is required for Firebird connections. Install one of:\n" \
123
+ " bundle add fb # if your project uses Bundler\n" \
124
+ " gem install fb # bare driver"
122
125
  end
123
126
 
124
127
  def close
@@ -10,8 +10,9 @@ module Tina4
10
10
  require "mongo"
11
11
  rescue LoadError
12
12
  raise LoadError,
13
- "The 'mongo' gem is required for MongoDB connections. " \
14
- "Install: gem install mongo"
13
+ "The 'mongo' gem is required for MongoDB connections. Install one of:\n" \
14
+ " bundle add mongo # if your project uses Bundler\n" \
15
+ " gem install mongo # bare driver"
15
16
  end
16
17
 
17
18
  uri = build_uri(connection_string, username, password)
@@ -6,7 +6,14 @@ module Tina4
6
6
  attr_reader :connection
7
7
 
8
8
  def connect(connection_string, username: nil, password: nil)
9
- require "tiny_tds"
9
+ begin
10
+ require "tiny_tds"
11
+ rescue LoadError
12
+ raise LoadError,
13
+ "The 'tiny_tds' gem is required for MSSQL connections. Install one of:\n" \
14
+ " bundle add tiny_tds # if your project uses Bundler\n" \
15
+ " gem install tiny_tds # bare driver"
16
+ end
10
17
  uri = parse_connection(connection_string)
11
18
  @connection = TinyTds::Client.new(
12
19
  host: uri[:host],
@@ -6,7 +6,14 @@ module Tina4
6
6
  attr_reader :connection
7
7
 
8
8
  def connect(connection_string, username: nil, password: nil)
9
- require "mysql2"
9
+ begin
10
+ require "mysql2"
11
+ rescue LoadError
12
+ raise LoadError,
13
+ "The 'mysql2' gem is required for MySQL connections. Install one of:\n" \
14
+ " bundle add mysql2 # if your project uses Bundler\n" \
15
+ " gem install mysql2 # bare driver"
16
+ end
10
17
  uri = URI.parse(connection_string)
11
18
  @connection = Mysql2::Client.new(
12
19
  host: uri.host || "localhost",
@@ -21,8 +21,9 @@ module Tina4
21
21
  require "odbc"
22
22
  rescue LoadError
23
23
  raise LoadError,
24
- "The 'ruby-odbc' gem is required for ODBC connections. " \
25
- "Install: gem install ruby-odbc"
24
+ "The 'ruby-odbc' gem is required for ODBC connections. Install one of:\n" \
25
+ " bundle add ruby-odbc # if your project uses Bundler\n" \
26
+ " gem install ruby-odbc # bare driver"
26
27
  end
27
28
 
28
29
  dsn_string = connection_string.to_s
@@ -6,7 +6,14 @@ module Tina4
6
6
  attr_reader :connection
7
7
 
8
8
  def connect(connection_string, username: nil, password: nil)
9
- require "pg"
9
+ begin
10
+ require "pg"
11
+ rescue LoadError
12
+ raise LoadError,
13
+ "The 'pg' gem is required for PostgreSQL connections. Install one of:\n" \
14
+ " bundle add pg # if your project uses Bundler\n" \
15
+ " gem install pg # bare driver"
16
+ end
10
17
  url = connection_string
11
18
  if username || password
12
19
  uri = URI.parse(url)
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.6"
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.6
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-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack