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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6b65514ddc21b35416e7856a21f17f084cde6f6176a0f84dd8ee03a3ec7c8604
4
- data.tar.gz: e6390a45d84dc78708aa3f043f053c41bba8b68c30170b11a58282581365c09d
3
+ metadata.gz: 3b6ae46cc828d11dd9474dbaad7453e48cd8c6282d6523732683e06812c919ba
4
+ data.tar.gz: d9245d48440921bca331af7c3be00843c21647cbd043473580c6a218f6a15f77
5
5
  SHA512:
6
- metadata.gz: 338db190efc246cb7a23f06b4a6f3dc620e969e3029cc2384f52bf8b082f6903dd21883b29316bcff676bb36f446e1fcad8cab434d8590f40b57a32a6d56baca
7
- data.tar.gz: 602303bc515970ac9085e1a5184d8684604b80c5047d65cc3bf6bb6aa2854fdce1bd9eb3a61b4ee8e0f0c74f3f347d63d62133c9e8df93b51d146eca67807da9
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[0...occ[:char_offset]].scan(opener).length
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 = text[0...char_offset]
1450
- count = prefix.scan(/<#{element_name}[>\s]/).length
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
- all_text = (lines1 + lines2).join
71
- non_ascii = Legend.detect_non_ascii(all_text, @visualization_map)
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
- all_text = (lines1 + lines2).join
23
- non_ascii = Legend.detect_non_ascii(all_text, @visualization_map)
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
- all_text = (lines1 + lines2).join
77
- non_ascii = Legend.detect_non_ascii(all_text, @visualization_map)
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.each_char.any? do |c|
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
- text.each_char do |char|
20
- next if char.ord <= 127
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Canon
4
- VERSION = "0.3.39"
4
+ VERSION = "0.3.41"
5
5
  end
@@ -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)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: canon
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.39
4
+ version: 0.3.41
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.