tina4ruby 3.13.46 → 3.13.47

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: 46946847036ae1f2e6a3fede0084ffe84ab4144b7ec7a16570048b70994a7bee
4
- data.tar.gz: db9caef85f32313bf13190f4ed5c3501c7783e82a155b5e59b7f21a6f87bd2f9
3
+ metadata.gz: '008f9200b3c67bd44a13fba5bbfeb6895792ff8844ca01815fcb116adf1cf526'
4
+ data.tar.gz: ff713e6d0bcefb5795d8a8f10eb5de37893bd8f24fb026bcff2b13128236467d
5
5
  SHA512:
6
- metadata.gz: decedd4ef3df3a7ee7243e64975ecc76471c763ec18fe2f8d024e683e02e3329c016baeff41c680cc75747c90cda61b4581fd7370ce34629a141f3fd6faf8489
7
- data.tar.gz: fd1c3e6b602d540388a56b2a0288d903adafc4e137b232645e7ee3e871c20885edadc531528ce443bd800045c175734666db312efed827cb8cef4c550dcd9cb1
6
+ metadata.gz: b8f63842da92b8084f4adec4920cf4d969d7c344f0d7db43bdea734f513f3eade99be7ffc63af89176f071c7fc411b9d3e083f96b79859415986a855967deb75
7
+ data.tar.gz: 1aa428df186321c9640cf4b790a9657619ba5c96c8de14016b4f805ba64fe19440ba7ffe8ec60104a7505f81b6d8c1a5cb7677489c844edbf54e9e82ced042f5
@@ -366,59 +366,134 @@ module Tina4
366
366
  sql.gsub(SMART_QUOTE_RE, SMART_QUOTES)
367
367
  end
368
368
 
369
- # Split SQL into individual statements, handling:
370
- # - $$ delimited stored procedure blocks
371
- # - // delimited blocks
372
- # - Block comments /* ... */
373
- # - Line comments -- ...
374
- # Matches the Python/Node.js approach: extract blocks first, split on ;, restore blocks.
369
+ # Split SQL into individual statements with a single-pass, quote- and
370
+ # comment-aware scanner. The split decision is made character by character so
371
+ # the delimiter only ever fires in real statement position.
372
+ #
373
+ # This is the fix for issue #54: the old implementation split on +delimiter+
374
+ # BEFORE stripping +-- …+ line comments, so a +;+ inside a line comment
375
+ # fragmented one statement into several broken pieces. A scanner that knows
376
+ # where it is (code / comment / string) cannot make that mistake.
377
+ #
378
+ # Handled, in priority order, only when NOT already inside a stored-proc block:
379
+ # - $$ … $$ and // … // stored-proc blocks are kept intact (inner ; never
380
+ # splits). A // preceded by ':' is a URL scheme (https://…), not a delimiter.
381
+ # - /* … */ block comments are stripped.
382
+ # - -- … line comments are stripped to end of line (the newline is kept).
383
+ # - '…' single-quoted strings and "…" double-quoted identifiers are copied
384
+ # verbatim, honouring the SQL doubled-quote escape ('' / ""); a ;, -- or /*
385
+ # inside a literal is data, not a delimiter or comment.
386
+ # Mirrors the tina4-python _split_statements / tina4-php scanner (parity).
375
387
  def split_sql_statements(sql, delimiter = ";")
376
388
  # Normalize smart/curly quotes to straight ASCII first, so SQL pasted from
377
389
  # an editor/doc (which converts " → “ ” and ' → ‘ ’) actually runs.
378
390
  sql = normalize_quotes(sql)
379
391
 
380
- blocks = []
392
+ statements = []
393
+ current = +""
394
+ n = sql.length
395
+ dlen = delimiter.length
396
+ i = 0
397
+ in_dollar_block = false
398
+ in_slash_block = false
399
+
400
+ while i < n
401
+ ch = sql[i]
402
+
403
+ # $$ … $$ stored-proc block (toggle).
404
+ if !in_slash_block && ch == "$" && i + 1 < n && sql[i + 1] == "$"
405
+ current << "$$"
406
+ i += 2
407
+ in_dollar_block = !in_dollar_block
408
+ next
409
+ end
381
410
 
382
- # Extract $$ ... $$ blocks (stored procedures, triggers, etc.)
383
- processed = sql.gsub(/\$\$(.*?)\$\$/m) do
384
- blocks << $~.to_s
385
- "__BLOCK_#{blocks.length - 1}__"
386
- end
411
+ # // // stored-proc block (toggle) but NOT a `://` URL scheme.
412
+ if !in_dollar_block && ch == "/" && i + 1 < n && sql[i + 1] == "/" &&
413
+ !(i.positive? && sql[i - 1] == ":")
414
+ current << "//"
415
+ i += 2
416
+ in_slash_block = !in_slash_block
417
+ next
418
+ end
387
419
 
388
- # Extract // ... // blocks (stored procedures, triggers, etc.). The `//`
389
- # delimiters must NOT be preceded by a colon, so a URL scheme
390
- # (`https://…`) or other `://` literal inside a migration is never
391
- # captured as an opaque stored-proc block (it would otherwise swallow
392
- # everything between two `//` occurrences and skip statement splitting).
393
- processed = processed.gsub(/(?<!:)\/\/(.*?)(?<!:)\/\//m) do
394
- blocks << $~.to_s
395
- "__BLOCK_#{blocks.length - 1}__"
396
- end
420
+ # Inside a stored-proc block: consume verbatim (inner ; never splits).
421
+ if in_dollar_block || in_slash_block
422
+ current << ch
423
+ i += 1
424
+ next
425
+ end
426
+
427
+ # Block comment /* … */ — stripped.
428
+ if ch == "/" && i + 1 < n && sql[i + 1] == "*"
429
+ endpos = sql.index("*/", i + 2)
430
+ i = endpos ? endpos + 2 : n
431
+ next
432
+ end
397
433
 
398
- # Remove block comments (/* ... */) but not inside stored proc blocks (already extracted)
399
- clean = processed.gsub(/\/\*.*?\*\//m, "")
434
+ # Line comment -- stripped to end of line; the newline is left for the
435
+ # next iteration so line structure (and NEXT-line boundaries) survive.
436
+ if ch == "-" && i + 1 < n && sql[i + 1] == "-"
437
+ endpos = sql.index("\n", i + 2)
438
+ i = endpos || n
439
+ next
440
+ end
400
441
 
401
- statements = []
402
- clean.split(delimiter).each do |stmt|
403
- lines = []
404
- stmt.split("\n").each do |line|
405
- stripped = line.strip
406
- next if stripped.empty? || stripped.start_with?("--")
407
- # Remove inline comments (-- after SQL)
408
- comment_pos = line.index("--")
409
- line = line[0...comment_pos] if comment_pos && comment_pos >= 0
410
- lines << line
442
+ # Single-quoted string literal — '' escapes a quote. Copied verbatim.
443
+ if ch == "'"
444
+ current << "'"
445
+ i += 1
446
+ while i < n
447
+ if sql[i] == "'" && i + 1 < n && sql[i + 1] == "'"
448
+ current << "''"
449
+ i += 2
450
+ elsif sql[i] == "'"
451
+ current << "'"
452
+ i += 1
453
+ break
454
+ else
455
+ current << sql[i]
456
+ i += 1
457
+ end
458
+ end
459
+ next
411
460
  end
412
- cleaned = lines.join("\n").strip
413
461
 
414
- # Restore block placeholders
415
- blocks.each_with_index do |block, i|
416
- cleaned = cleaned.gsub("__BLOCK_#{i}__", block)
462
+ # Double-quoted identifier — "" escapes a quote. Same verbatim handling.
463
+ if ch == '"'
464
+ current << '"'
465
+ i += 1
466
+ while i < n
467
+ if sql[i] == '"' && i + 1 < n && sql[i + 1] == '"'
468
+ current << '""'
469
+ i += 2
470
+ elsif sql[i] == '"'
471
+ current << '"'
472
+ i += 1
473
+ break
474
+ else
475
+ current << sql[i]
476
+ i += 1
477
+ end
478
+ end
479
+ next
480
+ end
481
+
482
+ # Statement delimiter — only reached outside blocks/comments/strings.
483
+ if !delimiter.empty? && sql[i, dlen] == delimiter
484
+ stmt = current.strip
485
+ statements << stmt unless stmt.empty?
486
+ current = +""
487
+ i += dlen
488
+ next
417
489
  end
418
490
 
419
- statements << cleaned unless cleaned.empty?
491
+ current << ch
492
+ i += 1
420
493
  end
421
494
 
495
+ stmt = current.strip
496
+ statements << stmt unless stmt.empty?
422
497
  statements
423
498
  end
424
499
 
@@ -103,6 +103,9 @@ module Tina4
103
103
  ""
104
104
  end
105
105
 
106
+ # Resolve #{ ... } interpolation (before $var substitution + nesting).
107
+ content = resolve_interpolation(content, variables)
108
+
106
109
  # Replace variable references
107
110
  variables.each do |name, value|
108
111
  content = content.gsub("$#{name}", value)
@@ -117,6 +120,22 @@ module Tina4
117
120
  content
118
121
  end
119
122
 
123
+ # Resolve SCSS #{ ... } interpolation. Each #{ expr } is replaced by its
124
+ # resolved inner text: a $variable inside the braces resolves to its value,
125
+ # anything else is inlined verbatim (trimmed). Lets a value carry a
126
+ # variable inside a string context plain $var substitution can't reach --
127
+ # e.g. calc(100% - #{$gap}) -> calc(100% - 20px) -- and lets a variable
128
+ # appear in a selector (.icon-#{$name} -> .icon-home). Run BEFORE nesting
129
+ # flatten so the literal braces never confuse the block matcher.
130
+ def resolve_interpolation(content, variables)
131
+ names = variables.keys.sort_by { |k| -k.length }
132
+ content.gsub(/#\{([^{}]*)\}/) do
133
+ inner = Regexp.last_match(1).strip
134
+ names.each { |name| inner = inner.gsub("$#{name}", variables[name]) }
135
+ inner
136
+ end
137
+ end
138
+
120
139
  def process_imports(content, base_dir)
121
140
  content.gsub(/@import\s+["'](.+?)["']\s*;/) do
122
141
  import_path = Regexp.last_match(1)
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.46"
4
+ VERSION = "3.13.47"
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.46
4
+ version: 3.13.47
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-24 00:00:00.000000000 Z
11
+ date: 2026-06-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack