canon 0.3.44 → 0.3.45

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: e7b141fefd0f254d40b0ba7621c7400d882f5a0bcee1fc2f96a51424ce4c7c36
4
- data.tar.gz: '018e400f0949c85ca2db99aa362664e6434834b135e32efc891b9f4b1c794f46'
3
+ metadata.gz: a17cd55bc56c7287d027191b11cd33a6bc0999b29abce17bb8c368758379e4ad
4
+ data.tar.gz: 7b7c89255ad4134219b512265b6b26c08a1352742dfc1500a821a830fa3cd2d3
5
5
  SHA512:
6
- metadata.gz: 0d25eaf6dfa12649678fb9e770762fbf1ad51923d7da03b168aa36acbb11fe3242edb958069b9738bf097aa0c90188498b3408f1fd900589bc091394c578d394
7
- data.tar.gz: e519ee287d5016becf20d31ad057d825d9c6118273305ce713f13c8c0429172283b576fe6a0c477a91cfbda92328b8a4d4f3491b1e6c34e69425d24ff7f12eb1
6
+ metadata.gz: d4673a7e48e7fb4aa901ece0c6fc3c097e3c046af4aee63b959b218d8809a32ac9e8f4ea3db990ff969f521d9842e826e03f4815da8610fba7138070cc420f8e
7
+ data.tar.gz: f5771189aed0ef86eb98b32b9bd94261221688aece84f6f7b44861c66afcde65d80b3b0a2cb599bb96c18aaa575da4ff86d1bb385b48ff7a3ff34c858342a632
@@ -927,7 +927,7 @@ module Canon
927
927
  # @return [Integer] the last line number
928
928
  def find_end_line(start_line, line_map, content)
929
929
  newline_count = content.count("\n")
930
- [start_line + newline_count, line_map.length - 1].min
930
+ [start_line + newline_count, line_map.line_count - 1].min
931
931
  end
932
932
 
933
933
  # Find the occurrence of a value at a specific element index.
@@ -1016,7 +1016,7 @@ module Canon
1016
1016
  # so the prefix scan counts exactly the preceding siblings —
1017
1017
  # no "inside the element" correction (count_elements_before_
1018
1018
  # position subtracts one for text-node offsets).
1019
- opener = /<#{element_name}[>\s]/
1019
+ opener = opener_for(element_name)
1020
1020
  occurrences = SourceLocator.locate_all(value, text, line_map)
1021
1021
  occurrences.each do |occ|
1022
1022
  count = count_elements_before_position_open(text,
@@ -1093,7 +1093,7 @@ module Canon
1093
1093
  line_map)
1094
1094
  return nil unless line_idx
1095
1095
 
1096
- col = search_start - line_map[line_idx][:start_offset]
1096
+ col = search_start - line_map.start_at(line_idx)
1097
1097
  { char_offset: search_start, line_number: line_idx, col: col }
1098
1098
  end
1099
1099
 
@@ -1265,7 +1265,7 @@ range_start, range_end)
1265
1265
  line_map)
1266
1266
  return nil unless line_idx
1267
1267
 
1268
- col = value_pos - line_map[line_idx][:start_offset]
1268
+ col = value_pos - line_map.start_at(line_idx)
1269
1269
  return { char_offset: value_pos, line_number: line_idx,
1270
1270
  col: col }
1271
1271
  end
@@ -1280,7 +1280,7 @@ range_start, range_end)
1280
1280
  line_map)
1281
1281
  return nil unless line_idx
1282
1282
 
1283
- col = value_pos - line_map[line_idx][:start_offset]
1283
+ col = value_pos - line_map.start_at(line_idx)
1284
1284
  return { char_offset: value_pos, line_number: line_idx, col: col }
1285
1285
  end
1286
1286
 
@@ -1332,7 +1332,7 @@ range_start, range_end)
1332
1332
  line_map)
1333
1333
  return nil unless line_idx
1334
1334
 
1335
- col = value_pos - line_map[line_idx][:start_offset]
1335
+ col = value_pos - line_map.start_at(line_idx)
1336
1336
  return { char_offset: value_pos, line_number: line_idx, col: col }
1337
1337
  end
1338
1338
  end
@@ -1386,7 +1386,7 @@ range_start, range_end)
1386
1386
  line_map)
1387
1387
  return nil unless line_idx
1388
1388
 
1389
- col = anchor_pos - line_map[line_idx][:start_offset]
1389
+ col = anchor_pos - line_map.start_at(line_idx)
1390
1390
  return { char_offset: anchor_pos, line_number: line_idx,
1391
1391
  col: col }
1392
1392
  else
@@ -1395,7 +1395,7 @@ range_start, range_end)
1395
1395
  line_map)
1396
1396
  return nil unless line_idx
1397
1397
 
1398
- col = tag_end_pos - line_map[line_idx][:start_offset]
1398
+ col = tag_end_pos - line_map.start_at(line_idx)
1399
1399
  return { char_offset: tag_end_pos, line_number: line_idx,
1400
1400
  col: col }
1401
1401
  end
@@ -1447,6 +1447,15 @@ range_start, range_end)
1447
1447
  # @param char_offset [Integer] character offset to check before
1448
1448
  # @param element_name [String] name of element to count
1449
1449
  # @return [Integer] element index (0-based) of the element containing the position
1450
+ # Opener regexes memoized per element name — compiling
1451
+ # /<name[>\s]/ per call was an allocation per occurrence check.
1452
+ OPENER_CACHE = {}.compare_by_identity
1453
+ private_constant :OPENER_CACHE
1454
+
1455
+ def opener_for(element_name)
1456
+ OPENER_CACHE[element_name] ||= /<#{element_name}[>\s]/
1457
+ end
1458
+
1450
1459
  # Occurrence count with offsets AT the element's own opening
1451
1460
  # tag (no inside-the-element correction — see
1452
1461
  # locate_element_at_index).
@@ -15,23 +15,51 @@ module Canon
15
15
  # SourceLocator.locate("line2", "line1\nline2\nline3", line_map)
16
16
  # # => { char_offset: 6, line_number: 1, col: 0 }
17
17
  class SourceLocator
18
+ # Line offset map: two flat Integer arrays (starts, ends) — a
19
+ # hash-per-line was one allocation per document line on every
20
+ # enrichment, the presentation stage's quiet constant.
21
+ class LineMap
22
+ attr_reader :starts, :ends
23
+
24
+ def initialize(starts, ends)
25
+ @starts = starts
26
+ @ends = ends
27
+ end
28
+
29
+ def empty?
30
+ @starts.empty?
31
+ end
32
+
33
+ def line_count
34
+ @starts.length
35
+ end
36
+
37
+ def start_at(index)
38
+ @starts[index]
39
+ end
40
+
41
+ def end_at(index)
42
+ @ends[index]
43
+ end
44
+ end
45
+
18
46
  # Build a line offset map from source text.
19
- # Each entry records the start and end character offset of a line.
20
47
  #
21
48
  # @param text [String] the full source text
22
- # @return [Array<Hash>] array of { start_offset:, end_offset: } hashes,
23
- # one per line (0-indexed)
49
+ # @return [LineMap] flat offset arrays, one entry per line
50
+ # (0-indexed)
24
51
  def self.build_line_map(text)
25
- return [] if text.nil? || text.empty?
52
+ return LineMap.new([], []) if text.nil? || text.empty?
26
53
 
27
- map = []
54
+ starts = []
28
55
  offset = 0
29
56
  text.each_line do |line|
30
- line_end = offset + line.length
31
- map << { start_offset: offset, end_offset: line_end }
32
- offset = line_end
57
+ starts << offset
58
+ offset += line.length
33
59
  end
34
- map
60
+ ends = starts[1..] || []
61
+ ends << text.length
62
+ LineMap.new(starts, ends)
35
63
  end
36
64
 
37
65
  # Locate a substring within source text and return its position.
@@ -55,7 +83,7 @@ module Canon
55
83
  line_idx = find_line_for_offset(char_offset, line_map)
56
84
  return nil if line_idx.nil?
57
85
 
58
- col = char_offset - line_map[line_idx][:start_offset]
86
+ col = char_offset - line_map.start_at(line_idx)
59
87
 
60
88
  { char_offset: char_offset, line_number: line_idx, col: col }
61
89
  end
@@ -77,7 +105,7 @@ module Canon
77
105
  line_idx = find_line_for_offset(pos, line_map)
78
106
  break if line_idx.nil?
79
107
 
80
- col = pos - line_map[line_idx][:start_offset]
108
+ col = pos - line_map.start_at(line_idx)
81
109
  results << { char_offset: pos, line_number: line_idx, col: col }
82
110
  offset = pos + 1
83
111
  end
@@ -92,8 +120,8 @@ module Canon
92
120
  # @param line_map [Array<Hash>] the line offset map
93
121
  # @return [Integer, nil] the 0-based line index, or nil
94
122
  def find_line_for_offset(char_offset, line_map)
95
- line_map.bsearch_index do |entry|
96
- entry[:end_offset] > char_offset
123
+ line_map.ends.bsearch_index do |end_offset|
124
+ end_offset > char_offset
97
125
  end
98
126
  end
99
127
  end
data/lib/canon/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Canon
4
- VERSION = "0.3.44"
4
+ VERSION = "0.3.45"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: canon
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.44
4
+ version: 0.3.45
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-09-15 00:00:00.000000000 Z
11
+ date: 2026-09-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: diff-lcs