canon 0.3.39 → 0.3.41
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 +4 -4
- data/lib/canon/diff/diff_node_enricher.rb +25 -3
- data/lib/canon/diff_formatter/by_line/html_formatter.rb +2 -2
- data/lib/canon/diff_formatter/by_line/simple_formatter.rb +2 -2
- data/lib/canon/diff_formatter/by_line/xml_formatter.rb +5 -3
- data/lib/canon/diff_formatter/diff_detail_formatter/text_utils.rb +1 -2
- data/lib/canon/diff_formatter/legend.rb +13 -2
- data/lib/canon/version.rb +1 -1
- data/lib/tasks/benchmark_runner.rb +22 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3b6ae46cc828d11dd9474dbaad7453e48cd8c6282d6523732683e06812c919ba
|
|
4
|
+
data.tar.gz: d9245d48440921bca331af7c3be00843c21647cbd043473580c6a218f6a15f77
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7e9eb37f4285e047e2defa49566b19f3d2ddec40cb1fe55f6711c16674dcd076a805244c1716996a442b1bac99dc44af8e2c56ef89c49d677a5cf6649096adcc
|
|
7
|
+
data.tar.gz: f39f78aae5823e33033d328138da4dc5a748c3ee1c55079190457f926bcebeea4767ffc5e515132cd3e767347d80b4065c7fdd1d8e60e7a0833306053271b2e3
|
|
@@ -1019,7 +1019,9 @@ module Canon
|
|
|
1019
1019
|
opener = /<#{element_name}[>\s]/
|
|
1020
1020
|
occurrences = SourceLocator.locate_all(value, text, line_map)
|
|
1021
1021
|
occurrences.each do |occ|
|
|
1022
|
-
count = text
|
|
1022
|
+
count = count_elements_before_position_open(text,
|
|
1023
|
+
occ[:char_offset],
|
|
1024
|
+
opener)
|
|
1023
1025
|
return occ if count == target_index
|
|
1024
1026
|
end
|
|
1025
1027
|
|
|
@@ -1445,9 +1447,29 @@ range_start, range_end)
|
|
|
1445
1447
|
# @param char_offset [Integer] character offset to check before
|
|
1446
1448
|
# @param element_name [String] name of element to count
|
|
1447
1449
|
# @return [Integer] element index (0-based) of the element containing the position
|
|
1450
|
+
# Occurrence count with offsets AT the element's own opening
|
|
1451
|
+
# tag (no inside-the-element correction — see
|
|
1452
|
+
# locate_element_at_index).
|
|
1453
|
+
def count_elements_before_position_open(text, char_offset, opener)
|
|
1454
|
+
count = 0
|
|
1455
|
+
pos = 0
|
|
1456
|
+
while (hit = text.index(opener, pos)) && hit < char_offset
|
|
1457
|
+
count += 1
|
|
1458
|
+
pos = hit + 1
|
|
1459
|
+
end
|
|
1460
|
+
count
|
|
1461
|
+
end
|
|
1462
|
+
|
|
1448
1463
|
def count_elements_before_position(text, char_offset, element_name)
|
|
1449
|
-
prefix
|
|
1450
|
-
|
|
1464
|
+
# Index loop instead of copying the prefix per call — the
|
|
1465
|
+
# copy was O(offset) on every occurrence check.
|
|
1466
|
+
opener = /<#{element_name}[>\s]/
|
|
1467
|
+
count = 0
|
|
1468
|
+
pos = 0
|
|
1469
|
+
while (hit = text.index(opener, pos)) && hit < char_offset
|
|
1470
|
+
count += 1
|
|
1471
|
+
pos = hit + 1
|
|
1472
|
+
end
|
|
1451
1473
|
# Subtract 1 because the count includes the element we are inside
|
|
1452
1474
|
[count - 1, 0].max
|
|
1453
1475
|
end
|
|
@@ -67,8 +67,8 @@ module Canon
|
|
|
67
67
|
output = []
|
|
68
68
|
|
|
69
69
|
# Detect non-ASCII characters
|
|
70
|
-
|
|
71
|
-
non_ascii
|
|
70
|
+
non_ascii = Legend.detect_non_ascii(doc1, @visualization_map)
|
|
71
|
+
non_ascii.merge!(Legend.detect_non_ascii(doc2, @visualization_map))
|
|
72
72
|
|
|
73
73
|
# Add Unicode legend if needed
|
|
74
74
|
unless non_ascii.empty?
|
|
@@ -19,8 +19,8 @@ module Canon
|
|
|
19
19
|
lines2 = doc2.split("\n", -1)
|
|
20
20
|
|
|
21
21
|
# Detect non-ASCII characters in the diff
|
|
22
|
-
|
|
23
|
-
non_ascii
|
|
22
|
+
non_ascii = Legend.detect_non_ascii(doc1, @visualization_map)
|
|
23
|
+
non_ascii.merge!(Legend.detect_non_ascii(doc2, @visualization_map))
|
|
24
24
|
|
|
25
25
|
# Add Unicode legend if any non-ASCII characters detected
|
|
26
26
|
unless non_ascii.empty?
|
|
@@ -72,9 +72,11 @@ module Canon
|
|
|
72
72
|
|
|
73
73
|
output = []
|
|
74
74
|
|
|
75
|
-
# Detect non-ASCII characters
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
# Detect non-ASCII characters — scan the source strings
|
|
76
|
+
# directly; joining the split lines back into one document
|
|
77
|
+
# per render was pure garbage.
|
|
78
|
+
non_ascii = Legend.detect_non_ascii(doc1, @visualization_map)
|
|
79
|
+
non_ascii.merge!(Legend.detect_non_ascii(doc2, @visualization_map))
|
|
78
80
|
|
|
79
81
|
# Add Unicode legend if needed
|
|
80
82
|
unless non_ascii.empty?
|
|
@@ -113,8 +113,7 @@ module Canon
|
|
|
113
113
|
def self.needs_escaping?(text)
|
|
114
114
|
return false if text.nil?
|
|
115
115
|
|
|
116
|
-
text.
|
|
117
|
-
codepoint = c.ord
|
|
116
|
+
text.each_codepoint.any? do |codepoint|
|
|
118
117
|
codepoint < 32 || codepoint >= 127 || codepoint == 34 || codepoint == 92
|
|
119
118
|
end
|
|
120
119
|
end
|
|
@@ -12,12 +12,23 @@ module Canon
|
|
|
12
12
|
# @param visualization_map [Hash] Character visualization map
|
|
13
13
|
# @return [Hash] Hash of characters with their metadata
|
|
14
14
|
def self.detect_non_ascii(text, visualization_map)
|
|
15
|
+
# each_char allocates a 1-char String per character — over two
|
|
16
|
+
# full documents per render that is the presentation stage's
|
|
17
|
+
# biggest allocation source. ASCII-only text (the common case)
|
|
18
|
+
# needs no scan at all; otherwise scan codepoints and
|
|
19
|
+
# materialize the character String only at non-ASCII offsets.
|
|
20
|
+
return {} if text.ascii_only?
|
|
21
|
+
|
|
15
22
|
detected = {}
|
|
16
23
|
category_map = DiffFormatter::CHARACTER_CATEGORY_MAP
|
|
17
24
|
metadata = DiffFormatter::CHARACTER_METADATA
|
|
18
25
|
|
|
19
|
-
|
|
20
|
-
|
|
26
|
+
index = -1
|
|
27
|
+
text.each_codepoint do |codepoint|
|
|
28
|
+
index += 1
|
|
29
|
+
next if codepoint <= 127
|
|
30
|
+
|
|
31
|
+
char = text[index, 1]
|
|
21
32
|
next if detected.key?(char)
|
|
22
33
|
|
|
23
34
|
visualization = visualization_map.fetch(char, char)
|
data/lib/canon/version.rb
CHANGED
|
@@ -225,6 +225,13 @@ compare_against: nil)
|
|
|
225
225
|
failure_means: "Slow formatting affects serialization performance. C14N is critical for digital signatures and XML canonicalization.",
|
|
226
226
|
compare_against: "Previous branch (main).",
|
|
227
227
|
},
|
|
228
|
+
by_line_rendering: {
|
|
229
|
+
name: "By-Line Rendering",
|
|
230
|
+
icon: "🖨️",
|
|
231
|
+
description: "Verbose by_line diff rendering (enricher, line builder, formatters). Referees the #86 rendering lane.",
|
|
232
|
+
failure_means: "Slow diff display affects developer-facing CLI output and CI failure reports.",
|
|
233
|
+
compare_against: "Previous branch (main). Inputs: documents with scattered value differences.",
|
|
234
|
+
},
|
|
228
235
|
data_comparison: {
|
|
229
236
|
name: "Data Comparison",
|
|
230
237
|
icon: "🧮",
|
|
@@ -272,6 +279,9 @@ compare_against: nil)
|
|
|
272
279
|
{ name: "JSON", method: :json_format, desc: "JSON formatting" },
|
|
273
280
|
{ name: "YAML", method: :yaml_format, desc: "YAML formatting" },
|
|
274
281
|
],
|
|
282
|
+
by_line_rendering: [
|
|
283
|
+
{ name: "XML", method: :by_line_render_xml, desc: "by_line XML render" },
|
|
284
|
+
],
|
|
275
285
|
data_comparison: [
|
|
276
286
|
{ name: "JSON", method: :json_compare_equivalent,
|
|
277
287
|
desc: "JSON equivalence" },
|
|
@@ -568,6 +578,18 @@ compare_against: nil)
|
|
|
568
578
|
yaml = DataGenerator.generate_yaml(items: @items)
|
|
569
579
|
data = YAML.safe_load(yaml, permitted_classes: [Time])
|
|
570
580
|
measure { Canon.format_yaml(data) }
|
|
581
|
+
when :by_line_render_xml
|
|
582
|
+
xml1 = DataGenerator.generate_xml(items: @items)
|
|
583
|
+
xml2 = DataGenerator.generate_xml(items: @items)
|
|
584
|
+
formatter = Canon::DiffFormatter.new(mode: :by_line,
|
|
585
|
+
display_preprocessing: :pretty_print,
|
|
586
|
+
use_color: false)
|
|
587
|
+
result = Canon::Comparison.equivalent?(xml1, xml2, format: :xml,
|
|
588
|
+
verbose: true)
|
|
589
|
+
measure do
|
|
590
|
+
formatter.format(result.differences, :xml,
|
|
591
|
+
doc1: xml1, doc2: xml2)
|
|
592
|
+
end
|
|
571
593
|
when :json_compare_equivalent
|
|
572
594
|
json1 = DataGenerator.generate_json(items: @items)
|
|
573
595
|
json2 = DataGenerator.generate_json(items: @items)
|