canon 0.2.0 → 0.2.1

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: 10234a14be49f993f58acac6d82bb6e8ca409d309bcd8446296f32f60ad6380f
4
- data.tar.gz: 80e29ddf981d17f0fcd5812921f6ec1fbdf056b3ae673dc4d3d94c9979cddc21
3
+ metadata.gz: bfa3648b7c5308716c22fbdc377d875dc60d479eb0e376cfe347eb02a279d3d7
4
+ data.tar.gz: 87024447cc6a13eaa8cb9d1edf91d360e35d2e6cb5a1f2b7035c067050fae24a
5
5
  SHA512:
6
- metadata.gz: bc63b5a8dae56a06781d8588809e8356eea618fdcdb13b933ea4c8a3c4f221201c05070a3ce100945c9f81ef6f122b73ff6ac723441aa2b3cbcc5fd31e8fee63
7
- data.tar.gz: fbcb9ceb782f7847e6f2c6afba275f7d68da410d253b6f34f263b5c91803a3fbaff031de9b2dda5b8787fc37d0c82e1cea97d3ae1489d507eaad969168a107a1
6
+ metadata.gz: 6299802984ef5bab4c658c534105491f853c9d6cd297a5df49e5f8c83e0b36d1b01d140c1ea510002cf2916f3902035e764dcd589e4e01c5eec10c4ba76fda28
7
+ data.tar.gz: 42e73a1c3f80942d7ad102187b8a19148eb8a4046539eabb45561a716711fa3a51ea228e7bc372b4f675f4777db9ad964ed6c4873c19a5be9dc375af8639d92d
@@ -130,10 +130,33 @@ Each difference displays:
130
130
  * **Status indicator**: `[NORMATIVE]` (green) or `[INFORMATIVE]` (yellow)
131
131
  * **Dimension**: Which aspect differs (colorized in magenta)
132
132
  * **Location**: XPath for XML/HTML, path for JSON/YAML (colorized in blue)
133
+ * **Reason**: When the reason contains visualized whitespace characters (░),
134
+ it is split across two lines with the before and after text vertically
135
+ aligned for easier visual comparison:
136
+ +
137
+ [source]
138
+ ----
139
+ Reason: Text: "░░term2░definition░░"
140
+ vs.: "░term2░definition░"
141
+ ----
142
+ +
143
+ When no visualized whitespace is present, the reason remains a single line.
133
144
  * **Expected section**: What was in File 1 (red heading, bold)
134
145
  * **Actual section**: What was in File 2 (green heading, bold)
135
146
  * **Changes summary**: Actionable description of the difference (yellow, bold)
136
147
 
148
+ When both the Expected and Actual values are short (under 30 characters), they
149
+ are rendered as compact single lines with aligned colons for succinctness:
150
+
151
+ [source]
152
+ ----
153
+ ⊖ Expected (File 1): "term2 definition"
154
+ ⊕ Actual (File 2) : "term2 definition"
155
+ ----
156
+
157
+ For longer values, they appear on separate lines with no blank line between
158
+ them.
159
+
137
160
  === Dimension-specific formats
138
161
 
139
162
  ==== Attribute presence differences
@@ -226,6 +226,9 @@ Canon::Comparison.equivalent?(html1, html2, preprocessing: :rendered)
226
226
  Pretty-prints before comparison:
227
227
  - Consistent indentation
228
228
  - One element per line
229
+ - Whitespace-only text nodes in strip-context elements (e.g. `<div>`) are
230
+ removed after formatting, so differences in structural whitespace between
231
+ elements do not produce false normative differences
229
232
  - Good for visual diffs
230
233
 
231
234
  [source,ruby]
@@ -393,12 +393,17 @@ module Canon
393
393
  end
394
394
  end
395
395
 
396
- # For :rendered preprocessing with Nokogiri nodes
397
- if preprocessing == :rendered
398
- # Normalize and return
396
+ # For preprocessing modes that require whitespace filtering,
397
+ # apply the same post-parsing normalization used for string inputs.
398
+ # This is needed because dom_diff() pre-parses HTML5 strings into
399
+ # Nokogiri fragments before calling HtmlComparator, bypassing the
400
+ # string-input path where these filters are normally applied.
401
+ if %i[normalize format rendered].include?(preprocessing)
399
402
  frag = node.is_a?(Nokogiri::XML::DocumentFragment) ? node : Nokogiri::XML.fragment(node.to_html)
400
403
  normalize_html_style_script_comments(frag)
401
- normalize_rendered_whitespace(frag, match_opts)
404
+ if preprocessing == :rendered
405
+ normalize_rendered_whitespace(frag, match_opts)
406
+ end
402
407
  remove_whitespace_only_text_nodes(frag)
403
408
  return frag
404
409
  end
@@ -14,6 +14,9 @@ module Canon
14
14
  # Formats dimension-specific detail for individual differences
15
15
  # Provides actionable, colorized output showing exactly what changed
16
16
  module DiffDetailFormatter
17
+ ANSI_ESCAPE = /\e\[[0-9;]*m/
18
+ COMPACT_DETAIL_MAX = 30
19
+
17
20
  class << self
18
21
  # Format all differences as a semantic diff report
19
22
  #
@@ -127,9 +130,7 @@ compact: false, expand_difference: false)
127
130
 
128
131
  # show reason if available
129
132
  if diff.respond_to?(:reason) && diff.reason
130
- output << "#{colorize('Reason:', :cyan, use_color,
131
- bold: true)} #{colorize(diff.reason,
132
- :yellow, use_color)}"
133
+ format_reason_line(output, diff.reason, use_color)
133
134
  end
134
135
  output << ""
135
136
 
@@ -138,13 +139,7 @@ compact: false, expand_difference: false)
138
139
  diff, use_color, compact: compact, expand_difference: expand_difference
139
140
  )
140
141
 
141
- output << colorize("⊖ Expected (File 1):", :red, use_color,
142
- bold: true)
143
- output << " #{detail1}"
144
- output << ""
145
- output << colorize("⊕ Actual (File 2):", :green, use_color,
146
- bold: true)
147
- output << " #{detail2}"
142
+ format_expected_actual(output, detail1, detail2, use_color)
148
143
 
149
144
  if changes && !changes.empty?
150
145
  output << ""
@@ -182,6 +177,52 @@ compact: false, expand_difference: false)
182
177
  colorize(error_msg, :red, use_color, bold: true)
183
178
  end
184
179
 
180
+ # Format the Reason line. When the reason contains visualized
181
+ # spaces (░), split into two vertically-aligned lines so the
182
+ # before/after text can be compared visually.
183
+ def format_reason_line(output, reason_text, use_color)
184
+ if reason_text.include?("\u2591") &&
185
+ reason_text.match?(/\A(Text|whitespace): .*\bvs\b/)
186
+ parts = reason_text.split(" vs ", 2)
187
+ if parts.length == 2
188
+ output << "#{colorize('Reason:', :cyan, use_color,
189
+ bold: true)} #{colorize(parts[0],
190
+ :yellow, use_color)}"
191
+ output << "#{' ' * 10}#{colorize("vs.: #{parts[1]}",
192
+ :yellow, use_color)}"
193
+ return
194
+ end
195
+ end
196
+ output << "#{colorize('Reason:', :cyan, use_color,
197
+ bold: true)} #{colorize(reason_text,
198
+ :yellow, use_color)}"
199
+ end
200
+
201
+ # Format the Expected/Actual block. Short values (both under 30
202
+ # chars) are rendered as compact single lines with aligned colons;
203
+ # longer values use the multi-line layout without a blank line gap.
204
+ def format_expected_actual(output, detail1, detail2, use_color)
205
+ plain1 = detail1.gsub(ANSI_ESCAPE, "")
206
+ plain2 = detail2.gsub(ANSI_ESCAPE, "")
207
+
208
+ if plain1.length < COMPACT_DETAIL_MAX &&
209
+ plain2.length < COMPACT_DETAIL_MAX
210
+ lbl1 = colorize("\u2296 Expected (File 1)", :red, use_color,
211
+ bold: true)
212
+ lbl2 = colorize("\u2295 Actual (File 2) ", :green, use_color,
213
+ bold: true)
214
+ output << "#{lbl1}: #{detail1}"
215
+ output << "#{lbl2}: #{detail2}"
216
+ else
217
+ output << colorize("\u2296 Expected (File 1):", :red, use_color,
218
+ bold: true)
219
+ output << " #{detail1}"
220
+ output << colorize("\u2295 Actual (File 2):", :green, use_color,
221
+ bold: true)
222
+ output << " #{detail2}"
223
+ end
224
+ end
225
+
185
226
  # Helper: Colorize text
186
227
  def colorize(text, color, use_color, bold: false)
187
228
  DiffDetailFormatterHelpers::ColorHelper.colorize(text, color,
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.2.0"
4
+ VERSION = "0.2.1"
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.2.0
4
+ version: 0.2.1
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-04-12 00:00:00.000000000 Z
11
+ date: 2026-04-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: diff-lcs