tina4ruby 3.13.26 → 3.13.29

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: 37b3d1b2d32cd45ef6a97096e97bd5c6a39009ace98ead88dac0fdc89e786528
4
- data.tar.gz: c02fc34b91be2c3dbad1f577b590be80d1eae9043803cd0fa2819ec465f47dce
3
+ metadata.gz: 01fb453b8f6a6ea027dc04bc3e1d56c808969f50739ca60946152030883e7fd0
4
+ data.tar.gz: b04d9e2902f61df6e37ae6eb81b95b6f60edf579739206ea97b9463a1ba5dd25
5
5
  SHA512:
6
- metadata.gz: 6efda648bfaf41d5fba86200f2a3d063e186982cf1325721dcbc49660933592492526802e4c052850e710887dbefd10275001738a2fb149d3abb1bae620e9445
7
- data.tar.gz: af138286dcc50de2e72defd69f535b9854ccbe5236b54c320a33b2a9e08487ff3ad557a90b6ae8486dd882c34c02d99d707b5abb8c344feb6a1e34b00a9d03d0
6
+ metadata.gz: 4c3ce7f5a22fef09964273c1645a3b1756a2359bf2d670bcc2b3c640bd8f0689a38653dd046b12c000fdc78ce9cbbb0dec386317a5d13e3cf09243c39461888f
7
+ data.tar.gz: dca9b42a7753fd2609d263867846d236e1c3e10b094f37a6030c690cadab75e3041b787e886c6feceb7dbafcb73b8bf3ba090c60fc69167e3309d1f3b79d378c
data/lib/tina4/docs.rb CHANGED
@@ -98,8 +98,7 @@ module Tina4
98
98
  # Full reflection of a single class — `nil` for unknown.
99
99
  def class_spec(fqn)
100
100
  ensure_index
101
- key = normalise_fqn(fqn)
102
- class_entry = all_entries.find { |e| e[:kind] == "class" && e[:fqn] == key }
101
+ class_entry = resolve_class(fqn)
103
102
  return nil if class_entry.nil?
104
103
 
105
104
  methods = all_entries.select do |m|
@@ -123,9 +122,10 @@ module Tina4
123
122
  # Single method spec — `nil` for unknown.
124
123
  def method_spec(class_fqn, method_name)
125
124
  ensure_index
126
- key = normalise_fqn(class_fqn)
125
+ klass = resolve_class(class_fqn)
126
+ resolved = klass ? klass[:fqn] : normalise_fqn(class_fqn)
127
127
  entry = all_entries.find do |e|
128
- e[:kind] == "method" && e[:class_fqn] == key && e[:name] == method_name.to_s
128
+ e[:kind] == "method" && e[:class_fqn] == resolved && e[:name] == method_name.to_s
129
129
  end
130
130
  entry && method_payload(entry)
131
131
  end
@@ -558,6 +558,38 @@ module Tina4
558
558
  tokens.each do |tk|
559
559
  score += 1 if !tk.empty? && doc.include?(tk)
560
560
  end
561
+
562
+ # Class-qualified queries ("Frond.add_test" / "Frond::add_test" /
563
+ # "Frond add_test"): score the owning class so the qualifier steers
564
+ # ranking instead of being dead weight.
565
+ class_fqn = (entry[:class_fqn] || entry[:class]).to_s
566
+ unless class_fqn.empty?
567
+ parent = class_fqn.split("::").last.to_s.downcase
568
+ unless parent.empty?
569
+ # Exact "Class.method" intent — normalise `.`/`::`/whitespace in the
570
+ # (already space-stripped) joined query to a single `.` separator.
571
+ q_norm = joined.gsub(/[:.]+/, ".")
572
+ if q_norm == "#{parent}.#{name}" || q_norm == "#{parent}.#{stripped}"
573
+ score += 6 # strongest signal we have
574
+ end
575
+ tokens.each do |tk|
576
+ next if tk.empty?
577
+ if tk == parent
578
+ score += 2.5
579
+ elsif parent.start_with?(tk)
580
+ score += 1
581
+ end
582
+ end
583
+ end
584
+ end
585
+
586
+ # Any token that is a whole dotted / :: segment of the fqn
587
+ # (namespace / class / method).
588
+ fqn_segs = entry[:fqn].to_s.downcase.split(/[.:#\s]+/).reject(&:empty?)
589
+ tokens.each do |tk|
590
+ score += 1 if !tk.empty? && fqn_segs.include?(tk)
591
+ end
592
+
561
593
  # Substring fallback — full joined query inside the name.
562
594
  if !joined.empty? && score.zero? && name.include?(joined)
563
595
  score += 2
@@ -632,5 +664,36 @@ module Tina4
632
664
  def normalise_fqn(fqn)
633
665
  fqn.to_s.sub(/\A::/, "")
634
666
  end
667
+
668
+ # Resolve a class entry by exact FQN, documented public path, or bare name.
669
+ #
670
+ # Callers naturally type the exact stored FQN (`Tina4::Database`), a bare
671
+ # class name (`Database`), or a longer path that nests the class name
672
+ # (`Tina4::Database::Database`). Match exactly first, then by class name
673
+ # (the last `::`/`.` segment), disambiguating by requiring the given
674
+ # segments to appear in the stored FQN (framework + shortest wins). A
675
+ # genuinely unknown name returns `nil` — no false positives.
676
+ def resolve_class(given)
677
+ classes = all_entries.select { |e| e[:kind] == "class" }
678
+ key = normalise_fqn(given)
679
+
680
+ exact = classes.find { |e| e[:fqn] == key }
681
+ return exact if exact
682
+
683
+ gsegs = key.split(/[:.]+/).reject(&:empty?)
684
+ gname = gsegs.empty? ? key : gsegs.last
685
+ cands = classes.select { |e| e[:fqn].to_s.split(/[:.]+/).last == gname }
686
+ return nil if cands.empty?
687
+ return cands.first if cands.size == 1
688
+
689
+ # Disambiguate: prefer classes whose stored FQN contains every given
690
+ # segment, then framework source, then the shortest FQN.
691
+ subset = cands.select do |e|
692
+ segs = e[:fqn].to_s.split(/[:.]+/)
693
+ gsegs.all? { |s| segs.include?(s) }
694
+ end
695
+ pool = subset.empty? ? cands : subset
696
+ pool.min_by { |e| [e[:source] == "framework" ? 0 : 1, e[:fqn].to_s.length, e[:fqn].to_s] }
697
+ end
635
698
  end
636
699
  end
data/lib/tina4/frond.rb CHANGED
@@ -10,6 +10,7 @@ require "json"
10
10
  require "digest"
11
11
  require "base64"
12
12
  require "cgi"
13
+ require "erb"
13
14
  require "uri"
14
15
  require "date"
15
16
  require "time"
@@ -738,7 +739,7 @@ module Tina4
738
739
  when "title" then value.to_s.split.map(&:capitalize).join(" ")
739
740
  when "string" then value.to_s
740
741
  when "int" then value.to_i
741
- when "escape", "e" then Frond.escape_html(value.to_s)
742
+ when "escape", "e" then Tina4::SafeString.new(Frond.escape_html(value.to_s))
742
743
  else value
743
744
  end
744
745
  next
@@ -1042,9 +1043,26 @@ module Tina4
1042
1043
  # ── Literal values: strings, numbers, booleans, null ──
1043
1044
 
1044
1045
  def eval_literal(expr)
1045
- if (expr.start_with?('"') && expr.end_with?('"')) ||
1046
- (expr.start_with?("'") && expr.end_with?("'"))
1047
- return expr[1..-2]
1046
+ if expr.length >= 2 && (expr[0] == '"' || expr[0] == "'") && expr[-1] == expr[0]
1047
+ # Only a SINGLE complete string literal — i.e. the opening quote's
1048
+ # match is the final char. Without this check, `'a' ~ 'b'` and
1049
+ # `'Y' if x else 'N'` (which merely start and end with a quote) get
1050
+ # their outer quotes stripped here before concat / inline-if run.
1051
+ q = expr[0]
1052
+ i = 1
1053
+ single = false
1054
+ while i < expr.length
1055
+ if expr[i] == "\\"
1056
+ i += 2
1057
+ next
1058
+ end
1059
+ if expr[i] == q
1060
+ single = (i == expr.length - 1)
1061
+ break
1062
+ end
1063
+ i += 1
1064
+ end
1065
+ return expr[1..-2] if single
1048
1066
  end
1049
1067
  return expr.to_i if expr =~ INTEGER_RE
1050
1068
  return expr.to_f if expr =~ FLOAT_RE
@@ -1894,8 +1912,8 @@ module Tina4
1894
1912
  "striptags" => ->(v, *_a) { v.to_s.gsub(STRIPTAGS_RE, "") },
1895
1913
 
1896
1914
  # -- Encoding --
1897
- "escape" => ->(v, *_a) { Frond.escape_html(v.to_s) },
1898
- "e" => ->(v, *_a) { Frond.escape_html(v.to_s) },
1915
+ "escape" => ->(v, *_a) { Tina4::SafeString.new(Frond.escape_html(v.to_s)) },
1916
+ "e" => ->(v, *_a) { Tina4::SafeString.new(Frond.escape_html(v.to_s)) },
1899
1917
  "raw" => ->(v, *_a) { v },
1900
1918
  "safe" => ->(v, *_a) { v },
1901
1919
  "json_encode" => ->(v, *_a) { JSON.generate(v) rescue v.to_s },
@@ -1914,7 +1932,7 @@ module Tina4
1914
1932
  v.to_s
1915
1933
  end
1916
1934
  },
1917
- "url_encode" => ->(v, *_a) { CGI.escape(v.to_s) },
1935
+ "url_encode" => ->(v, *_a) { ERB::Util.url_encode(v.to_s) },
1918
1936
 
1919
1937
  # -- JSON / JS --
1920
1938
  "to_json" => ->(v, *a) {
@@ -2058,7 +2076,7 @@ module Tina4
2058
2076
  lines.join("\n")
2059
2077
  },
2060
2078
  "slug" => ->(v, *_a) { v.to_s.downcase.gsub(SLUG_CLEAN_RE, "-").gsub(SLUG_TRIM_RE, "") },
2061
- "nl2br" => ->(v, *_a) { v.to_s.gsub("\n", "<br>\n") },
2079
+ "nl2br" => ->(v, *_a) { Tina4::SafeString.new(Frond.escape_html(v.to_s).gsub("\n", "<br />\n")) },
2062
2080
  "format" => ->(v, *a) {
2063
2081
  if a.any?
2064
2082
  v.to_s % a
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.26"
4
+ VERSION = "3.13.29"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tina4ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.13.26
4
+ version: 3.13.29
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tina4 Team