canon 0.3.44 → 0.3.46
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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2dc57b150c1a0a2b9179f4b88a78c22aeb1529aa74d30857d8ca71a6159d6639
|
|
4
|
+
data.tar.gz: 65db8a7cadb8718b11a475858d3bfd5d4adcf0a4c738a7c940a6dfa60a6bfd3e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d411dc92e7978a904f303a95397f301286e9a12a3fc3732bb91919c2564375221b12cb13db64198f41942edeb5bbe2dfc8512b173cbfbdab44d27409d5a009a9
|
|
7
|
+
data.tar.gz: eaa339eae29ca147aa6ee9a5cecf9a328e69fc9b1f64b3acb742d11f4d451bef89efc11cde012e06e15f21b10ec09f95fbf36e0456c718ba925c015e47dba0ce
|
|
@@ -21,19 +21,22 @@ module Canon
|
|
|
21
21
|
# @param text1 [String] The first document (preprocessed)
|
|
22
22
|
# @param text2 [String] The second document (preprocessed)
|
|
23
23
|
# @return [Array<DiffLine>] The assembled diff lines
|
|
24
|
-
def self.build(diff_nodes, text1, text2)
|
|
24
|
+
def self.build(diff_nodes, text1, text2, lines1: nil, lines2: nil)
|
|
25
25
|
return [] if diff_nodes.nil? || diff_nodes.empty?
|
|
26
26
|
return [] if text1.nil? || text2.nil?
|
|
27
27
|
|
|
28
|
-
new(diff_nodes, text1, text2).build
|
|
28
|
+
new(diff_nodes, text1, text2, lines1, lines2).build
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
-
def initialize(diff_nodes, text1, text2)
|
|
31
|
+
def initialize(diff_nodes, text1, text2, lines1 = nil, lines2 = nil)
|
|
32
32
|
@diff_nodes = diff_nodes
|
|
33
33
|
@text1 = text1
|
|
34
34
|
@text2 = text2
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
# The caller may hand in the already-split lines (the
|
|
36
|
+
# formatter splits these documents too); re-splitting both
|
|
37
|
+
# documents per pipeline stage was ~2x the line strings.
|
|
38
|
+
@lines1 = lines1 || text1.split("\n")
|
|
39
|
+
@lines2 = lines2 || text2.split("\n")
|
|
37
40
|
# Build reverse indices for efficient content lookup in gap handling.
|
|
38
41
|
# Maps content string to array of line indices where that content appears.
|
|
39
42
|
@line_to_indices1 = build_line_index(@lines1)
|
|
@@ -22,21 +22,22 @@ module Canon
|
|
|
22
22
|
# @param text1 [String] The first document (preprocessed)
|
|
23
23
|
# @param text2 [String] The second document (preprocessed)
|
|
24
24
|
# @return [Array<DiffNode>] The same DiffNodes, enriched in place
|
|
25
|
-
def self.build(diff_nodes, text1, text2)
|
|
25
|
+
def self.build(diff_nodes, text1, text2, lines1: nil, lines2: nil)
|
|
26
26
|
return diff_nodes if diff_nodes.nil? || diff_nodes.empty?
|
|
27
27
|
return diff_nodes if text1.nil? || text2.nil?
|
|
28
28
|
|
|
29
|
-
new(diff_nodes, text1, text2).enrich
|
|
29
|
+
new(diff_nodes, text1, text2, lines1, lines2).enrich
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
-
def initialize(diff_nodes, text1, text2)
|
|
32
|
+
def initialize(diff_nodes, text1, text2, lines1 = nil, lines2 = nil)
|
|
33
33
|
@diff_nodes = diff_nodes
|
|
34
34
|
@text1 = text1
|
|
35
35
|
@text2 = text2
|
|
36
36
|
@line_map1 = SourceLocator.build_line_map(text1)
|
|
37
37
|
@line_map2 = SourceLocator.build_line_map(text2)
|
|
38
|
-
|
|
39
|
-
@
|
|
38
|
+
# Shared split — see DiffLineBuilder's note.
|
|
39
|
+
@lines1 = lines1 || text1.split("\n")
|
|
40
|
+
@lines2 = lines2 || text2.split("\n")
|
|
40
41
|
# Track occurrences for text_content dimension to find correct element instance
|
|
41
42
|
@text_occurrence1 = Hash.new(0)
|
|
42
43
|
@text_occurrence2 = Hash.new(0)
|
|
@@ -927,7 +928,7 @@ module Canon
|
|
|
927
928
|
# @return [Integer] the last line number
|
|
928
929
|
def find_end_line(start_line, line_map, content)
|
|
929
930
|
newline_count = content.count("\n")
|
|
930
|
-
[start_line + newline_count, line_map.
|
|
931
|
+
[start_line + newline_count, line_map.line_count - 1].min
|
|
931
932
|
end
|
|
932
933
|
|
|
933
934
|
# Find the occurrence of a value at a specific element index.
|
|
@@ -1016,7 +1017,7 @@ module Canon
|
|
|
1016
1017
|
# so the prefix scan counts exactly the preceding siblings —
|
|
1017
1018
|
# no "inside the element" correction (count_elements_before_
|
|
1018
1019
|
# position subtracts one for text-node offsets).
|
|
1019
|
-
opener =
|
|
1020
|
+
opener = opener_for(element_name)
|
|
1020
1021
|
occurrences = SourceLocator.locate_all(value, text, line_map)
|
|
1021
1022
|
occurrences.each do |occ|
|
|
1022
1023
|
count = count_elements_before_position_open(text,
|
|
@@ -1093,7 +1094,7 @@ module Canon
|
|
|
1093
1094
|
line_map)
|
|
1094
1095
|
return nil unless line_idx
|
|
1095
1096
|
|
|
1096
|
-
col = search_start - line_map
|
|
1097
|
+
col = search_start - line_map.start_at(line_idx)
|
|
1097
1098
|
{ char_offset: search_start, line_number: line_idx, col: col }
|
|
1098
1099
|
end
|
|
1099
1100
|
|
|
@@ -1265,7 +1266,7 @@ range_start, range_end)
|
|
|
1265
1266
|
line_map)
|
|
1266
1267
|
return nil unless line_idx
|
|
1267
1268
|
|
|
1268
|
-
col = value_pos - line_map
|
|
1269
|
+
col = value_pos - line_map.start_at(line_idx)
|
|
1269
1270
|
return { char_offset: value_pos, line_number: line_idx,
|
|
1270
1271
|
col: col }
|
|
1271
1272
|
end
|
|
@@ -1280,7 +1281,7 @@ range_start, range_end)
|
|
|
1280
1281
|
line_map)
|
|
1281
1282
|
return nil unless line_idx
|
|
1282
1283
|
|
|
1283
|
-
col = value_pos - line_map
|
|
1284
|
+
col = value_pos - line_map.start_at(line_idx)
|
|
1284
1285
|
return { char_offset: value_pos, line_number: line_idx, col: col }
|
|
1285
1286
|
end
|
|
1286
1287
|
|
|
@@ -1332,7 +1333,7 @@ range_start, range_end)
|
|
|
1332
1333
|
line_map)
|
|
1333
1334
|
return nil unless line_idx
|
|
1334
1335
|
|
|
1335
|
-
col = value_pos - line_map
|
|
1336
|
+
col = value_pos - line_map.start_at(line_idx)
|
|
1336
1337
|
return { char_offset: value_pos, line_number: line_idx, col: col }
|
|
1337
1338
|
end
|
|
1338
1339
|
end
|
|
@@ -1386,7 +1387,7 @@ range_start, range_end)
|
|
|
1386
1387
|
line_map)
|
|
1387
1388
|
return nil unless line_idx
|
|
1388
1389
|
|
|
1389
|
-
col = anchor_pos - line_map
|
|
1390
|
+
col = anchor_pos - line_map.start_at(line_idx)
|
|
1390
1391
|
return { char_offset: anchor_pos, line_number: line_idx,
|
|
1391
1392
|
col: col }
|
|
1392
1393
|
else
|
|
@@ -1395,7 +1396,7 @@ range_start, range_end)
|
|
|
1395
1396
|
line_map)
|
|
1396
1397
|
return nil unless line_idx
|
|
1397
1398
|
|
|
1398
|
-
col = tag_end_pos - line_map
|
|
1399
|
+
col = tag_end_pos - line_map.start_at(line_idx)
|
|
1399
1400
|
return { char_offset: tag_end_pos, line_number: line_idx,
|
|
1400
1401
|
col: col }
|
|
1401
1402
|
end
|
|
@@ -1447,6 +1448,15 @@ range_start, range_end)
|
|
|
1447
1448
|
# @param char_offset [Integer] character offset to check before
|
|
1448
1449
|
# @param element_name [String] name of element to count
|
|
1449
1450
|
# @return [Integer] element index (0-based) of the element containing the position
|
|
1451
|
+
# Opener regexes memoized per element name — compiling
|
|
1452
|
+
# /<name[>\s]/ per call was an allocation per occurrence check.
|
|
1453
|
+
OPENER_CACHE = {}.compare_by_identity
|
|
1454
|
+
private_constant :OPENER_CACHE
|
|
1455
|
+
|
|
1456
|
+
def opener_for(element_name)
|
|
1457
|
+
OPENER_CACHE[element_name] ||= /<#{element_name}[>\s]/
|
|
1458
|
+
end
|
|
1459
|
+
|
|
1450
1460
|
# Occurrence count with offsets AT the element's own opening
|
|
1451
1461
|
# tag (no inside-the-element correction — see
|
|
1452
1462
|
# 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 [
|
|
23
|
-
#
|
|
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
|
-
|
|
54
|
+
starts = []
|
|
28
55
|
offset = 0
|
|
29
56
|
text.each_line do |line|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
offset = line_end
|
|
57
|
+
starts << offset
|
|
58
|
+
offset += line.length
|
|
33
59
|
end
|
|
34
|
-
|
|
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
|
|
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
|
|
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 |
|
|
96
|
-
|
|
123
|
+
line_map.ends.bsearch_index do |end_offset|
|
|
124
|
+
end_offset > char_offset
|
|
97
125
|
end
|
|
98
126
|
end
|
|
99
127
|
end
|
|
@@ -44,12 +44,22 @@ module Canon
|
|
|
44
44
|
# Compute line number width BEFORE formatting
|
|
45
45
|
compute_line_num_width(doc1, doc2)
|
|
46
46
|
|
|
47
|
+
# The pipeline splits both documents once here; every stage
|
|
48
|
+
# downstream reuses the arrays (three redundant full-doc
|
|
49
|
+
# splits per render before).
|
|
50
|
+
@pipeline_lines1 = doc1.split("\n")
|
|
51
|
+
@pipeline_lines2 = doc2.split("\n")
|
|
52
|
+
|
|
47
53
|
# Phase 1: Enrich DiffNodes with character positions
|
|
48
|
-
Canon::Diff::DiffNodeEnricher.build(@differences, doc1, doc2
|
|
54
|
+
Canon::Diff::DiffNodeEnricher.build(@differences, doc1, doc2,
|
|
55
|
+
lines1: @pipeline_lines1,
|
|
56
|
+
lines2: @pipeline_lines2)
|
|
49
57
|
|
|
50
58
|
# Phase 2: Assemble DiffLines from enriched DiffNodes
|
|
51
59
|
diff_lines = Canon::Diff::DiffLineBuilder.build(@differences, doc1,
|
|
52
|
-
doc2
|
|
60
|
+
doc2,
|
|
61
|
+
lines1: @pipeline_lines1,
|
|
62
|
+
lines2: @pipeline_lines2)
|
|
53
63
|
|
|
54
64
|
# Layers 3-5: Build report through pipeline
|
|
55
65
|
report = Canon::Diff::DiffReportBuilder.build(
|
|
@@ -178,8 +188,8 @@ module Canon
|
|
|
178
188
|
def format_report(report, doc1, doc2)
|
|
179
189
|
return "" if report.contexts.empty?
|
|
180
190
|
|
|
181
|
-
lines1 = doc1.split("\n")
|
|
182
|
-
lines2 = doc2.split("\n")
|
|
191
|
+
lines1 = @pipeline_lines1 || doc1.split("\n")
|
|
192
|
+
lines2 = @pipeline_lines2 || doc2.split("\n")
|
|
183
193
|
|
|
184
194
|
output = []
|
|
185
195
|
|
data/lib/canon/version.rb
CHANGED
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.
|
|
4
|
+
version: 0.3.46
|
|
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-
|
|
11
|
+
date: 2026-09-16 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: diff-lcs
|