canon 0.2.9 → 0.2.12

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 (152) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +21 -22
  3. data/Rakefile +25 -2
  4. data/lib/canon/cache.rb +18 -27
  5. data/lib/canon/cli.rb +0 -3
  6. data/lib/canon/commands/diff_command.rb +0 -6
  7. data/lib/canon/commands/format_command.rb +0 -4
  8. data/lib/canon/commands.rb +9 -0
  9. data/lib/canon/comparison/child_realignment.rb +0 -2
  10. data/lib/canon/comparison/compare_profile.rb +30 -36
  11. data/lib/canon/comparison/comparison_result.rb +0 -2
  12. data/lib/canon/comparison/diff_node_builder.rb +353 -0
  13. data/lib/canon/comparison/dimensions/dimension.rb +51 -0
  14. data/lib/canon/comparison/dimensions/dimension_set.rb +49 -0
  15. data/lib/canon/comparison/dimensions/registry.rb +101 -60
  16. data/lib/canon/comparison/dimensions.rb +15 -46
  17. data/lib/canon/comparison/html_comparator.rb +20 -141
  18. data/lib/canon/comparison/html_compare_profile.rb +15 -18
  19. data/lib/canon/comparison/json_comparator.rb +4 -165
  20. data/lib/canon/comparison/json_parser.rb +0 -2
  21. data/lib/canon/comparison/markup_comparator.rb +14 -210
  22. data/lib/canon/comparison/match_options/base_resolver.rb +18 -29
  23. data/lib/canon/comparison/match_options/json_resolver.rb +4 -28
  24. data/lib/canon/comparison/match_options/xml_resolver.rb +4 -45
  25. data/lib/canon/comparison/match_options/yaml_resolver.rb +4 -30
  26. data/lib/canon/comparison/match_options.rb +13 -88
  27. data/lib/canon/comparison/node_inspector.rb +13 -48
  28. data/lib/canon/comparison/pipeline.rb +269 -0
  29. data/lib/canon/comparison/profile_definition.rb +0 -2
  30. data/lib/canon/comparison/ruby_object_comparator.rb +1 -1
  31. data/lib/canon/comparison/strategies/match_strategy_factory.rb +9 -58
  32. data/lib/canon/comparison/strategies/semantic_tree_match_strategy.rb +4 -11
  33. data/lib/canon/comparison/strategies.rb +16 -0
  34. data/lib/canon/comparison/xml_comparator/attribute_comparator.rb +19 -5
  35. data/lib/canon/comparison/xml_comparator/attribute_filter.rb +0 -3
  36. data/lib/canon/comparison/xml_comparator/child_comparison.rb +0 -6
  37. data/lib/canon/comparison/xml_comparator/namespace_comparator.rb +1 -6
  38. data/lib/canon/comparison/xml_comparator/node_parser.rb +2 -6
  39. data/lib/canon/comparison/xml_comparator.rb +4 -492
  40. data/lib/canon/comparison/xml_comparator_helpers.rb +21 -0
  41. data/lib/canon/comparison/xml_node_comparison.rb +4 -119
  42. data/lib/canon/comparison/yaml_comparator.rb +0 -3
  43. data/lib/canon/comparison.rb +144 -267
  44. data/lib/canon/config/config_dsl.rb +159 -0
  45. data/lib/canon/config/env_provider.rb +0 -3
  46. data/lib/canon/config/env_schema.rb +48 -58
  47. data/lib/canon/config/profile_loader.rb +0 -1
  48. data/lib/canon/config.rb +116 -468
  49. data/lib/canon/diff/diff_block_builder.rb +0 -2
  50. data/lib/canon/diff/diff_classifier.rb +0 -5
  51. data/lib/canon/diff/diff_context.rb +0 -2
  52. data/lib/canon/diff/diff_context_builder.rb +0 -2
  53. data/lib/canon/diff/diff_line_builder.rb +2 -3
  54. data/lib/canon/diff/diff_node_enricher.rb +0 -4
  55. data/lib/canon/diff/diff_node_mapper.rb +10 -12
  56. data/lib/canon/diff/diff_report_builder.rb +0 -4
  57. data/lib/canon/diff/formatting_detector.rb +3 -3
  58. data/lib/canon/diff/node_serializer.rb +0 -7
  59. data/lib/canon/diff/xml_serialization_formatter.rb +0 -3
  60. data/lib/canon/diff.rb +39 -0
  61. data/lib/canon/diff_formatter/by_line/base_formatter.rb +4 -17
  62. data/lib/canon/diff_formatter/by_line/html_formatter.rb +7 -19
  63. data/lib/canon/diff_formatter/by_line/json_formatter.rb +0 -3
  64. data/lib/canon/diff_formatter/by_line/simple_formatter.rb +0 -3
  65. data/lib/canon/diff_formatter/by_line/xml_formatter.rb +7 -26
  66. data/lib/canon/diff_formatter/by_line/yaml_formatter.rb +0 -3
  67. data/lib/canon/diff_formatter/by_object/base_formatter.rb +20 -17
  68. data/lib/canon/diff_formatter/by_object/json_formatter.rb +0 -2
  69. data/lib/canon/diff_formatter/by_object/xml_formatter.rb +119 -3
  70. data/lib/canon/diff_formatter/by_object/yaml_formatter.rb +0 -2
  71. data/lib/canon/diff_formatter/by_object_formatter.rb +1 -5
  72. data/lib/canon/diff_formatter/debug_output.rb +0 -2
  73. data/lib/canon/diff_formatter/diff_detail_formatter/dimension_formatter.rb +27 -61
  74. data/lib/canon/diff_formatter/diff_detail_formatter/location_extractor.rb +26 -29
  75. data/lib/canon/diff_formatter/diff_detail_formatter/node_utils.rb +1 -2
  76. data/lib/canon/diff_formatter/diff_detail_formatter/text_utils.rb +1 -7
  77. data/lib/canon/diff_formatter/diff_detail_formatter.rb +0 -7
  78. data/lib/canon/diff_formatter/diff_detail_formatter_helpers.rb +23 -0
  79. data/lib/canon/diff_formatter.rb +26 -20
  80. data/lib/canon/formatters/html4_formatter.rb +0 -2
  81. data/lib/canon/formatters/html5_formatter.rb +0 -2
  82. data/lib/canon/formatters/html_formatter.rb +0 -3
  83. data/lib/canon/formatters/json_formatter.rb +0 -1
  84. data/lib/canon/formatters/xml_formatter.rb +0 -4
  85. data/lib/canon/formatters/yaml_formatter.rb +0 -1
  86. data/lib/canon/formatters.rb +16 -0
  87. data/lib/canon/html/data_model.rb +1 -11
  88. data/lib/canon/html.rb +4 -3
  89. data/lib/canon/options/cli_generator.rb +0 -2
  90. data/lib/canon/options/registry.rb +0 -2
  91. data/lib/canon/options.rb +9 -0
  92. data/lib/canon/pretty_printer/html.rb +0 -1
  93. data/lib/canon/pretty_printer/xml_normalized.rb +0 -2
  94. data/lib/canon/pretty_printer.rb +12 -0
  95. data/lib/canon/tree_diff/adapters/html_adapter.rb +1 -1
  96. data/lib/canon/tree_diff/adapters.rb +14 -0
  97. data/lib/canon/tree_diff/core/attribute_comparator.rb +0 -6
  98. data/lib/canon/tree_diff/core/node_signature.rb +1 -1
  99. data/lib/canon/tree_diff/core/tree_node.rb +12 -5
  100. data/lib/canon/tree_diff/core.rb +17 -0
  101. data/lib/canon/tree_diff/matchers/hash_matcher.rb +0 -7
  102. data/lib/canon/tree_diff/matchers/similarity_matcher.rb +1 -5
  103. data/lib/canon/tree_diff/matchers/structural_propagator.rb +1 -5
  104. data/lib/canon/tree_diff/matchers.rb +15 -0
  105. data/lib/canon/tree_diff/operation_converter.rb +7 -15
  106. data/lib/canon/tree_diff/operation_converter_helpers/metadata_enricher.rb +2 -12
  107. data/lib/canon/tree_diff/operation_converter_helpers/post_processor.rb +13 -7
  108. data/lib/canon/tree_diff/operation_converter_helpers/reason_builder.rb +2 -2
  109. data/lib/canon/tree_diff/operation_converter_helpers/update_change_handler.rb +4 -6
  110. data/lib/canon/tree_diff/operation_converter_helpers.rb +18 -0
  111. data/lib/canon/tree_diff/operations/operation_detector.rb +6 -5
  112. data/lib/canon/tree_diff/operations.rb +13 -0
  113. data/lib/canon/tree_diff.rb +26 -27
  114. data/lib/canon/validators/base_validator.rb +5 -10
  115. data/lib/canon/validators/html_validator.rb +2 -8
  116. data/lib/canon/validators/json_validator.rb +0 -1
  117. data/lib/canon/validators/xml_validator.rb +2 -8
  118. data/lib/canon/validators/yaml_validator.rb +0 -1
  119. data/lib/canon/validators.rb +12 -0
  120. data/lib/canon/version.rb +1 -1
  121. data/lib/canon/xml/c14n.rb +0 -4
  122. data/lib/canon/xml/data_model.rb +5 -15
  123. data/lib/canon/xml/line_range_mapper.rb +0 -2
  124. data/lib/canon/xml/nodes/attribute_node.rb +0 -2
  125. data/lib/canon/xml/nodes/comment_node.rb +0 -2
  126. data/lib/canon/xml/nodes/element_node.rb +0 -2
  127. data/lib/canon/xml/nodes/namespace_node.rb +0 -2
  128. data/lib/canon/xml/nodes/processing_instruction_node.rb +0 -2
  129. data/lib/canon/xml/nodes/root_node.rb +0 -2
  130. data/lib/canon/xml/nodes/text_node.rb +0 -2
  131. data/lib/canon/xml/nodes.rb +19 -0
  132. data/lib/canon/xml/processor.rb +0 -5
  133. data/lib/canon/xml/sax_builder.rb +1 -8
  134. data/lib/canon/xml/whitespace_normalizer.rb +2 -2
  135. data/lib/canon/xml.rb +33 -0
  136. data/lib/canon/xml_backend.rb +50 -14
  137. data/lib/canon/xml_parsing.rb +32 -18
  138. data/lib/canon.rb +25 -15
  139. data/lib/tasks/performance.rake +0 -58
  140. data/lib/tasks/performance_comparator.rb +132 -65
  141. data/lib/tasks/performance_helpers.rb +4 -249
  142. data/lib/tasks/performance_report.rb +309 -0
  143. metadata +28 -15
  144. data/lib/canon/comparison/dimensions/attribute_order_dimension.rb +0 -64
  145. data/lib/canon/comparison/dimensions/attribute_presence_dimension.rb +0 -64
  146. data/lib/canon/comparison/dimensions/attribute_values_dimension.rb +0 -167
  147. data/lib/canon/comparison/dimensions/base_dimension.rb +0 -107
  148. data/lib/canon/comparison/dimensions/comments_dimension.rb +0 -117
  149. data/lib/canon/comparison/dimensions/element_position_dimension.rb +0 -86
  150. data/lib/canon/comparison/dimensions/structural_whitespace_dimension.rb +0 -115
  151. data/lib/canon/comparison/dimensions/text_content_dimension.rb +0 -102
  152. data/lib/canon/comparison/xml_comparator/diff_node_builder.rb +0 -270
@@ -1,72 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "base_match_strategy"
4
-
5
3
  module Canon
6
4
  module Comparison
7
5
  module Strategies
8
6
  # Factory for creating match strategies
9
7
  #
10
- # Selects the appropriate match strategy based on match options.
11
- # This provides a single point for strategy instantiation and enables
12
- # easy extension with new matching algorithms.
13
- #
14
- # @example Create a strategy
15
- # strategy = MatchStrategyFactory.create(
16
- # format: :xml,
17
- # match_options: { semantic_diff: true }
18
- # )
19
- # differences = strategy.match(doc1, doc2)
20
- #
8
+ # After semantic dispatch normalization, this factory is only called
9
+ # with semantic_diff: true. DOM matching is handled directly by
10
+ # the format comparators (XmlComparator, HtmlComparator, etc.).
21
11
  class MatchStrategyFactory
22
- # Create appropriate match strategy
23
- #
24
- # Examines match options to determine which strategy to use:
25
- # - If semantic_diff is enabled: SemanticTreeMatchStrategy
26
- # - Otherwise (default): DomMatchStrategy
27
- #
28
- # Future strategies can be added here by checking additional
29
- # options and returning the appropriate strategy class.
30
- #
31
- # @param format [Symbol] Document format (:xml, :html, :json, :yaml)
32
- # @param match_options [Hash] Match options
33
- # @option match_options [Boolean] :semantic_diff Use semantic tree matching
34
- # @return [BaseMatchStrategy] Instantiated strategy
35
- #
36
- # @example DOM matching (default)
37
- # strategy = MatchStrategyFactory.create(
38
- # format: :xml,
39
- # match_options: {}
40
- # )
41
- # # Returns DomMatchStrategy
42
- #
43
- # @example Semantic tree matching
44
- # strategy = MatchStrategyFactory.create(
45
- # format: :xml,
46
- # match_options: { semantic_diff: true }
47
- # )
48
- # # Returns SemanticTreeMatchStrategy
49
- #
50
12
  def self.create(format:, match_options:)
51
- # Check for semantic diff option
52
- if match_options[:semantic_diff]
53
- require_relative "semantic_tree_match_strategy"
54
- SemanticTreeMatchStrategy.new(format: format,
55
- match_options: match_options)
56
- else
57
- # Default to DOM matching
58
- require_relative "dom_match_strategy"
59
- DomMatchStrategy.new(format: format, match_options: match_options)
13
+ unless match_options[:semantic_diff]
14
+ raise ArgumentError,
15
+ "MatchStrategyFactory requires semantic_diff: true; " \
16
+ "DOM matching is handled by format comparators directly"
60
17
  end
61
18
 
62
- # Future: Add more strategies here
63
- # Example:
64
- # elsif match_options[:hybrid_diff]
65
- # require_relative "hybrid_match_strategy"
66
- # HybridMatchStrategy.new(format, match_options)
67
- # elsif match_options[:fuzzy_diff]
68
- # require_relative "fuzzy_match_strategy"
69
- # FuzzyMatchStrategy.new(format, match_options)
19
+ SemanticTreeMatchStrategy.new(format: format,
20
+ match_options: match_options)
70
21
  end
71
22
  end
72
23
  end
@@ -1,10 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "base_match_strategy"
4
- require_relative "../../tree_diff/tree_diff_integrator"
5
- require_relative "../../tree_diff/operation_converter"
6
- require_relative "../xml_node_comparison"
7
-
8
3
  module Canon
9
4
  module Comparison
10
5
  module Strategies
@@ -126,7 +121,7 @@ module Canon
126
121
  # @return [Array<String>] Preprocessed strings
127
122
  def preprocess_xml(doc1, doc2)
128
123
  xml1 = if doc1.is_a?(Canon::Xml::Node)
129
- XmlNodeComparison.serialize_node_to_xml(doc1)
124
+ Canon::Diff::NodeSerializer.serialize(doc1)
130
125
  elsif Canon::XmlParsing.xml_node?(doc1)
131
126
  Canon::XmlParsing.serialize(doc1)
132
127
  else
@@ -134,7 +129,7 @@ module Canon
134
129
  end
135
130
 
136
131
  xml2 = if doc2.is_a?(Canon::Xml::Node)
137
- XmlNodeComparison.serialize_node_to_xml(doc2)
132
+ Canon::Diff::NodeSerializer.serialize(doc2)
138
133
  elsif Canon::XmlParsing.xml_node?(doc2)
139
134
  Canon::XmlParsing.serialize(doc2)
140
135
  else
@@ -162,7 +157,7 @@ module Canon
162
157
  # For XML::DocumentFragment (from parse_node_as_fragment), use to_s
163
158
  # to avoid Nokogiri auto-inserting meta tags during to_html serialization
164
159
  html1 = if doc1.is_a?(Canon::Xml::Node)
165
- XmlNodeComparison.serialize_node_to_xml(doc1)
160
+ Canon::Diff::NodeSerializer.serialize(doc1)
166
161
  elsif doc1.is_a?(Nokogiri::XML::DocumentFragment)
167
162
  doc1.to_s
168
163
  elsif Canon::XmlParsing.xml_node?(doc1)
@@ -172,7 +167,7 @@ module Canon
172
167
  end
173
168
 
174
169
  html2 = if doc2.is_a?(Canon::Xml::Node)
175
- XmlNodeComparison.serialize_node_to_xml(doc2)
170
+ Canon::Diff::NodeSerializer.serialize(doc2)
176
171
  elsif doc2.is_a?(Nokogiri::XML::DocumentFragment)
177
172
  doc2.to_s
178
173
  elsif Canon::XmlParsing.xml_node?(doc2)
@@ -194,7 +189,6 @@ module Canon
194
189
  # @param doc2 [Object] Second JSON document
195
190
  # @return [Array<String>] Preprocessed strings
196
191
  def preprocess_json(doc1, doc2)
197
- require_relative "../../formatters/json_formatter"
198
192
  [Canon.format(doc1, :json), Canon.format(doc2, :json)]
199
193
  end
200
194
 
@@ -206,7 +200,6 @@ module Canon
206
200
  # @param doc2 [Object] Second YAML document
207
201
  # @return [Array<String>] Preprocessed strings
208
202
  def preprocess_yaml(doc1, doc2)
209
- require_relative "../../formatters/yaml_formatter"
210
203
  [Canon.format(doc1, :yaml), Canon.format(doc2, :yaml)]
211
204
  end
212
205
  end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Canon
4
+ module Comparison
5
+ # Match strategy framework. Children are autoloaded — never
6
+ # `require_relative` them.
7
+ module Strategies
8
+ autoload :BaseMatchStrategy,
9
+ "canon/comparison/strategies/base_match_strategy"
10
+ autoload :MatchStrategyFactory,
11
+ "canon/comparison/strategies/match_strategy_factory"
12
+ autoload :SemanticTreeMatchStrategy,
13
+ "canon/comparison/strategies/semantic_tree_match_strategy"
14
+ end
15
+ end
16
+ end
@@ -15,8 +15,8 @@ module Canon
15
15
  # @return [Symbol] Comparison result
16
16
  def self.compare(node1, node2, opts, differences)
17
17
  # Get attributes using the appropriate method for each node type
18
- raw_attrs1 = node1.respond_to?(:attribute_nodes) ? node1.attribute_nodes : node1.attributes
19
- raw_attrs2 = node2.respond_to?(:attribute_nodes) ? node2.attribute_nodes : node2.attributes
18
+ raw_attrs1 = get_raw_attributes(node1)
19
+ raw_attrs2 = get_raw_attributes(node2)
20
20
 
21
21
  attrs1 = XmlComparatorHelpers::AttributeFilter.filter(raw_attrs1,
22
22
  opts)
@@ -158,9 +158,6 @@ differences)
158
158
  # @param differences [Array] Array to append difference to
159
159
  def self.add_attribute_difference(n1:, n2:, diff1:, diff2:,
160
160
  dimension:, differences:, **opts)
161
- # Import DiffNodeBuilder to avoid circular dependency
162
- require_relative "diff_node_builder"
163
-
164
161
  diff_node = Canon::Comparison::DiffNodeBuilder.build(
165
162
  node1: n1,
166
163
  node2: n2,
@@ -171,6 +168,23 @@ dimension:, differences:, **opts)
171
168
  )
172
169
  differences << diff_node if diff_node
173
170
  end
171
+
172
+ # Get raw attributes from a node, dispatching by type.
173
+ # Nokogiri::XML::Element has attribute_nodes (NodeSet),
174
+ # Canon::Xml::Nodes::ElementNode has attribute_nodes (Array),
175
+ # Moxml::Element has attributes (Hash-like).
176
+ def self.get_raw_attributes(node)
177
+ case node
178
+ when Canon::Xml::Nodes::ElementNode
179
+ node.attribute_nodes
180
+ else
181
+ if Canon::XmlBackend.nokogiri? && node.is_a?(Nokogiri::XML::Element)
182
+ node.attribute_nodes
183
+ else
184
+ node.attributes
185
+ end
186
+ end
187
+ end
174
188
  end
175
189
  end
176
190
  end
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "../match_options"
4
- require_relative "../../xml/namespace_helper"
5
-
6
3
  module Canon
7
4
  module Comparison
8
5
  module XmlComparatorHelpers
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "../node_inspector"
4
-
5
3
  module Canon
6
4
  module Comparison
7
5
  module XmlComparatorHelpers
@@ -38,7 +36,6 @@ module Canon
38
36
  # pretty_printed_received → drop \n-starting whitespace nodes from node2
39
37
  # The ephemeral _pretty_print_side_active flag is consumed by node_excluded?
40
38
  # and must NOT be forwarded into recursive compare_nodes calls.
41
- require_relative "../xml_node_comparison"
42
39
  opts1 = XmlNodeComparison.opts_for_side(opts, :expected)
43
40
  opts2 = XmlNodeComparison.opts_for_side(opts, :received)
44
41
 
@@ -78,9 +75,6 @@ module Canon
78
75
  # Use ElementMatcher for semantic comparison
79
76
  def use_element_matcher_comparison(children1, children2, parent_node, comparator,
80
77
  opts, child_opts, diff_children, differences)
81
- require_relative "../../xml/element_matcher"
82
- require_relative "../../xml/nodes/root_node"
83
-
84
78
  # Create temporary RootNode wrappers
85
79
  temp_root1 = Canon::Xml::Nodes::RootNode.new
86
80
  temp_root1.children = children1.dup
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "../../xml/namespace_helper"
4
-
5
3
  module Canon
6
4
  module Comparison
7
5
  module XmlComparatorHelpers
@@ -153,10 +151,7 @@ changed, opts, differences)
153
151
  end.join(', ')}"
154
152
  end
155
153
 
156
- # Import DiffNodeBuilder to avoid circular dependency
157
- require_relative "diff_node_builder"
158
-
159
- diff_node = DiffNodeBuilder.build(
154
+ diff_node = Canon::Comparison::DiffNodeBuilder.build(
160
155
  node1: node1,
161
156
  node2: node2,
162
157
  diff1: Comparison::UNEQUAL_ATTRIBUTES,
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "../../xml/c14n"
4
-
5
3
  module Canon
6
4
  module Comparison
7
5
  module XmlComparatorHelpers
@@ -37,8 +35,7 @@ module Canon
37
35
  # Select parser backend
38
36
  resolved_parser = parser || resolve_parser_config
39
37
 
40
- if resolved_parser == :sax
41
- require_relative "../../xml/sax_builder"
38
+ if resolved_parser == :sax && RUBY_ENGINE != "opal"
42
39
  Canon::Xml::SaxBuilder.parse(xml_string,
43
40
  preserve_whitespace: preserve_whitespace)
44
41
  else
@@ -97,8 +94,7 @@ parser: nil)
97
94
 
98
95
  resolved_parser = parser || resolve_parser_config
99
96
 
100
- if resolved_parser == :sax
101
- require_relative "../../xml/sax_builder"
97
+ if resolved_parser == :sax && RUBY_ENGINE != "opal"
102
98
  Canon::Xml::SaxBuilder.parse(xml_str,
103
99
  preserve_whitespace: preserve_whitespace)
104
100
  else