canon 0.1.14 → 0.1.15

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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/lib/canon/color_detector.rb +3 -3
  3. data/lib/canon/comparison/comparison_result.rb +1 -3
  4. data/lib/canon/comparison/dimensions/attribute_order_dimension.rb +1 -1
  5. data/lib/canon/comparison/dimensions/attribute_presence_dimension.rb +1 -1
  6. data/lib/canon/comparison/dimensions/comments_dimension.rb +2 -2
  7. data/lib/canon/comparison/dimensions/element_position_dimension.rb +1 -1
  8. data/lib/canon/comparison/dimensions/registry.rb +1 -1
  9. data/lib/canon/comparison/dimensions/structural_whitespace_dimension.rb +2 -2
  10. data/lib/canon/comparison/dimensions/text_content_dimension.rb +2 -2
  11. data/lib/canon/comparison/format_detector.rb +1 -1
  12. data/lib/canon/comparison/html_comparator.rb +2 -6
  13. data/lib/canon/comparison/markup_comparator.rb +7 -7
  14. data/lib/canon/comparison/strategies/semantic_tree_match_strategy.rb +3 -3
  15. data/lib/canon/comparison/xml_comparator/child_comparison.rb +2 -2
  16. data/lib/canon/comparison/xml_comparator/node_parser.rb +1 -1
  17. data/lib/canon/comparison/xml_comparator.rb +4 -8
  18. data/lib/canon/comparison/xml_node_comparison.rb +3 -3
  19. data/lib/canon/comparison.rb +1 -1
  20. data/lib/canon/config/env_provider.rb +3 -3
  21. data/lib/canon/diff/diff_block_builder.rb +2 -2
  22. data/lib/canon/diff/diff_context_builder.rb +1 -1
  23. data/lib/canon/diff/diff_node_mapper.rb +1 -1
  24. data/lib/canon/diff_formatter/by_line/base_formatter.rb +2 -2
  25. data/lib/canon/diff_formatter/by_line/html_formatter.rb +2 -2
  26. data/lib/canon/diff_formatter/by_line/xml_formatter.rb +2 -2
  27. data/lib/canon/diff_formatter/by_object/base_formatter.rb +1 -1
  28. data/lib/canon/diff_formatter/by_object/json_formatter.rb +3 -1
  29. data/lib/canon/diff_formatter/by_object/xml_formatter.rb +1 -1
  30. data/lib/canon/diff_formatter/debug_output.rb +4 -6
  31. data/lib/canon/diff_formatter/diff_detail_formatter/dimension_formatter.rb +1 -1
  32. data/lib/canon/diff_formatter/diff_detail_formatter/node_utils.rb +7 -0
  33. data/lib/canon/diff_formatter/diff_detail_formatter.rb +6 -0
  34. data/lib/canon/diff_formatter.rb +5 -5
  35. data/lib/canon/errors.rb +3 -3
  36. data/lib/canon/rspec_matchers.rb +2 -2
  37. data/lib/canon/tree_diff/adapters/json_adapter.rb +2 -6
  38. data/lib/canon/tree_diff/adapters/yaml_adapter.rb +2 -6
  39. data/lib/canon/tree_diff/core/matching.rb +2 -2
  40. data/lib/canon/tree_diff/core/node_signature.rb +2 -4
  41. data/lib/canon/tree_diff/core/tree_node.rb +1 -1
  42. data/lib/canon/tree_diff/operation_converter.rb +1 -1
  43. data/lib/canon/tree_diff/operation_converter_helpers/reason_builder.rb +1 -1
  44. data/lib/canon/version.rb +1 -1
  45. data/lib/canon/xml/line_range_mapper.rb +1 -1
  46. data/lib/canon/xml/nodes/attribute_node.rb +4 -0
  47. data/lib/canon/xml/nodes/element_node.rb +4 -0
  48. data/lib/canon/xml/nodes/namespace_node.rb +4 -0
  49. data/lib/canon/xml/nodes/processing_instruction_node.rb +4 -0
  50. data/lib/canon.rb +1 -1
  51. metadata +3 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 81995d22ec29adb9b2fb60f0ed8bc0219fe28e468c89a2001901b0f4521c757b
4
- data.tar.gz: fabc6e6c77e92848783e747459377caa787330d3360f83f544b6372cc68ba227
3
+ metadata.gz: '04860609f8d3300ccebf84a0f2208510600dcfaac4b3f54f698eb2de7eed0493'
4
+ data.tar.gz: fc50abe2a915d7d7ff1cd630c0a5a50849b6fd5780664cb6bb419300b2388743
5
5
  SHA512:
6
- metadata.gz: d33e2fcd54ae3b5cab9fdcfe980b1a8d1f2f97b1389ea430ecfda093b275e77f93e49e5a4f1171797df3fcb7f8d0ef28654301dbb846c16a4eb751018ea10129
7
- data.tar.gz: 85ffc85bf577b631c9aee7e16f81e0be163de2025154dd197962943c533cd3a1aa0d79799d084194780ff8654e766b0ac3ac36b635425d47e64009c71a4edb6d
6
+ metadata.gz: d1bcc3ad7439fdd7f65627c53cd0dc4b92d781bdbdaf330d2bb8ecc89b3319a4fc02e78de8e961cdb69854661881db39bf59c664210c7a59b7a011355b1db71b
7
+ data.tar.gz: cca1b3f2eee48054b5431118350e64acb46f485096cd8c88798e0935210985d63eb93de4727a42d17cfaad8ef03c55e7d1745922500f3a3495a791d86fd60676
@@ -82,7 +82,7 @@ module Canon
82
82
  # @return [Boolean] true if colors appear to be supported
83
83
  def detect_from_env
84
84
  # Check TERM variable
85
- term = ENV["TERM"]
85
+ term = ENV.fetch("TERM", nil)
86
86
  if term && NO_COLOR_TERMS.any? { |t| term.include?(t) }
87
87
  # Known no-color terminals
88
88
  return false
@@ -100,7 +100,7 @@ module Canon
100
100
  end
101
101
 
102
102
  # Check for known color-capable terminals
103
- colorterm = ENV["COLORTERM"]
103
+ colorterm = ENV.fetch("COLORTERM", nil)
104
104
  return true if COLOR_TERM_VALUES.include?(colorterm)
105
105
 
106
106
  # Default: assume colors are supported on modern terminals
@@ -125,7 +125,7 @@ module Canon
125
125
  # - Generic CI: check for specific TeamCity/Terminal variables
126
126
  #
127
127
  # @return [Boolean] true if CI environment likely supports colors
128
- def detect_ci_colors
128
+ def detect_ci_colors # rubocop:disable Naming/PredicateMethod
129
129
  # Most modern CI systems support ANSI colors
130
130
  # Only disable for explicitly known non-color CI
131
131
  return false if ENV["TERM"] == "dumb"
@@ -44,10 +44,8 @@ html_version: nil, match_options: nil, algorithm: :dom, original_strings: nil)
44
44
  if diff.is_a?(Canon::Diff::DiffNode)
45
45
  diff.normative?
46
46
  # Legacy Hash format - always considered normative (structural differences)
47
- elsif diff.is_a?(Hash)
48
- true
49
47
  else
50
- false
48
+ diff.is_a?(Hash)
51
49
  end
52
50
  end
53
51
  end
@@ -37,7 +37,7 @@ module Canon
37
37
  # @param order1 [Array<Symbol>] First attribute order
38
38
  # @param order2 [Array<Symbol>] Second attribute order
39
39
  # @return [Boolean] true if attribute order is exactly the same
40
- def compare_strict(order1, order2)
40
+ def compare_strict(order1, order2) # rubocop:disable Naming/PredicateMethod
41
41
  order1 == order2
42
42
  end
43
43
 
@@ -37,7 +37,7 @@ module Canon
37
37
  # @param names1 [Array<Symbol>] First attribute names
38
38
  # @param names2 [Array<Symbol>] Second attribute names
39
39
  # @return [Boolean] true if attribute names are exactly equal
40
- def compare_strict(names1, names2)
40
+ def compare_strict(names1, names2) # rubocop:disable Naming/PredicateMethod
41
41
  names1.sort == names2.sort
42
42
  end
43
43
 
@@ -37,7 +37,7 @@ module Canon
37
37
  # @param comments1 [Array<String>] First comments array
38
38
  # @param comments2 [Array<String>] Second comments array
39
39
  # @return [Boolean] true if comments are exactly equal
40
- def compare_strict(comments1, comments2)
40
+ def compare_strict(comments1, comments2) # rubocop:disable Naming/PredicateMethod
41
41
  comments1 == comments2
42
42
  end
43
43
 
@@ -48,7 +48,7 @@ module Canon
48
48
  # @param comments1 [Array<String>] First comments array
49
49
  # @param comments2 [Array<String>] Second comments array
50
50
  # @return [Boolean] true if normalized comments are equal
51
- def compare_normalize(comments1, comments2)
51
+ def compare_normalize(comments1, comments2) # rubocop:disable Naming/PredicateMethod
52
52
  normalize_comments(comments1) == normalize_comments(comments2)
53
53
  end
54
54
 
@@ -39,7 +39,7 @@ module Canon
39
39
  # @param pos1 [Integer] First position
40
40
  # @param pos2 [Integer] Second position
41
41
  # @return [Boolean] true if positions are equal
42
- def compare_strict(pos1, pos2)
42
+ def compare_strict(pos1, pos2) # rubocop:disable Naming/PredicateMethod
43
43
  pos1 == pos2
44
44
  end
45
45
 
@@ -67,7 +67,7 @@ module Canon
67
67
  # @param node2 [Object] Second node
68
68
  # @param behavior [Symbol] Comparison behavior
69
69
  # @return [Boolean] true if nodes match for this dimension
70
- def self.compare(dimension_name, node1, node2, behavior)
70
+ def self.compare(dimension_name, node1, node2, behavior) # rubocop:disable Naming/PredicateMethod
71
71
  dimension = get(dimension_name)
72
72
  dimension.equivalent?(node1, node2, behavior)
73
73
  end
@@ -41,7 +41,7 @@ module Canon
41
41
  # @param ws1 [Array<String>] First whitespace array
42
42
  # @param ws2 [Array<String>] Second whitespace array
43
43
  # @return [Boolean] true if structural whitespace is exactly equal
44
- def compare_strict(ws1, ws2)
44
+ def compare_strict(ws1, ws2) # rubocop:disable Naming/PredicateMethod
45
45
  ws1 == ws2
46
46
  end
47
47
 
@@ -52,7 +52,7 @@ module Canon
52
52
  # @param ws1 [Array<String>] First whitespace array
53
53
  # @param ws2 [Array<String>] Second whitespace array
54
54
  # @return [Boolean] true if normalized structural whitespace is equal
55
- def compare_normalize(ws1, ws2)
55
+ def compare_normalize(ws1, ws2) # rubocop:disable Naming/PredicateMethod
56
56
  normalize_whitespace(ws1) == normalize_whitespace(ws2)
57
57
  end
58
58
 
@@ -37,7 +37,7 @@ module Canon
37
37
  # @param text1 [String, nil] First text
38
38
  # @param text2 [String, nil] Second text
39
39
  # @return [Boolean] true if texts are exactly equal
40
- def compare_strict(text1, text2)
40
+ def compare_strict(text1, text2) # rubocop:disable Naming/PredicateMethod
41
41
  text1.to_s == text2.to_s
42
42
  end
43
43
 
@@ -49,7 +49,7 @@ module Canon
49
49
  # @param text1 [String, nil] First text
50
50
  # @param text2 [String, nil] Second text
51
51
  # @return [Boolean] true if normalized texts are equal
52
- def compare_normalize(text1, text2)
52
+ def compare_normalize(text1, text2) # rubocop:disable Naming/PredicateMethod
53
53
  normalized1 = normalize_text(text1)
54
54
  normalized2 = normalize_text(text2)
55
55
 
@@ -52,7 +52,7 @@ module Canon
52
52
  # @return [Symbol] Format type
53
53
  def detect_string(str)
54
54
  # Use cache for format detection
55
- Cache.fetch(:format_detect, Cache.key_for_format_detection(str)) do
55
+ Cache.fetch(:format_detect, Cache.key_for_format_detection(str)) do # rubocop:disable Lint/UselessDefaultValueArgument
56
56
  detect_string_uncached(str)
57
57
  end
58
58
  end
@@ -134,9 +134,7 @@ module Canon
134
134
  # Classify DiffNodes as normative/informative if we have verbose output
135
135
  if opts[:verbose] && !differences.empty?
136
136
  classifier = Canon::Diff::DiffClassifier.new(match_opts)
137
- classifier.classify_all(differences.select do |d|
138
- d.is_a?(Canon::Diff::DiffNode)
139
- end)
137
+ classifier.classify_all(differences.grep(Canon::Diff::DiffNode))
140
138
  end
141
139
 
142
140
  if opts[:verbose]
@@ -153,9 +151,7 @@ module Canon
153
151
  # Non-verbose mode: check equivalence
154
152
  # If comparison found differences, classify them to determine if normative
155
153
  classifier = Canon::Diff::DiffClassifier.new(match_opts)
156
- classifier.classify_all(differences.select do |d|
157
- d.is_a?(Canon::Diff::DiffNode)
158
- end)
154
+ classifier.classify_all(differences.grep(Canon::Diff::DiffNode))
159
155
  # Equivalent if no normative differences (matches semantic algorithm)
160
156
  differences.none?(&:normative?)
161
157
  else
@@ -116,13 +116,13 @@ module Canon
116
116
 
117
117
  # Canon::Xml::Node ElementNode
118
118
  if node.is_a?(Canon::Xml::Nodes::ElementNode)
119
- node.attribute_nodes.each_with_object({}) do |attr, hash|
120
- hash[attr.name] = attr.value
119
+ node.attribute_nodes.to_h do |attr|
120
+ [attr.name, attr.value]
121
121
  end
122
122
  # Nokogiri nodes
123
123
  elsif node.respond_to?(:attributes)
124
- node.attributes.each_with_object({}) do |(_, attr), hash|
125
- hash[attr.name] = attr.value
124
+ node.attributes.to_h do |_, attr|
125
+ [attr.name, attr.value]
126
126
  end
127
127
  else
128
128
  {}
@@ -231,9 +231,9 @@ module Canon
231
231
  # @param node [Object] Node to check
232
232
  # @return [Boolean] true if node is a text node
233
233
  def text_node?(node)
234
- node.respond_to?(:text?) && node.text? &&
235
- !node.respond_to?(:element?) ||
236
- node.respond_to?(:node_type) && node.node_type == :text
234
+ (node.respond_to?(:text?) && node.text? &&
235
+ !node.respond_to?(:element?)) ||
236
+ (node.respond_to?(:node_type) && node.node_type == :text)
237
237
  end
238
238
 
239
239
  # Get text content from a node
@@ -146,8 +146,8 @@ module Canon
146
146
  # MUST match DOM diff preprocessing EXACTLY (xml_comparator.rb:106-109)
147
147
  # Simple pattern: add newline between adjacent tags
148
148
  [
149
- xml1.gsub(/></, ">\n<"),
150
- xml2.gsub(/></, ">\n<"),
149
+ xml1.gsub("><", ">\n<"),
150
+ xml2.gsub("><", ">\n<"),
151
151
  ]
152
152
  end
153
153
 
@@ -185,7 +185,7 @@ module Canon
185
185
 
186
186
  # KEY FIX: Use simple gsub, NOT Canon.format
187
187
  # This ensures proper line-by-line display matching DOM diff format
188
- [html1.gsub(/></, ">\n<"), html2.gsub(/></, ">\n<")]
188
+ [html1.gsub("><", ">\n<"), html2.gsub("><", ">\n<")]
189
189
  end
190
190
 
191
191
  # Preprocess JSON documents
@@ -241,9 +241,9 @@ diff_children, differences)
241
241
  smaller_set = children2
242
242
  end
243
243
 
244
- smaller_set_names = smaller_set.map do |c|
244
+ smaller_set_names = smaller_set.filter_map do |c|
245
245
  c.respond_to?(:name) ? c.name : nil
246
- end.compact
246
+ end
247
247
 
248
248
  new_larger_set = []
249
249
  max_len = larger_set.length
@@ -26,7 +26,7 @@ module Canon
26
26
  end
27
27
 
28
28
  # Apply preprocessing to XML string before parsing
29
- xml_string = apply_preprocessing(node, preprocessing)
29
+ xml_string = apply_preprocessing(node, preprocessing).strip
30
30
 
31
31
  # Use Canon::Xml::DataModel for parsing to get Canon::Xml::Node instances
32
32
  Canon::Xml::DataModel.from_xml(xml_string,
@@ -123,17 +123,15 @@ module Canon
123
123
  # Classify DiffNodes as normative/informative if we have verbose output
124
124
  if opts[:verbose] && !differences.empty?
125
125
  classifier = Canon::Diff::DiffClassifier.new(match_opts)
126
- classifier.classify_all(differences.select do |d|
127
- d.is_a?(Canon::Diff::DiffNode)
128
- end)
126
+ classifier.classify_all(differences.grep(Canon::Diff::DiffNode))
129
127
  end
130
128
 
131
129
  if opts[:verbose]
132
130
  # Serialize parsed nodes for consistent formatting
133
131
  # This ensures both sides formatted identically, showing only real differences
134
132
  preprocessed = [
135
- serialize_node(node1).gsub(/></, ">\n<"),
136
- serialize_node(node2).gsub(/></, ">\n<"),
133
+ serialize_node(node1).gsub("><", ">\n<"),
134
+ serialize_node(node2).gsub("><", ">\n<"),
137
135
  ]
138
136
 
139
137
  ComparisonResult.new(
@@ -148,9 +146,7 @@ module Canon
148
146
  # Non-verbose mode: check equivalence
149
147
  # If comparison found differences, classify them to determine if normative
150
148
  classifier = Canon::Diff::DiffClassifier.new(match_opts)
151
- classifier.classify_all(differences.select do |d|
152
- d.is_a?(Canon::Diff::DiffNode)
153
- end)
149
+ classifier.classify_all(differences.grep(Canon::Diff::DiffNode))
154
150
  # Equivalent if no normative differences (matches semantic algorithm)
155
151
  differences.none?(&:normative?)
156
152
  else
@@ -281,9 +281,9 @@ diff_children, differences)
281
281
  # @param node [Object] Node to check
282
282
  # @return [Boolean] true if node is a text node
283
283
  def self.text_node?(node)
284
- node.respond_to?(:text?) && node.text? &&
285
- !node.respond_to?(:element?) ||
286
- node.respond_to?(:node_type) && node.node_type == :text
284
+ (node.respond_to?(:text?) && node.text? &&
285
+ !node.respond_to?(:element?)) ||
286
+ (node.respond_to?(:node_type) && node.node_type == :text)
287
287
  end
288
288
 
289
289
  # Extract text content from a node
@@ -495,7 +495,7 @@ module Canon
495
495
 
496
496
  # Use cache for string documents
497
497
  Cache.fetch(:document_parse,
498
- Cache.key_for_document(doc, format, preprocessing)) do
498
+ Cache.key_for_document(doc, format, preprocessing)) do # rubocop:disable Lint/UselessDefaultValueArgument
499
499
  yield doc
500
500
  end
501
501
  end
@@ -37,12 +37,12 @@ module Canon
37
37
  attributes.each do |attr|
38
38
  # Try format-specific ENV var first
39
39
  env_key = EnvSchema.env_key(format, config_type, attr)
40
- value = ENV[env_key]
40
+ value = ENV.fetch(env_key, nil)
41
41
 
42
42
  # Fall back to global ENV var if format-specific not set
43
43
  if value.nil?
44
44
  global_key = EnvSchema.global_env_key(attr)
45
- value = ENV[global_key]
45
+ value = ENV.fetch(global_key, nil)
46
46
  end
47
47
 
48
48
  # Convert and store if value exists
@@ -57,7 +57,7 @@ module Canon
57
57
  result = {}
58
58
  attributes.each do |attr|
59
59
  global_key = EnvSchema.global_env_key(attr)
60
- value = ENV[global_key]
60
+ value = ENV.fetch(global_key, nil)
61
61
 
62
62
  if value
63
63
  result[attr] = TypeConverter.convert(attr, value)
@@ -66,13 +66,13 @@ module Canon
66
66
  # Create a DiffBlock from lines
67
67
  def create_block(start_idx, end_idx, diff_lines)
68
68
  # Determine types from diff_lines
69
- types = diff_lines.map(&:type).uniq.map do |t|
69
+ types = diff_lines.map(&:type).uniq.filter_map do |t|
70
70
  case t
71
71
  when :added then "+"
72
72
  when :removed then "-"
73
73
  when :changed then "!"
74
74
  end
75
- end.compact
75
+ end
76
76
 
77
77
  # Create block
78
78
  block = DiffBlock.new(
@@ -33,7 +33,7 @@ grouping_lines: nil)
33
33
  grouped_blocks = if @grouping_lines
34
34
  group_nearby_blocks(@diff_blocks, @grouping_lines)
35
35
  else
36
- @diff_blocks.map { |block| [block] }
36
+ @diff_blocks.zip
37
37
  end
38
38
 
39
39
  # Create contexts with context lines
@@ -182,7 +182,7 @@ module Canon
182
182
  if node.respond_to?(:name) && node.name == line_element_name
183
183
  true
184
184
  # Check if the node's parent has the matching name (for TextNode diffs)
185
- elsif node.respond_to?(:parent) && node.parent.respond_to?(:name) && node.parent.name == line_element_name
185
+ elsif node.respond_to?(:parent) && node.parent.respond_to?(:name) && node.parent.name == line_element_name # rubocop:disable Style/IfWithBooleanLiteralBranches
186
186
  true
187
187
  else
188
188
  false
@@ -160,7 +160,7 @@ module Canon
160
160
 
161
161
  # Keep only context_lines after the last change
162
162
  keep_until = [last_change_pos + context_lines, hunk.length - 1].min
163
- hunk.slice!(keep_until + 1..-1) if keep_until < hunk.length - 1
163
+ hunk.slice!((keep_until + 1)..-1) if keep_until < hunk.length - 1
164
164
  end
165
165
 
166
166
  # Colorize text if color is enabled
@@ -454,7 +454,7 @@ module Canon
454
454
  # Try to get from config if available
455
455
  config = Canon::Config.instance
456
456
  # Default to 10,000 if config not available
457
- config&.xml&.diff&.max_diff_lines || 10_000
457
+ config&.xml&.diff&.max_diff_lines || 10_000 # rubocop:disable Style/SafeNavigationChainLength
458
458
  end
459
459
 
460
460
  # Build set of children of matched parents
@@ -294,9 +294,9 @@ module Canon
294
294
  @diff_grouping_lines)
295
295
  format_diff_groups(groups)
296
296
  else
297
- diff_sections.map do |s|
297
+ diff_sections.filter_map do |s|
298
298
  s[:formatted]
299
- end.compact.join("\n\n")
299
+ end.join("\n\n")
300
300
  end
301
301
 
302
302
  output << formatted_diffs
@@ -289,9 +289,9 @@ module Canon
289
289
  @diff_grouping_lines)
290
290
  format_diff_groups(groups)
291
291
  else
292
- diff_sections.map do |s|
292
+ diff_sections.filter_map do |s|
293
293
  s[:formatted]
294
- end.compact.join("\n\n")
294
+ end.join("\n\n")
295
295
  end
296
296
 
297
297
  output << formatted_diffs
@@ -298,7 +298,7 @@ show_diffs: :all)
298
298
  # Try to get from config if available
299
299
  config = Canon::Config.instance
300
300
  # Default to 10,000 if config not available
301
- config&.xml&.diff&.max_diff_lines || 10_000
301
+ config&.xml&.diff&.max_diff_lines || 10_000 # rubocop:disable Style/SafeNavigationChainLength
302
302
  end
303
303
  end
304
304
  end
@@ -293,7 +293,9 @@ module Canon
293
293
  if value.empty?
294
294
  "{}"
295
295
  else
296
- "{Hash with #{value.keys.length} keys: #{value.keys.take(3).map(&:to_s).join(', ')}#{value.keys.length > 3 ? '...' : ''}}"
296
+ "{Hash with #{value.keys.length} keys: " \
297
+ "#{value.keys.take(3).join(', ')}" \
298
+ "#{'...' if value.keys.length > 3}}"
297
299
  end
298
300
  else
299
301
  value.inspect
@@ -73,7 +73,7 @@ module Canon
73
73
  path = extract_dom_path(diff)
74
74
  end
75
75
 
76
- super(tree, path, diff)
76
+ super
77
77
  end
78
78
 
79
79
  private
@@ -27,9 +27,9 @@ module Canon
27
27
 
28
28
  output = []
29
29
  output << ""
30
- output << "=" * 80
30
+ output << ("=" * 80)
31
31
  output << "CANON VERBOSE MODE - DETAILED OPTIONS"
32
- output << "=" * 80
32
+ output << ("=" * 80)
33
33
  output << ""
34
34
 
35
35
  # Show match options as a table
@@ -44,7 +44,7 @@ module Canon
44
44
  output << format_comparison_summary(comparison_result)
45
45
  output << ""
46
46
 
47
- output << "=" * 80
47
+ output << ("=" * 80)
48
48
  output << ""
49
49
 
50
50
  output.join("\n")
@@ -63,9 +63,7 @@ module Canon
63
63
  internal_keys = %i[tree_diff_operations tree_diff_statistics
64
64
  tree_diff_matching]
65
65
 
66
- rows = comparison_result.match_options.reject do |dimension, _behavior|
67
- internal_keys.include?(dimension)
68
- end.map do |dimension, behavior|
66
+ rows = comparison_result.match_options.except(*internal_keys).map do |dimension, behavior|
69
67
  {
70
68
  dimension: dimension.to_s,
71
69
  behavior: behavior.to_s,
@@ -148,7 +148,7 @@ module Canon
148
148
  changed_str = changed.map do |prefix|
149
149
  attr_name = prefix.empty? ? "xmlns" : "xmlns:#{prefix}"
150
150
  "#{ColorHelper.colorize(attr_name, :cyan, use_color)}: " \
151
- "\"#{ns_decls1[prefix]}\" → \"#{ns_decls2[prefix]}\""
151
+ "\"#{ns_decls1[prefix]}\" → \"#{ns_decls2[prefix]}\""
152
152
  end.join(", ")
153
153
  changes_parts << "Changed: #{changed_str}"
154
154
  end
@@ -144,6 +144,11 @@ module Canon
144
144
  elsif node.respond_to?(:get_attribute)
145
145
  attr = node.get_attribute(attr_name)
146
146
  attr.respond_to?(:value) ? attr.value : attr
147
+ elsif node.respond_to?(:attribute_nodes)
148
+ attribute_node = node.attribute_nodes.find do |attr|
149
+ attr.name == attr_name.to_s
150
+ end
151
+ attribute_node&.value
147
152
  end
148
153
  end
149
154
 
@@ -162,6 +167,8 @@ module Canon
162
167
  node.inner_text
163
168
  elsif node.respond_to?(:value)
164
169
  node.value
170
+ elsif node.respond_to?(:node_info)
171
+ node.node_info
165
172
  elsif node.respond_to?(:to_s)
166
173
  node.to_s
167
174
  else
@@ -109,6 +109,12 @@ module Canon
109
109
  output << "#{colorize('Location:', :cyan, use_color,
110
110
  bold: true)} #{colorize(location, :blue,
111
111
  use_color)}"
112
+
113
+ # show reason if available
114
+ if diff.respond_to?(:reason) && diff.reason
115
+ output << "#{colorize('Reason:', :cyan, use_color,
116
+ bold: true)} #{colorize(diff.reason, :yellow, use_color)}"
117
+ end
112
118
  output << ""
113
119
 
114
120
  # Dimension-specific details
@@ -410,7 +410,7 @@ module Canon
410
410
  when :xml
411
411
  [
412
412
  Canon::Xml::C14n.canonicalize(expected, with_comments: false).gsub(
413
- /></, ">\n<"
413
+ "><", ">\n<"
414
414
  ),
415
415
  Canon::Xml::C14n.canonicalize(actual, with_comments: false).gsub(
416
416
  />\s+$/, ""
@@ -510,11 +510,11 @@ module Canon
510
510
  output << colorize("=== ORIGINAL INPUTS (Raw) ===", :cyan, :bold)
511
511
  output << ""
512
512
  output << colorize("EXPECTED:", :yellow, :bold)
513
- output << "-" * 70
513
+ output << ("-" * 70)
514
514
  output << raw1
515
515
  output << ""
516
516
  output << colorize("RECEIVED:", :yellow, :bold)
517
- output << "-" * 70
517
+ output << ("-" * 70)
518
518
  output << raw2
519
519
  output << ""
520
520
  output << ""
@@ -543,11 +543,11 @@ preprocessing_info = nil)
543
543
  end
544
544
  output << ""
545
545
  output << colorize("EXPECTED:", :yellow, :bold)
546
- output << "-" * 70
546
+ output << ("-" * 70)
547
547
  output << preprocessed1
548
548
  output << ""
549
549
  output << colorize("RECEIVED:", :yellow, :bold)
550
- output << "-" * 70
550
+ output << ("-" * 70)
551
551
  output << preprocessed2
552
552
  output << ""
553
553
  output << ""
data/lib/canon/errors.rb CHANGED
@@ -83,13 +83,13 @@ module Canon
83
83
  case limit_type
84
84
  when :file_size
85
85
  "File size (#{format_bytes(actual)}) exceeds limit (#{format_bytes(limit)}). " \
86
- "Increase limit via CANON_MAX_FILE_SIZE or config.diff.max_file_size"
86
+ "Increase limit via CANON_MAX_FILE_SIZE or config.diff.max_file_size"
87
87
  when :node_count
88
88
  "Tree node count (#{actual}) exceeds limit (#{limit}). " \
89
- "Increase limit via CANON_MAX_NODE_COUNT or config.diff.max_node_count"
89
+ "Increase limit via CANON_MAX_NODE_COUNT or config.diff.max_node_count"
90
90
  when :diff_lines
91
91
  "Diff output (#{actual} lines) exceeds limit (#{limit} lines). " \
92
- "Output truncated. Increase limit via CANON_MAX_DIFF_LINES or config.diff.max_diff_lines"
92
+ "Output truncated. Increase limit via CANON_MAX_DIFF_LINES or config.diff.max_diff_lines"
93
93
  else
94
94
  "Size limit exceeded: #{limit_type} (#{actual} > #{limit})"
95
95
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "canon" unless defined?(::Canon)
3
+ require "canon" unless defined?(Canon)
4
4
  require "canon/comparison"
5
5
  require "canon/diff_formatter"
6
6
  require "canon/config"
@@ -137,7 +137,7 @@ module Canon
137
137
  @target
138
138
  end
139
139
 
140
- def diffable
140
+ def diffable # rubocop:disable Naming/PredicateMethod
141
141
  false
142
142
  end
143
143
 
@@ -166,13 +166,9 @@ module Canon
166
166
  # @param tree_node [Core::TreeNode] Array tree node
167
167
  # @return [Array] Reconstructed array
168
168
  def build_array(tree_node)
169
- array = []
170
-
171
- tree_node.children.each do |child|
172
- array << from_tree(child)
169
+ tree_node.children.map do |child|
170
+ from_tree(child)
173
171
  end
174
-
175
- array
176
172
  end
177
173
 
178
174
  # Parse value from value TreeNode
@@ -169,13 +169,9 @@ module Canon
169
169
  # @param tree_node [Core::TreeNode] Array tree node
170
170
  # @return [Array] Reconstructed array
171
171
  def build_array(tree_node)
172
- array = []
173
-
174
- tree_node.children.each do |child|
175
- array << from_tree(child)
172
+ tree_node.children.map do |child|
173
+ from_tree(child)
176
174
  end
177
-
178
- array
179
175
  end
180
176
 
181
177
  # Parse value from value TreeNode
@@ -28,7 +28,7 @@ module Canon
28
28
  # @param node1 [TreeNode] Node from tree 1
29
29
  # @param node2 [TreeNode] Node from tree 2
30
30
  # @return [Boolean] true if added, false if violates constraints
31
- def add(node1, node2)
31
+ def add(node1, node2) # rubocop:disable Naming/PredicateMethod
32
32
  return false unless valid_pair?(node1, node2)
33
33
 
34
34
  @pairs << [node1, node2]
@@ -43,7 +43,7 @@ module Canon
43
43
  # @param node1 [TreeNode] Node from tree 1
44
44
  # @param node2 [TreeNode] Node from tree 2
45
45
  # @return [Boolean] true if removed, false if not found
46
- def remove(node1, node2)
46
+ def remove(node1, node2) # rubocop:disable Naming/PredicateMethod
47
47
  removed = @pairs.delete([node1, node2])
48
48
  return false unless removed
49
49
 
@@ -81,12 +81,10 @@ module Canon
81
81
  #
82
82
  # @return [Array<String>] Path components
83
83
  def compute_path
84
- components = []
85
-
86
84
  # Build path from root to node
87
85
  ancestors = @node.ancestors.reverse
88
- ancestors.each do |ancestor|
89
- components << path_component(ancestor)
86
+ components = ancestors.map do |ancestor|
87
+ path_component(ancestor)
90
88
  end
91
89
 
92
90
  # Add the node itself
@@ -292,7 +292,7 @@ module Canon
292
292
  attr_diff = attribute_difference(other)
293
293
 
294
294
  # Weighted combination
295
- depth_diff * 0.3 + content_diff * 0.5 + attr_diff * 0.2
295
+ (depth_diff * 0.3) + (content_diff * 0.5) + (attr_diff * 0.2)
296
296
  end
297
297
 
298
298
  # Get content as a set for similarity calculation
@@ -352,7 +352,7 @@ module Canon
352
352
  #
353
353
  # @param dimension [Symbol] Match dimension
354
354
  # @return [Boolean] true if normative (should be shown)
355
- def determine_normative(dimension)
355
+ def determine_normative(dimension) # rubocop:disable Naming/PredicateMethod
356
356
  # Check match options behavior for this dimension
357
357
  behavior = @match_options.behavior_for(dimension)
358
358
 
@@ -160,7 +160,7 @@ module Canon
160
160
  text = text.to_s
161
161
  return text if text.length <= max_length
162
162
 
163
- "#{text[0...max_length - 3]}..."
163
+ "#{text[0...(max_length - 3)]}..."
164
164
  end
165
165
  end
166
166
  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.1.14"
4
+ VERSION = "0.1.15"
5
5
  end
@@ -40,7 +40,7 @@ module Canon
40
40
  # Line range for an element
41
41
  LineRange = Struct.new(:start_line, :end_line, :elem) do
42
42
  def contains?(line_num)
43
- line_num >= start_line && line_num <= end_line
43
+ line_num.between?(start_line, end_line)
44
44
  end
45
45
 
46
46
  def length
@@ -48,6 +48,10 @@ module Canon
48
48
  def xml_base?
49
49
  xml_attribute? && name == "base"
50
50
  end
51
+
52
+ def node_info
53
+ "name: #{name} value: #{value} namespace_uri: #{namespace_uri} prefix: #{prefix}"
54
+ end
51
55
  end
52
56
  end
53
57
  end
@@ -50,6 +50,10 @@ module Canon
50
50
  [attr.namespace_uri.to_s, attr.local_name]
51
51
  end
52
52
  end
53
+
54
+ def node_info
55
+ "name: #{name} namespace_uri: #{namespace_uri} prefix: #{prefix}"
56
+ end
53
57
  end
54
58
  end
55
59
  end
@@ -32,6 +32,10 @@ module Canon
32
32
  def xml_namespace?
33
33
  prefix == "xml" && uri == "http://www.w3.org/XML/1998/namespace"
34
34
  end
35
+
36
+ def node_info
37
+ "prefix: #{prefix} uri: #{uri}"
38
+ end
35
39
  end
36
40
  end
37
41
  end
@@ -18,6 +18,10 @@ module Canon
18
18
  def node_type
19
19
  :processing_instruction
20
20
  end
21
+
22
+ def node_info
23
+ "data: #{data} target: #{target}"
24
+ end
21
25
  end
22
26
  end
23
27
  end
data/lib/canon.rb CHANGED
@@ -13,7 +13,7 @@ require_relative "canon/formatters/html4_formatter"
13
13
  require_relative "canon/formatters/html5_formatter"
14
14
  require_relative "canon/comparison"
15
15
 
16
- require_relative "canon/rspec_matchers" if defined?(::RSpec)
16
+ require_relative "canon/rspec_matchers" if defined?(RSpec)
17
17
 
18
18
  module Canon
19
19
  SUPPORTED_FORMATS = %i[xml yaml json html html4 html5 string].freeze
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.1.14
4
+ version: 0.1.15
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-02-25 00:00:00.000000000 Z
11
+ date: 2026-03-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: diff-lcs
@@ -351,6 +351,7 @@ metadata:
351
351
  homepage_uri: https://github.com/lutaml/canon
352
352
  source_code_uri: https://github.com/lutaml/canon
353
353
  changelog_uri: https://github.com/lutaml/canon
354
+ rubygems_mfa_required: 'true'
354
355
  post_install_message:
355
356
  rdoc_options: []
356
357
  require_paths: