tina4ruby 3.10.15 → 3.10.18

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: 89bb63a5bf75c1e5100a6180e7283668bceb7a422e3dbfcb9ab175634ae0b8a5
4
- data.tar.gz: 020d40942508338964f6e9c3b06417a11303f2997eb55827baefe59c9443bb11
3
+ metadata.gz: b0dfa03a5f7986169a14d59895459e7ca50d70f0254789c36dbc29aca80b1b93
4
+ data.tar.gz: e248121b6577b8c5d0ec172ab47e2f82235b500d3508f75e8e0fedd3071ac657
5
5
  SHA512:
6
- metadata.gz: 0c712a05318ae0cb27fa194121ff32999264835d27f7237e39cf3f80fd40b3b834b8e47a3a63c9605a43c0f503f284dc25bf3c3aa9441a69c14d192f382cacf2
7
- data.tar.gz: bebefcf988a513b917ca9803474b07cf3dd6c116c5b3595a52d9582484c4fdfddd7e4b31472128012455f2d508d56866e378ef52809902d616ea5ba6a42a5c4d
6
+ metadata.gz: 164505c4f5449c7fbfa15ca1b21fbdffa3beac78a7d84e907407ed11881e3a4335d7df8dd6c46cb4bb4fb11ff3f9b89d2cd4b8cd02a77bdfb72a584b965f942e
7
+ data.tar.gz: b8797d3fd1abe7baf178c73a752b47357d8e56753a699d833a100940433884d036211cfc9942fcee481ca56d610efcb50169ec086df04f31e549e73971b7fb65
data/lib/tina4/frond.rb CHANGED
@@ -495,6 +495,41 @@ module Tina4
495
495
  eval_expr(arg, context)
496
496
  end
497
497
 
498
+ # Find the first occurrence of +needle+ that is not inside quotes or
499
+ # parentheses. Returns the index, or -1 if not found.
500
+ def find_outside_quotes(expr, needle)
501
+ in_q = nil
502
+ depth = 0
503
+ i = 0
504
+ nlen = needle.length
505
+ while i <= expr.length - nlen
506
+ ch = expr[i]
507
+ if (ch == '"' || ch == "'") && depth == 0
508
+ if in_q.nil?
509
+ in_q = ch
510
+ elsif ch == in_q
511
+ in_q = nil
512
+ end
513
+ i += 1
514
+ next
515
+ end
516
+ if in_q
517
+ i += 1
518
+ next
519
+ end
520
+ if ch == "("
521
+ depth += 1
522
+ elsif ch == ")"
523
+ depth -= 1
524
+ end
525
+ if depth == 0 && expr[i, nlen] == needle
526
+ return i
527
+ end
528
+ i += 1
529
+ end
530
+ -1
531
+ end
532
+
498
533
  # Find the index of a top-level ``?`` that is part of a ternary operator.
499
534
  # Respects quoted strings, parentheses, and skips ``??`` (null coalesce).
500
535
  # Returns -1 if not found.
@@ -690,18 +725,46 @@ module Tina4
690
725
  return (Regexp.last_match(1).to_i..Regexp.last_match(2).to_i).to_a
691
726
  end
692
727
 
693
- # Ternary: condition ? "yes" : "no"
694
- ternary = expr.match(/\A(.+?)\s*\?\s*(.+?)\s*:\s*(.+)\z/)
695
- if ternary
696
- cond = eval_expr(ternary[1], context)
697
- return truthy?(cond) ? eval_expr(ternary[2], context) : eval_expr(ternary[3], context)
728
+ # Parenthesized sub-expression: (expr) strip parens and evaluate inner
729
+ if expr.start_with?("(") && expr.end_with?(")")
730
+ depth = 0
731
+ matched = true
732
+ expr.each_char.with_index do |ch, pi|
733
+ depth += 1 if ch == "("
734
+ depth -= 1 if ch == ")"
735
+ if depth == 0 && pi < expr.length - 1
736
+ matched = false
737
+ break
738
+ end
739
+ end
740
+ return eval_expr(expr[1..-2], context) if matched
741
+ end
742
+
743
+ # Ternary: condition ? "yes" : "no" — quote-aware
744
+ q_pos = find_outside_quotes(expr, "?")
745
+ if q_pos > 0
746
+ cond_part = expr[0...q_pos].strip
747
+ rest = expr[(q_pos + 1)..]
748
+ c_pos = find_outside_quotes(rest, ":")
749
+ if c_pos >= 0
750
+ true_part = rest[0...c_pos].strip
751
+ false_part = rest[(c_pos + 1)..].strip
752
+ cond = eval_expr(cond_part, context)
753
+ return truthy?(cond) ? eval_expr(true_part, context) : eval_expr(false_part, context)
754
+ end
698
755
  end
699
756
 
700
- # Jinja2-style inline if: value if condition else other_value
701
- inline_if = expr.match(/\A(.+?)\s+if\s+(.+?)\s+else\s+(.+)\z/)
702
- if inline_if
703
- cond = eval_expr(inline_if[2], context)
704
- return truthy?(cond) ? eval_expr(inline_if[1], context) : eval_expr(inline_if[3], context)
757
+ # Jinja2-style inline if: value if condition else other_value — quote-aware
758
+ if_pos = find_outside_quotes(expr, " if ")
759
+ if if_pos >= 0
760
+ else_pos = find_outside_quotes(expr, " else ")
761
+ if else_pos && else_pos > if_pos
762
+ value_part = expr[0...if_pos].strip
763
+ cond_part = expr[(if_pos + 4)...else_pos].strip
764
+ else_part = expr[(else_pos + 6)..].strip
765
+ cond = eval_expr(cond_part, context)
766
+ return truthy?(cond) ? eval_expr(value_part, context) : eval_expr(else_part, context)
767
+ end
705
768
  end
706
769
 
707
770
  # Null coalescing: value ?? "default"
@@ -1459,6 +1522,25 @@ module Tina4
1459
1522
  },
1460
1523
  "url_encode" => ->(v, *_a) { CGI.escape(v.to_s) },
1461
1524
 
1525
+ # -- JSON / JS --
1526
+ "to_json" => ->(v, *a) {
1527
+ indent = a[0] ? a[0].to_i : nil
1528
+ json = indent ? JSON.pretty_generate(v) : JSON.generate(v)
1529
+ # Escape <, >, & for safe HTML embedding
1530
+ Tina4::SafeString.new(json.gsub("<", '\u003c').gsub(">", '\u003e').gsub("&", '\u0026'))
1531
+ },
1532
+ "tojson" => ->(v, *a) {
1533
+ indent = a[0] ? a[0].to_i : nil
1534
+ json = indent ? JSON.pretty_generate(v) : JSON.generate(v)
1535
+ Tina4::SafeString.new(json.gsub("<", '\u003c').gsub(">", '\u003e').gsub("&", '\u0026'))
1536
+ },
1537
+ "js_escape" => ->(v, *_a) {
1538
+ Tina4::SafeString.new(
1539
+ v.to_s.gsub("\\", "\\\\").gsub("'", "\\'").gsub('"', '\\"')
1540
+ .gsub("\n", "\\n").gsub("\r", "\\r").gsub("\t", "\\t")
1541
+ )
1542
+ },
1543
+
1462
1544
  # -- Hashing --
1463
1545
  "md5" => ->(v, *_a) { Digest::MD5.hexdigest(v.to_s) },
1464
1546
  "sha256" => ->(v, *_a) { Digest::SHA256.hexdigest(v.to_s) },
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.10.15"
4
+ VERSION = "3.10.18"
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.10.15
4
+ version: 3.10.18
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-03-28 00:00:00.000000000 Z
11
+ date: 2026-03-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack