canon 0.2.11 → 0.3.0
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/.rubocop_todo.yml +62 -25
- data/CLAUDE.md +197 -0
- data/Rakefile +5 -2
- data/lib/canon/cache.rb +3 -1
- data/lib/canon/cli.rb +0 -3
- data/lib/canon/commands/diff_command.rb +1 -8
- data/lib/canon/commands/format_command.rb +0 -4
- data/lib/canon/commands.rb +9 -0
- data/lib/canon/comparison/child_realignment.rb +0 -2
- data/lib/canon/comparison/compare_profile.rb +30 -36
- data/lib/canon/comparison/comparison_result.rb +1 -4
- data/lib/canon/comparison/diff_node_builder.rb +353 -0
- data/lib/canon/comparison/dimensions/dimension.rb +51 -0
- data/lib/canon/comparison/dimensions/dimension_set.rb +49 -0
- data/lib/canon/comparison/dimensions/registry.rb +101 -60
- data/lib/canon/comparison/dimensions.rb +15 -46
- data/lib/canon/comparison/format_detector.rb +27 -29
- data/lib/canon/comparison/html_comparator.rb +20 -143
- data/lib/canon/comparison/html_compare_profile.rb +15 -18
- data/lib/canon/comparison/json_comparator.rb +4 -165
- data/lib/canon/comparison/json_parser.rb +0 -2
- data/lib/canon/comparison/markup_comparator.rb +14 -210
- data/lib/canon/comparison/match_options/base_resolver.rb +18 -29
- data/lib/canon/comparison/match_options/json_resolver.rb +4 -28
- data/lib/canon/comparison/match_options/xml_resolver.rb +4 -45
- data/lib/canon/comparison/match_options/yaml_resolver.rb +4 -30
- data/lib/canon/comparison/match_options.rb +13 -88
- data/lib/canon/comparison/node_inspector.rb +5 -9
- data/lib/canon/comparison/pipeline.rb +269 -0
- data/lib/canon/comparison/profile_definition.rb +0 -2
- data/lib/canon/comparison/ruby_object_comparator.rb +1 -1
- data/lib/canon/comparison/strategies/match_strategy_factory.rb +9 -58
- data/lib/canon/comparison/strategies/semantic_tree_match_strategy.rb +4 -11
- data/lib/canon/comparison/strategies.rb +16 -0
- data/lib/canon/comparison/xml_comparator/attribute_comparator.rb +1 -4
- data/lib/canon/comparison/xml_comparator/attribute_filter.rb +0 -3
- data/lib/canon/comparison/xml_comparator/child_comparison.rb +0 -6
- data/lib/canon/comparison/xml_comparator/namespace_comparator.rb +1 -6
- data/lib/canon/comparison/xml_comparator/node_parser.rb +3 -7
- data/lib/canon/comparison/xml_comparator.rb +4 -492
- data/lib/canon/comparison/xml_comparator_helpers.rb +21 -0
- data/lib/canon/comparison/xml_node_comparison.rb +6 -121
- data/lib/canon/comparison/yaml_comparator.rb +0 -3
- data/lib/canon/comparison.rb +144 -267
- data/lib/canon/config/config_dsl.rb +159 -0
- data/lib/canon/config/env_provider.rb +0 -3
- data/lib/canon/config/env_schema.rb +48 -58
- data/lib/canon/config/profile_loader.rb +0 -1
- data/lib/canon/config.rb +116 -468
- data/lib/canon/diff/diff_block_builder.rb +0 -2
- data/lib/canon/diff/diff_classifier.rb +0 -5
- data/lib/canon/diff/diff_context.rb +0 -2
- data/lib/canon/diff/diff_context_builder.rb +0 -2
- data/lib/canon/diff/diff_line_builder.rb +0 -3
- data/lib/canon/diff/diff_node_enricher.rb +0 -4
- data/lib/canon/diff/diff_node_mapper.rb +0 -4
- data/lib/canon/diff/diff_report_builder.rb +0 -4
- data/lib/canon/diff/formatting_detector.rb +0 -1
- data/lib/canon/diff/node_serializer.rb +0 -7
- data/lib/canon/diff.rb +39 -0
- data/lib/canon/diff_formatter/by_line/base_formatter.rb +5 -19
- data/lib/canon/diff_formatter/by_line/html_formatter.rb +8 -21
- data/lib/canon/diff_formatter/by_line/json_formatter.rb +0 -3
- data/lib/canon/diff_formatter/by_line/simple_formatter.rb +0 -3
- data/lib/canon/diff_formatter/by_line/xml_formatter.rb +7 -26
- data/lib/canon/diff_formatter/by_line/yaml_formatter.rb +0 -3
- data/lib/canon/diff_formatter/by_line_formatter.rb +1 -2
- data/lib/canon/diff_formatter/by_object/base_formatter.rb +8 -15
- data/lib/canon/diff_formatter/by_object/json_formatter.rb +0 -2
- data/lib/canon/diff_formatter/by_object/xml_formatter.rb +0 -2
- data/lib/canon/diff_formatter/by_object/yaml_formatter.rb +0 -2
- data/lib/canon/diff_formatter/debug_output.rb +0 -2
- data/lib/canon/diff_formatter/diff_detail_formatter/dimension_formatter.rb +24 -58
- data/lib/canon/diff_formatter/diff_detail_formatter/location_extractor.rb +0 -2
- data/lib/canon/diff_formatter/diff_detail_formatter/node_utils.rb +3 -4
- data/lib/canon/diff_formatter/diff_detail_formatter/text_utils.rb +1 -7
- data/lib/canon/diff_formatter/diff_detail_formatter.rb +0 -7
- data/lib/canon/diff_formatter/diff_detail_formatter_helpers.rb +23 -0
- data/lib/canon/diff_formatter/theme.rb +2 -2
- data/lib/canon/diff_formatter.rb +13 -13
- data/lib/canon/formatters/html4_formatter.rb +0 -2
- data/lib/canon/formatters/html5_formatter.rb +0 -2
- data/lib/canon/formatters/html_formatter.rb +0 -3
- data/lib/canon/formatters/json_formatter.rb +0 -1
- data/lib/canon/formatters/xml_formatter.rb +0 -4
- data/lib/canon/formatters/yaml_formatter.rb +0 -1
- data/lib/canon/formatters.rb +16 -0
- data/lib/canon/html/data_model.rb +0 -10
- data/lib/canon/html/nokogiri_support.rb +60 -0
- data/lib/canon/html.rb +5 -3
- data/lib/canon/options/cli_generator.rb +0 -2
- data/lib/canon/options/registry.rb +0 -2
- data/lib/canon/options.rb +9 -0
- data/lib/canon/pretty_printer/html.rb +0 -1
- data/lib/canon/pretty_printer/xml.rb +6 -1
- data/lib/canon/pretty_printer/xml_normalized.rb +5 -7
- data/lib/canon/pretty_printer.rb +12 -0
- data/lib/canon/tree_diff/adapters/html_adapter.rb +1 -1
- data/lib/canon/tree_diff/adapters.rb +14 -0
- data/lib/canon/tree_diff/core/attribute_comparator.rb +0 -6
- data/lib/canon/tree_diff/core/node_signature.rb +1 -1
- data/lib/canon/tree_diff/core/tree_node.rb +13 -5
- data/lib/canon/tree_diff/core.rb +17 -0
- data/lib/canon/tree_diff/matchers/hash_matcher.rb +0 -7
- data/lib/canon/tree_diff/matchers/similarity_matcher.rb +1 -5
- data/lib/canon/tree_diff/matchers/structural_propagator.rb +1 -5
- data/lib/canon/tree_diff/matchers.rb +15 -0
- data/lib/canon/tree_diff/operation_converter.rb +0 -8
- data/lib/canon/tree_diff/operation_converter_helpers/metadata_enricher.rb +2 -12
- data/lib/canon/tree_diff/operation_converter_helpers/post_processor.rb +13 -7
- data/lib/canon/tree_diff/operation_converter_helpers/reason_builder.rb +2 -2
- data/lib/canon/tree_diff/operation_converter_helpers/update_change_handler.rb +4 -6
- data/lib/canon/tree_diff/operation_converter_helpers.rb +18 -0
- data/lib/canon/tree_diff/operations/operation_detector.rb +2 -5
- data/lib/canon/tree_diff/operations.rb +13 -0
- data/lib/canon/tree_diff.rb +26 -27
- data/lib/canon/validators/base_validator.rb +0 -2
- data/lib/canon/validators/html_validator.rb +0 -1
- data/lib/canon/validators/json_validator.rb +0 -1
- data/lib/canon/validators/xml_validator.rb +0 -1
- data/lib/canon/validators/yaml_validator.rb +0 -1
- data/lib/canon/validators.rb +12 -0
- data/lib/canon/version.rb +1 -1
- data/lib/canon/xml/c14n.rb +0 -4
- data/lib/canon/xml/data_model.rb +206 -43
- data/lib/canon/xml/line_range_mapper.rb +0 -2
- data/lib/canon/xml/nodes/attribute_node.rb +0 -2
- data/lib/canon/xml/nodes/comment_node.rb +0 -2
- data/lib/canon/xml/nodes/element_node.rb +0 -2
- data/lib/canon/xml/nodes/namespace_node.rb +0 -2
- data/lib/canon/xml/nodes/processing_instruction_node.rb +0 -2
- data/lib/canon/xml/nodes/root_node.rb +0 -2
- data/lib/canon/xml/nodes/text_node.rb +0 -2
- data/lib/canon/xml/nodes.rb +19 -0
- data/lib/canon/xml/processor.rb +10 -7
- data/lib/canon/xml/sax/moxml_driver.rb +58 -0
- data/lib/canon/xml/sax/nokogiri_driver.rb +54 -0
- data/lib/canon/xml/sax.rb +37 -0
- data/lib/canon/xml/sax_builder.rb +31 -27
- data/lib/canon/xml.rb +34 -0
- data/lib/canon/xml_backend.rb +40 -19
- data/lib/canon/xml_parsing.rb +89 -116
- data/lib/canon.rb +26 -17
- data/lib/tasks/benchmark_runner.rb +4 -0
- data/lib/tasks/performance.rake +2 -58
- data/lib/tasks/performance_comparator.rb +132 -65
- data/lib/tasks/performance_helpers.rb +4 -249
- data/lib/tasks/performance_report.rb +313 -0
- metadata +31 -13
- data/lib/canon/comparison/dimensions/attribute_order_dimension.rb +0 -64
- data/lib/canon/comparison/dimensions/attribute_presence_dimension.rb +0 -64
- data/lib/canon/comparison/dimensions/attribute_values_dimension.rb +0 -167
- data/lib/canon/comparison/dimensions/base_dimension.rb +0 -107
- data/lib/canon/comparison/dimensions/comments_dimension.rb +0 -117
- data/lib/canon/comparison/dimensions/element_position_dimension.rb +0 -86
- data/lib/canon/comparison/dimensions/structural_whitespace_dimension.rb +0 -115
- data/lib/canon/comparison/dimensions/text_content_dimension.rb +0 -102
- data/lib/canon/comparison/xml_comparator/diff_node_builder.rb +0 -300
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0d1f4de27225e068e0bc1a00b2b7b2d980847bf9027a21f042de148aae65926e
|
|
4
|
+
data.tar.gz: 4561eaa1efb9abcd541ec8da05f2e33df11859c35d6e5fe3f5fee9fa9d22af3d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ee29ec80689f0c53771017dca13d48ba4c3162db98f0e3605c3198148035cd564d818b8e4c08728608ef3abd055231d82850f79f933d2206ec4a36d488c6faa6
|
|
7
|
+
data.tar.gz: 37ae2bd7e928265b8c54e4025ae73d7e8761ce6e28ee2933e005129bd218d7003a5d547103bba140e11e829ef4d45aa7ef41768766b302f6608814f08fb74983
|
data/.rubocop_todo.yml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# This configuration was generated by
|
|
2
|
-
# `rubocop --auto-gen-config
|
|
3
|
-
# using RuboCop version 1.
|
|
2
|
+
# `rubocop --auto-gen-config`
|
|
3
|
+
# on 2026-08-27 03:37:16 UTC using RuboCop version 1.90.0.
|
|
4
4
|
# The point is for the user to remove these configuration records
|
|
5
5
|
# one by one as the offenses are removed from the code base.
|
|
6
6
|
# Note that changes in the inspected code, or installation of new
|
|
@@ -11,14 +11,44 @@ Gemspec/RequiredRubyVersion:
|
|
|
11
11
|
Exclude:
|
|
12
12
|
- 'canon.gemspec'
|
|
13
13
|
|
|
14
|
-
# Offense count:
|
|
14
|
+
# Offense count: 6
|
|
15
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
16
|
+
# Configuration parameters: AllowForAlignment, AllowBeforeTrailingComments, ForceEqualSignAlignment.
|
|
17
|
+
Layout/ExtraSpacing:
|
|
18
|
+
Exclude:
|
|
19
|
+
- 'lib/canon/diff_formatter/theme.rb'
|
|
20
|
+
- 'lib/tasks/performance_report.rb'
|
|
21
|
+
|
|
22
|
+
# Offense count: 4
|
|
23
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
24
|
+
# Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle.
|
|
25
|
+
# SupportedHashRocketStyles: key, separator, table
|
|
26
|
+
# SupportedColonStyles: key, separator, table
|
|
27
|
+
# SupportedLastArgumentHashStyles: always_inspect, always_ignore, ignore_implicit, ignore_explicit
|
|
28
|
+
Layout/HashAlignment:
|
|
29
|
+
Exclude:
|
|
30
|
+
- 'lib/tasks/performance_report.rb'
|
|
31
|
+
|
|
32
|
+
# Offense count: 1267
|
|
15
33
|
# This cop supports safe autocorrection (--autocorrect).
|
|
16
34
|
# Configuration parameters: Max, AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, AllowRBSInlineAnnotation, AllowCopDirectives, AllowedPatterns, SplitStrings.
|
|
17
35
|
# URISchemes: http, https
|
|
18
36
|
Layout/LineLength:
|
|
19
37
|
Enabled: false
|
|
20
38
|
|
|
21
|
-
# Offense count:
|
|
39
|
+
# Offense count: 4
|
|
40
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
41
|
+
# Configuration parameters: AllowInHeredoc.
|
|
42
|
+
Layout/TrailingWhitespace:
|
|
43
|
+
Exclude:
|
|
44
|
+
- 'lib/tasks/performance_report.rb'
|
|
45
|
+
|
|
46
|
+
# Offense count: 1
|
|
47
|
+
Lint/CopDirectiveSyntax:
|
|
48
|
+
Exclude:
|
|
49
|
+
- 'lib/canon/comparison/comparison_result.rb'
|
|
50
|
+
|
|
51
|
+
# Offense count: 57
|
|
22
52
|
# Configuration parameters: IgnoreLiteralBranches, IgnoreConstantBranches, IgnoreDuplicateElseBranch.
|
|
23
53
|
Lint/DuplicateBranch:
|
|
24
54
|
Enabled: false
|
|
@@ -32,7 +62,7 @@ Lint/EmptyConditionalBody:
|
|
|
32
62
|
- 'spec/canon/comparison_spec.rb'
|
|
33
63
|
|
|
34
64
|
# Offense count: 5
|
|
35
|
-
# Configuration parameters:
|
|
65
|
+
# Configuration parameters: MaxRangeSize.
|
|
36
66
|
Lint/MissingCopEnableDirective:
|
|
37
67
|
Exclude:
|
|
38
68
|
- 'lib/canon/commands/format_command.rb'
|
|
@@ -62,12 +92,12 @@ Lint/UselessConstantScoping:
|
|
|
62
92
|
Exclude:
|
|
63
93
|
- 'lib/canon/diff_formatter/theme.rb'
|
|
64
94
|
|
|
65
|
-
# Offense count:
|
|
95
|
+
# Offense count: 296
|
|
66
96
|
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max.
|
|
67
97
|
Metrics/AbcSize:
|
|
68
98
|
Enabled: false
|
|
69
99
|
|
|
70
|
-
# Offense count:
|
|
100
|
+
# Offense count: 28
|
|
71
101
|
# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns, inherit_mode.
|
|
72
102
|
# AllowedMethods: refine
|
|
73
103
|
Metrics/BlockLength:
|
|
@@ -78,12 +108,12 @@ Metrics/BlockLength:
|
|
|
78
108
|
Metrics/BlockNesting:
|
|
79
109
|
Max: 4
|
|
80
110
|
|
|
81
|
-
# Offense count:
|
|
111
|
+
# Offense count: 262
|
|
82
112
|
# Configuration parameters: AllowedMethods, AllowedPatterns, Max.
|
|
83
113
|
Metrics/CyclomaticComplexity:
|
|
84
114
|
Enabled: false
|
|
85
115
|
|
|
86
|
-
# Offense count:
|
|
116
|
+
# Offense count: 501
|
|
87
117
|
# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
|
|
88
118
|
Metrics/MethodLength:
|
|
89
119
|
Max: 146
|
|
@@ -93,12 +123,12 @@ Metrics/MethodLength:
|
|
|
93
123
|
Metrics/ParameterLists:
|
|
94
124
|
Max: 10
|
|
95
125
|
|
|
96
|
-
# Offense count:
|
|
126
|
+
# Offense count: 207
|
|
97
127
|
# Configuration parameters: AllowedMethods, AllowedPatterns, Max.
|
|
98
128
|
Metrics/PerceivedComplexity:
|
|
99
129
|
Enabled: false
|
|
100
130
|
|
|
101
|
-
# Offense count:
|
|
131
|
+
# Offense count: 28
|
|
102
132
|
# Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames.
|
|
103
133
|
# AllowedNames: as, at, by, cc, db, id, if, in, io, ip, of, on, os, pp, to
|
|
104
134
|
Naming/MethodParameterName:
|
|
@@ -115,16 +145,6 @@ Naming/PredicateMethod:
|
|
|
115
145
|
Exclude:
|
|
116
146
|
- 'spec/canon/comparison/xml_attribute_diff_spec.rb'
|
|
117
147
|
|
|
118
|
-
# Offense count: 6
|
|
119
|
-
# Configuration parameters: EnforcedStyle, CheckMethodNames, CheckSymbols, AllowedIdentifiers, AllowedPatterns.
|
|
120
|
-
# SupportedStyles: snake_case, normalcase, non_integer
|
|
121
|
-
# AllowedIdentifiers: TLS1_1, TLS1_2, capture3, iso8601, rfc1123_date, rfc822, rfc2822, rfc3339, x86_64
|
|
122
|
-
Naming/VariableNumber:
|
|
123
|
-
Exclude:
|
|
124
|
-
- 'lib/canon/comparison/json_comparator.rb'
|
|
125
|
-
- 'lib/canon/comparison/markup_comparator.rb'
|
|
126
|
-
- 'lib/canon/comparison/xml_comparator/diff_node_builder.rb'
|
|
127
|
-
|
|
128
148
|
# Offense count: 4
|
|
129
149
|
# Configuration parameters: MinSize.
|
|
130
150
|
Performance/CollectionLiteralInLoop:
|
|
@@ -134,7 +154,7 @@ Performance/CollectionLiteralInLoop:
|
|
|
134
154
|
- 'lib/canon/xml/xml_base_handler.rb'
|
|
135
155
|
- 'spec/canon/diff/diff_node_mapper_comments_spec.rb'
|
|
136
156
|
|
|
137
|
-
# Offense count:
|
|
157
|
+
# Offense count: 111
|
|
138
158
|
# Configuration parameters: Prefixes, AllowedPatterns.
|
|
139
159
|
# Prefixes: when, with, without
|
|
140
160
|
RSpec/ContextWording:
|
|
@@ -151,7 +171,7 @@ RSpec/DescribeMethod:
|
|
|
151
171
|
- 'spec/canon/comparison/multiple_differences_spec.rb'
|
|
152
172
|
- 'spec/canon/diff_formatter/character_map_customization_spec.rb'
|
|
153
173
|
|
|
154
|
-
# Offense count:
|
|
174
|
+
# Offense count: 916
|
|
155
175
|
# Configuration parameters: CountAsOne.
|
|
156
176
|
RSpec/ExampleLength:
|
|
157
177
|
Max: 44
|
|
@@ -203,11 +223,11 @@ RSpec/MultipleDescribes:
|
|
|
203
223
|
Exclude:
|
|
204
224
|
- 'spec/canon/comparison/match_options_spec.rb'
|
|
205
225
|
|
|
206
|
-
# Offense count:
|
|
226
|
+
# Offense count: 783
|
|
207
227
|
RSpec/MultipleExpectations:
|
|
208
228
|
Max: 15
|
|
209
229
|
|
|
210
|
-
# Offense count:
|
|
230
|
+
# Offense count: 92
|
|
211
231
|
# Configuration parameters: AllowSubject.
|
|
212
232
|
RSpec/MultipleMemoizedHelpers:
|
|
213
233
|
Max: 16
|
|
@@ -267,6 +287,23 @@ RSpec/VerifiedDoubles:
|
|
|
267
287
|
- 'spec/canon/diff_formatter/diff_detail_formatter_spec.rb'
|
|
268
288
|
- 'spec/canon/tree_diff/operation_converter_spec.rb'
|
|
269
289
|
|
|
290
|
+
# Offense count: 13
|
|
291
|
+
# This cop supports unsafe autocorrection (--autocorrect-all).
|
|
292
|
+
Style/DirectiveScope:
|
|
293
|
+
Exclude:
|
|
294
|
+
- 'lib/canon.rb'
|
|
295
|
+
- 'lib/canon/commands/diff_command.rb'
|
|
296
|
+
- 'lib/canon/comparison/comparison_result.rb'
|
|
297
|
+
- 'lib/canon/diff_formatter.rb'
|
|
298
|
+
- 'lib/canon/diff_formatter/by_line/base_formatter.rb'
|
|
299
|
+
- 'lib/canon/diff_formatter/by_line/html_formatter.rb'
|
|
300
|
+
- 'lib/canon/diff_formatter/by_line_formatter.rb'
|
|
301
|
+
- 'spec/canon/comparison/xml_comparator_spec.rb'
|
|
302
|
+
- 'spec/canon/comparison_spec.rb'
|
|
303
|
+
- 'spec/canon/diff_formatter/formatting_diff_visualization_spec.rb'
|
|
304
|
+
- 'spec/canon/fixtures_integrity_spec.rb'
|
|
305
|
+
- 'spec/canon/string_matcher_spec.rb'
|
|
306
|
+
|
|
270
307
|
# Offense count: 1
|
|
271
308
|
# This cop supports safe autocorrection (--autocorrect).
|
|
272
309
|
# Configuration parameters: EnforcedStyle, AllowComments.
|
data/CLAUDE.md
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Canon is a canonicalization, formatting, and comparison library for serialization formats (XML, HTML, JSON, YAML). It produces standardized forms suitable for comparison, testing, digital signatures, and human-readable output.
|
|
8
|
+
|
|
9
|
+
## Common Commands
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Install dependencies
|
|
13
|
+
bundle install
|
|
14
|
+
|
|
15
|
+
# Run all tests
|
|
16
|
+
bundle exec rake spec # or: bundle exec rspec
|
|
17
|
+
|
|
18
|
+
# Run a single test file
|
|
19
|
+
bundle exec rspec spec/canon/comparison/xml_comparator_spec.rb
|
|
20
|
+
|
|
21
|
+
# Run tests with specific pattern
|
|
22
|
+
bundle exec rspec spec/canon/comparison --tag focus
|
|
23
|
+
|
|
24
|
+
# Lint (rubocop)
|
|
25
|
+
bundle exec rake rubocop
|
|
26
|
+
|
|
27
|
+
# Default task (runs tests + lint)
|
|
28
|
+
bundle exec rake
|
|
29
|
+
|
|
30
|
+
# Performance benchmarks (takes ~5 minutes)
|
|
31
|
+
bundle exec rake performance:run
|
|
32
|
+
|
|
33
|
+
# Quick benchmark (~30 seconds)
|
|
34
|
+
bundle exec rake performance:quick
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Size Limits
|
|
38
|
+
|
|
39
|
+
Canon protects against pathologically large files with configurable limits:
|
|
40
|
+
- **File size**: Default 5MB (`CANON_MAX_FILE_SIZE`)
|
|
41
|
+
- **Node count**: Default 10,000 (`CANON_MAX_NODE_COUNT`)
|
|
42
|
+
- **Diff output**: Default 10,000 lines (`CANON_MAX_DIFF_LINES`)
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
export CANON_MAX_FILE_SIZE=10485760 # 10MB
|
|
46
|
+
export CANON_MAX_NODE_COUNT=50000
|
|
47
|
+
bundle exec rspec
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Architecture
|
|
51
|
+
|
|
52
|
+
### Core Entry Points
|
|
53
|
+
|
|
54
|
+
- `lib/canon.rb` — Main module entry point. Provides `Canon.format`, `Canon.parse`, and shorthand methods like `Canon.format_xml`, `Canon.parse_json`. Defines `SUPPORTED_FORMATS = [:xml, :yaml, :json, :html, :html4, :html5, :string]`.
|
|
55
|
+
- `lib/canon/cli.rb` — Thor-based CLI. Two commands: `canon format` (canonicalize/pretty-print) and `canon diff` (semantic comparison). See CLI long descriptions for full option documentation.
|
|
56
|
+
- `exe/canon` — CLI entry point (runs the Thor CLI).
|
|
57
|
+
|
|
58
|
+
### Two Comparison Algorithms — Distinct by Design
|
|
59
|
+
|
|
60
|
+
Canon provides two **fundamentally different** comparison algorithms. They are NOT alternative implementations of the same approach. They use different methods, accept different options, produce different intermediate representations, and solve different problems. The pipelines must never be merged or "unified."
|
|
61
|
+
|
|
62
|
+
#### DOM Algorithm (`diff_algorithm: :dom`, default)
|
|
63
|
+
|
|
64
|
+
Position-based recursive tree walk. Compares children by position, attributes by name, namespace by URI.
|
|
65
|
+
|
|
66
|
+
- **Method**: Positional recursive descent through the DOM tree
|
|
67
|
+
- **Code path**: `dom_diff` → format comparators (`XmlComparator`, `HtmlComparator`, `JsonComparator`, `YamlComparator`)
|
|
68
|
+
- **Algorithm-specific options**: Filtering (`ignore_children`, `ignore_text_nodes`, `ignore_attrs`, `ignore_nodes`, `ignore_attr_content`, `diff_children`)
|
|
69
|
+
- **Intermediate representation**: Integer comparison-result codes (EQUIVALENT, MISSING_NODE, UNEQUAL_TEXT_CONTENTS, etc.)
|
|
70
|
+
- **Output**: DiffNodes with diff codes
|
|
71
|
+
- **Strength**: Deterministic positional comparison with fine-grained filtering; O(n) performance
|
|
72
|
+
- **Location**: `lib/canon/comparison/`
|
|
73
|
+
|
|
74
|
+
#### Semantic Tree Algorithm (`diff_algorithm: :semantic`)
|
|
75
|
+
|
|
76
|
+
Signature-based tree matching. Computes node signatures, uses hash matching + similarity matching + structural propagation to find node correspondences even when nodes have moved. Produces insert/update/delete/move operations.
|
|
77
|
+
|
|
78
|
+
- **Method**: Signature computation → three-phase matching (hash, similarity, structural propagation) → operation detection
|
|
79
|
+
- **Code path**: `semantic_diff` → `TreeDiffIntegrator` → format adapters → `OperationConverter` → DiffNodes
|
|
80
|
+
- **Algorithm-specific options**: Matching strategy (`similarity_threshold`, `hash_matching`, `similarity_matching`, `propagation`)
|
|
81
|
+
- **Intermediate representation**: Operations (INSERT, DELETE, UPDATE, MOVE)
|
|
82
|
+
- **Output**: Operations → converted to DiffNodes
|
|
83
|
+
- **Strength**: Detects moved/renamed nodes and structural reorganization
|
|
84
|
+
- **Location**: `lib/canon/tree_diff/`
|
|
85
|
+
|
|
86
|
+
#### Shared Infrastructure (safe to consolidate)
|
|
87
|
+
|
|
88
|
+
Both algorithms share these pipeline steps — this is the ONLY layer where consolidation makes sense:
|
|
89
|
+
- Format detection (`FormatDetector`)
|
|
90
|
+
- Config resolution (`Canon::Config`)
|
|
91
|
+
- Match option resolution (`MatchOptions` resolvers, profiles, dimensions)
|
|
92
|
+
- Preprocessing (`:none`, `:c14n`, `:normalize`, `:format`)
|
|
93
|
+
- Parsing (format-specific `.parse` methods)
|
|
94
|
+
- DiffNode output format — both produce DiffNodes
|
|
95
|
+
- Diff formatting (`by_line` / `by_object`) — both feed into the same formatters
|
|
96
|
+
|
|
97
|
+
#### NOT Shared (do NOT merge)
|
|
98
|
+
|
|
99
|
+
- The comparison engines are entirely separate codepaths with no shared comparison logic
|
|
100
|
+
- DOM-specific filtering options (`ignore_*`) have no meaning in semantic matching
|
|
101
|
+
- Semantic-specific matching options (`similarity_threshold`, `hash_matching`) have no meaning in DOM comparison
|
|
102
|
+
- Intermediate representations differ: diff codes vs tree-matching operations
|
|
103
|
+
|
|
104
|
+
#### Two Activation Paths
|
|
105
|
+
|
|
106
|
+
Semantic diff can be activated two ways:
|
|
107
|
+
1. `diff_algorithm: :semantic` — caught by `Comparison.equivalent?`, routes to `Comparison.semantic_diff` (owns full pipeline end-to-end)
|
|
108
|
+
2. `match: { semantic_diff: true }` — passes through `dom_diff` to the format comparator, which detects the flag and calls its own `perform_semantic_tree_diff`
|
|
109
|
+
|
|
110
|
+
Path 1 is the primary API. Path 2 exists for direct comparator calls and is tested in `spec/canon/tree_diff/canon_integration_spec.rb`.
|
|
111
|
+
|
|
112
|
+
### Four-Layer Architecture
|
|
113
|
+
|
|
114
|
+
The comparison pipeline flows through four independent layers:
|
|
115
|
+
|
|
116
|
+
1. **Preprocessing** — Optional normalization (`:none`, `:c14n`, `:normalize`, `:format`)
|
|
117
|
+
2. **Algorithm Selection** — `:dom` (position-based, stable) or `:semantic` (signature-based, experimental)
|
|
118
|
+
3. **Match Options** — Dimension behaviors per format (`:text_content`, `:structural_whitespace`, `:attribute_values`, etc.) controlled via profiles (`:strict`, `:spec_friendly`, `:rendered`, `:content_only`)
|
|
119
|
+
4. **Diff Formatting** — `by_line` or `by_object` output mode
|
|
120
|
+
|
|
121
|
+
**Critical distinction**: Do NOT use `Canon.format_xml` output for string comparison in tests. The formatting process changes line counts and causes false failures. Use `Canon::Comparison.equivalent?` or RSpec matchers instead.
|
|
122
|
+
|
|
123
|
+
### Key Modules
|
|
124
|
+
|
|
125
|
+
- `lib/canon/comparison/` — DOM-based comparison logic. `comparison.rb` is the main facade; comparators live in submodules. `MatchOptions::Xml` defines profiles (`:strict`, `:spec_friendly`, `:rendered`, `:content_only`) and per-dimension behaviors.
|
|
126
|
+
- `lib/canon/tree_diff/` — Semantic tree diff engine. Separate from the DOM comparators. Integrates back into the main pipeline via `OperationConverter`.
|
|
127
|
+
- `lib/canon/diff_formatter/` — Output formatters for diff results. Two modes: `by_line` (line-by-line, used for HTML and strings) and `by_object` (semantic/object-level, used for XML/JSON/YAML). Contains format-specific formatters for XML, HTML, JSON, YAML output.
|
|
128
|
+
- `lib/canon/formatters/` — Pretty-printers for canonicalization/formatting (distinct from diff formatters). `XmlFormatter` supports Canonical XML (C14N).
|
|
129
|
+
- `lib/canon/config.rb` — Global configuration with per-format settings (profiles, preprocessing, diff options). Read at runtime via `Canon::Config.instance`.
|
|
130
|
+
- `lib/canon/rspec_matchers.rb` — RSpec matchers (`be_xml_equivalent_to`, `be_json_equivalent_to`, etc.). These delegate to `Canon::Comparison.equivalent?` with the global config. Automatically included in RSpec.
|
|
131
|
+
- `lib/canon/xml/sax_builder.rb` — SAX-based canon-tree builder (~6x faster than DOM parsing for large documents). Engine-neutral; `lib/canon/xml/sax.rb` selects the driver (`NokogiriDriver` on CRuby, `MoxmlDriver` under Opal).
|
|
132
|
+
|
|
133
|
+
### XML Engines
|
|
134
|
+
|
|
135
|
+
Canon is engine-agnostic across three seams (MECE — one concern per module):
|
|
136
|
+
|
|
137
|
+
- `Canon::XmlBackend` — XML engine selection. The default follows moxml's resolved adapter: **leptris whenever it is installed** (parse ~1.6x, serialize ~3.8x vs Nokogiri), raw Nokogiri otherwise (wrapping Nokogiri in moxml buys nothing — the wrapper adds 2-3x overhead). `:moxml` under Opal. `CANON_XML_BACKEND=nokogiri|moxml` forces either engine.
|
|
138
|
+
- `Canon::XmlParsing` — the only place that talks to engines for XML parse/serialize (moxml parses pass `readonly: true` — canon never mutates engine documents); node type queries answer for ANY recognized node (Nokogiri or moxml) by type, never by active backend — user-supplied Nokogiri nodes keep working under the moxml engine.
|
|
139
|
+
- `Canon::Html::NokogiriSupport` — HTML is always Nokogiri on CRuby (moxml has no HTML adapter, leptris no HTML parser); independent of the XML engine.
|
|
140
|
+
- `Canon::Xml::Sax` — SAX driver selection: `NokogiriDriver` on CRuby (per-event C callbacks beat FFI SAX today), `MoxmlDriver` under Opal. The builder (`SaxBuilder`) is engine-neutral.
|
|
141
|
+
|
|
142
|
+
Engine parity is complete through libleptris 1.9.8 / leptris-ruby 1.9.33 / moxml 0.5.12: attribute-value normalization, prolog/epilog PIs and comments, DTD ATTLIST defaults off by default (opt-in via `ParseOptions::DTDATTR`), PI-data whitespace matching libxml2, materialize with namespace declarations and a root-subtree-only record contract (moxml#140), readonly parse, deterministic `Document#free` (wired into `from_moxml_xml` — moxml#134). Canon's moxml conversion is record-based (`build_from_moxml` consumes `materialize` records — no per-node wrapper allocation; ~0.55x the Nokogiri conversion). `spec/canon/xml/engine_parity_spec.rb` is fully green. Remaining product gaps: moxml#129 (serializer byte parity — last blocker for pretty-printing on leptris) and leptris#594 (SAX at parity, not faster — the SAX driver stays Nokogiri). Pretty-printers (`PrettyPrinter::Xml`, `XmlNormalized`) deliberately stay on the Nokogiri pipeline — pretty-printed bytes are canon's product.
|
|
143
|
+
|
|
144
|
+
Engine A/B testing: `CANON_XML_BACKEND=nokogiri bundle exec rspec` (or `=moxml` to force leptris when it isn't the resolved default). The default suite must stay green under BOTH values; the only expected pendings are the upstream-tracked ones. The benchmark header (`rake performance:quick`) reports the active engine.
|
|
145
|
+
|
|
146
|
+
### Format Detection
|
|
147
|
+
|
|
148
|
+
`Canon::Comparison::FormatDetector` auto-detects format from string content or object type (Moxml::Node → XML, Nokogiri::HTML → HTML, Hash → JSON, etc.). HTML4 vs HTML5 is determined by DOCTYPE.
|
|
149
|
+
|
|
150
|
+
### Difference Result Format
|
|
151
|
+
|
|
152
|
+
When `verbose: true`, comparison returns `Canon::Comparison::ComparisonResult` (or legacy Hash/Array). The result exposes:
|
|
153
|
+
- `differences` — array of `DiffNode` objects
|
|
154
|
+
- `equivalent?` — boolean
|
|
155
|
+
- `preprocessed_strings` / `original_strings` — for diff display
|
|
156
|
+
- `tree_diff_operations` / `tree_diff_statistics` / `tree_diff_matching` — only when using semantic algorithm
|
|
157
|
+
|
|
158
|
+
### Difference Classification
|
|
159
|
+
|
|
160
|
+
Differences are classified into three tiers:
|
|
161
|
+
- **Normative** — Affects equivalence (documents not equivalent if different)
|
|
162
|
+
- **Informative** — Tracked but doesn't affect equivalence
|
|
163
|
+
- **Formatting-only** — Pure whitespace/formatting differences when normalized content matches
|
|
164
|
+
|
|
165
|
+
Use `show_diffs: :normative` to display only equivalence-affecting differences.
|
|
166
|
+
|
|
167
|
+
### Testing Notes
|
|
168
|
+
|
|
169
|
+
- Tests use a shared fixture system (`spec/canon/fixtures/`). `spec/canon/fixtures_integrity_spec.rb` validates fixture references.
|
|
170
|
+
- `spec/spec_helper.rb` disables monkey-patching and uses `expect` syntax.
|
|
171
|
+
- Specs named `*_spec.rb` under `spec/canon/` map to `lib/canon/`.
|
|
172
|
+
|
|
173
|
+
## Architectural Rules
|
|
174
|
+
|
|
175
|
+
These rules are non-negotiable. Violations must be fixed before merge.
|
|
176
|
+
|
|
177
|
+
### No `respond_to?` — use proper types
|
|
178
|
+
|
|
179
|
+
`respond_to?` is a type-system bypass. It means the code does not know what it is working with, which is a failure of the model. Every object flowing through the comparison pipeline is one of a known set of types (`Canon::Xml::Node` and subclasses, `Nokogiri::XML::Node` and subclasses, `ComparisonResult`, `DiffNode`, `Hash`, `String`). Use `is_a?` for type dispatch, or better yet, design the classes so that polymorphism handles dispatch automatically (e.g., a shared base class or module providing the same interface).
|
|
180
|
+
|
|
181
|
+
If you find yourself writing `respond_to?`, stop and introduce a proper type check or a shared protocol instead.
|
|
182
|
+
|
|
183
|
+
### No `send` to bypass visibility — make the method public
|
|
184
|
+
|
|
185
|
+
Using `send` to call a private method from another module or class is an encapsulation violation. If another object needs to call a method, that method must be public. Private means "internal implementation detail of this class" — if it is needed externally, it is not private. Either make it public or rethink the boundary.
|
|
186
|
+
|
|
187
|
+
### No duplicated type-checking logic
|
|
188
|
+
|
|
189
|
+
Backend-agnostic node queries (text node?, text content, whitespace check) must live in one place. The `Canon::Comparison::NodeInspector` module provides a single source of truth for cross-backend node type operations. All code that needs to query node properties must use it — never re-implement type dispatch inline.
|
|
190
|
+
|
|
191
|
+
### Single module for cross-cutting utilities
|
|
192
|
+
|
|
193
|
+
When multiple modules need the same capability (e.g., checking if a node is a whitespace-only text node), extract a single utility module. Do not duplicate the logic in each consumer.
|
|
194
|
+
|
|
195
|
+
### Two algorithm pipelines must remain separate
|
|
196
|
+
|
|
197
|
+
DOM and Semantic comparison are fundamentally different algorithms with different option sets, different intermediate representations, and different codepaths. Proposals to "unify" or "merge" the algorithm pipelines are always wrong. The correct approach is to extract shared infrastructure (format detection, config, parsing, output formatting) into reusable methods while keeping the algorithm cores independent. See "Two Comparison Algorithms — Distinct by Design" above.
|
data/Rakefile
CHANGED
|
@@ -24,7 +24,9 @@ begin
|
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
# REXML: bundled gem since Ruby 3.4
|
|
27
|
-
rexml_lib = $LOAD_PATH.find
|
|
27
|
+
rexml_lib = $LOAD_PATH.find do |p|
|
|
28
|
+
File.exist?(File.join(p, "rexml", "document.rb"))
|
|
29
|
+
end
|
|
28
30
|
Opal.append_path rexml_lib if rexml_lib
|
|
29
31
|
end
|
|
30
32
|
rescue LoadError
|
|
@@ -42,7 +44,8 @@ namespace :spec do
|
|
|
42
44
|
desc "Run Opal (JavaScript) tests"
|
|
43
45
|
Opal::RSpec::RakeTask.new(:opal) do |_server, runner|
|
|
44
46
|
runner.default_path = "spec"
|
|
45
|
-
runner.requires = %w[rexml_compat rexml/document rexml/xpath
|
|
47
|
+
runner.requires = %w[rexml_compat rexml/document rexml/xpath
|
|
48
|
+
moxml moxml/adapter/rexml spec_helper]
|
|
46
49
|
runner.pattern = "spec/canon/opal_xml_smoke_spec.rb"
|
|
47
50
|
end
|
|
48
51
|
end
|
data/lib/canon/cache.rb
CHANGED
|
@@ -101,7 +101,9 @@ module Canon
|
|
|
101
101
|
Digest::SHA256.hexdigest(content)[0..16]
|
|
102
102
|
else
|
|
103
103
|
# Opal fallback: simple string hash
|
|
104
|
-
h = content.each_char.reduce(0)
|
|
104
|
+
h = content.each_char.reduce(0) do |acc, c|
|
|
105
|
+
((acc * 31) + c.ord) & 0xFFFFFFFF
|
|
106
|
+
end
|
|
105
107
|
h.to_s(16).rjust(8, "0")
|
|
106
108
|
end
|
|
107
109
|
end
|
data/lib/canon/cli.rb
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "../comparison"
|
|
4
|
-
require_relative "../diff_formatter"
|
|
5
|
-
require_relative "../color_detector"
|
|
6
3
|
require "json"
|
|
7
4
|
require "yaml"
|
|
8
5
|
|
|
@@ -15,7 +12,7 @@ module Canon
|
|
|
15
12
|
end
|
|
16
13
|
|
|
17
14
|
# rubocop:disable Metrics/MethodLength
|
|
18
|
-
# rubocop:disable Metrics/AbcSize
|
|
15
|
+
# rubocop:disable-next Metrics/AbcSize
|
|
19
16
|
def run(file1, file2)
|
|
20
17
|
# Detect formats
|
|
21
18
|
format1 = @options[:format1] || @options[:format] || detect_format(file1)
|
|
@@ -66,7 +63,6 @@ module Canon
|
|
|
66
63
|
|
|
67
64
|
# Show configuration in verbose mode using shared DebugOutput
|
|
68
65
|
if @options[:verbose]
|
|
69
|
-
require_relative "../diff_formatter/debug_output"
|
|
70
66
|
config_output = Canon::DiffFormatter::DebugOutput.verbose_tables_only(
|
|
71
67
|
result,
|
|
72
68
|
{
|
|
@@ -102,7 +98,6 @@ module Canon
|
|
|
102
98
|
rescue StandardError => e
|
|
103
99
|
abort "Error processing files: #{e.message}"
|
|
104
100
|
end
|
|
105
|
-
# rubocop:enable Metrics/AbcSize
|
|
106
101
|
# rubocop:enable Metrics/MethodLength
|
|
107
102
|
|
|
108
103
|
private
|
|
@@ -219,12 +214,10 @@ module Canon
|
|
|
219
214
|
|
|
220
215
|
case format
|
|
221
216
|
when :xml
|
|
222
|
-
require_relative "../pretty_printer/xml"
|
|
223
217
|
formatted1 = Canon::PrettyPrinter::Xml.new(indent: 2).format(content1)
|
|
224
218
|
formatted2 = Canon::PrettyPrinter::Xml.new(indent: 2).format(content2)
|
|
225
219
|
[formatted1, formatted2]
|
|
226
220
|
when :html
|
|
227
|
-
require_relative "../pretty_printer/html"
|
|
228
221
|
formatted1 = Canon::PrettyPrinter::Html.new(indent: 2).format(content1)
|
|
229
222
|
formatted2 = Canon::PrettyPrinter::Html.new(indent: 2).format(content2)
|
|
230
223
|
[formatted1, formatted2]
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Canon
|
|
4
|
+
# Thor command implementations invoked by {CLI}. Children are autoloaded.
|
|
5
|
+
module Commands
|
|
6
|
+
autoload :DiffCommand, "canon/commands/diff_command"
|
|
7
|
+
autoload :FormatCommand, "canon/commands/format_command"
|
|
8
|
+
end
|
|
9
|
+
end
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module Canon
|
|
4
4
|
module Comparison
|
|
5
5
|
# CompareProfile encapsulates the policy decisions about how differences
|
|
6
|
-
# in various dimensions should be handled during comparison
|
|
6
|
+
# in various dimensions should be handled during comparison.
|
|
7
7
|
#
|
|
8
8
|
# This class provides separation of concerns:
|
|
9
9
|
# - CompareProfile: Policy decisions (what to track, what affects equivalence)
|
|
@@ -17,69 +17,46 @@ module Canon
|
|
|
17
17
|
@match_options = match_options
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
# Should DiffNodes be created for differences in this dimension?
|
|
21
|
-
#
|
|
22
|
-
# In verbose mode, we want to track ALL differences for reporting.
|
|
23
|
-
# In non-verbose mode, we only need to track normative differences.
|
|
24
|
-
#
|
|
25
|
-
# @param dimension [Symbol] The match dimension to check
|
|
26
|
-
# @return [Boolean] true if differences should be tracked
|
|
27
20
|
def track_dimension?(_dimension)
|
|
28
|
-
# Always track dimensions that affect equivalence
|
|
29
|
-
# In verbose mode, also track informative dimensions
|
|
30
21
|
true
|
|
31
22
|
end
|
|
32
23
|
|
|
33
24
|
# Should differences in this dimension affect equivalence?
|
|
34
25
|
#
|
|
35
|
-
# This determines the return value of the comparison:
|
|
36
|
-
# - true: differences make documents non-equivalent
|
|
37
|
-
# - false: differences are informative only
|
|
38
|
-
#
|
|
39
26
|
# @param dimension [Symbol] The match dimension to check
|
|
40
27
|
# @return [Boolean] true if differences affect equivalence
|
|
41
28
|
def affects_equivalence?(dimension)
|
|
42
29
|
behavior = behavior_for(dimension)
|
|
43
|
-
|
|
44
|
-
# :strict → affects equivalence
|
|
45
|
-
# :normalize → might affect (if normalization fails)
|
|
46
|
-
# :ignore → does NOT affect equivalence
|
|
47
30
|
behavior != :ignore
|
|
48
31
|
end
|
|
49
32
|
|
|
50
33
|
# Is a difference in this dimension normative (affects equivalence)?
|
|
51
34
|
#
|
|
52
|
-
#
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
# - structural_whitespace: only :strict is normative (:normalize and :ignore are informative)
|
|
56
|
-
# - all other dimensions: normative unless behavior is :ignore
|
|
35
|
+
# Delegates to the Dimension object's normative? rule. Falls back to
|
|
36
|
+
# the default rule (normative unless :ignore) for dimensions not in the
|
|
37
|
+
# format's dimension set (e.g., derived dimensions like :element_structure).
|
|
57
38
|
#
|
|
58
39
|
# @param dimension [Symbol] The match dimension to check
|
|
59
40
|
# @return [Boolean] true if normative, false if informative
|
|
60
41
|
def normative_dimension?(dimension)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
42
|
+
dim = dimension_for(dimension)
|
|
43
|
+
if dim
|
|
44
|
+
dim.normative?(behavior_for(dimension))
|
|
45
|
+
else
|
|
46
|
+
behavior_for(dimension) != :ignore
|
|
66
47
|
end
|
|
67
|
-
|
|
68
|
-
# For all other dimensions, normative if behavior affects equivalence
|
|
69
|
-
affects_equivalence?(dimension)
|
|
70
48
|
end
|
|
71
49
|
|
|
72
50
|
# Can a difference in this dimension be formatting-only?
|
|
73
51
|
#
|
|
74
|
-
#
|
|
75
|
-
#
|
|
52
|
+
# Delegates to the Dimension object's supports_formatting_detection?
|
|
53
|
+
# flag. Falls back to false for unknown dimensions.
|
|
76
54
|
#
|
|
77
55
|
# @param dimension [Symbol] The match dimension to check
|
|
78
56
|
# @return [Boolean] true if formatting detection should apply
|
|
79
57
|
def supports_formatting_detection?(dimension)
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
%i[text_content structural_whitespace].include?(dimension)
|
|
58
|
+
dim = dimension_for(dimension)
|
|
59
|
+
dim ? dim.supports_formatting_detection? : false
|
|
83
60
|
end
|
|
84
61
|
|
|
85
62
|
# Get the behavior setting for a dimension
|
|
@@ -94,6 +71,23 @@ module Canon
|
|
|
94
71
|
:strict
|
|
95
72
|
end
|
|
96
73
|
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
def dimension_for(name)
|
|
78
|
+
set = Dimensions::Registry.for(extract_format)
|
|
79
|
+
set[name]
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def extract_format
|
|
83
|
+
if match_options.is_a?(ResolvedMatchOptions)
|
|
84
|
+
match_options.format
|
|
85
|
+
elsif match_options.is_a?(Hash)
|
|
86
|
+
match_options[:format]
|
|
87
|
+
else
|
|
88
|
+
:xml
|
|
89
|
+
end
|
|
90
|
+
end
|
|
97
91
|
end
|
|
98
92
|
end
|
|
99
93
|
end
|
|
@@ -133,8 +133,6 @@ parse_errors_expected: nil, parse_errors_received: nil)
|
|
|
133
133
|
# @return [String] Formatted diff output
|
|
134
134
|
def diff(use_color: true, context_lines: 3, diff_grouping_lines: nil,
|
|
135
135
|
show_diffs: :all, diff_mode: :separate, legacy_terminal: false)
|
|
136
|
-
require_relative "../diff_formatter"
|
|
137
|
-
|
|
138
136
|
formatter = Canon::DiffFormatter.new(
|
|
139
137
|
use_color: use_color,
|
|
140
138
|
mode: :by_line,
|
|
@@ -164,13 +162,12 @@ show_diffs: :all, diff_mode: :separate, legacy_terminal: false)
|
|
|
164
162
|
def summarize_diff_node(diff)
|
|
165
163
|
parts = ["Not equivalent:"]
|
|
166
164
|
|
|
167
|
-
# rubocop:disable Layout/SpaceBeforeInterpolation,Style/ConditionalAssignment
|
|
165
|
+
# rubocop:disable-next Layout/SpaceBeforeInterpolation,Style/ConditionalAssignment
|
|
168
166
|
if diff.path
|
|
169
167
|
parts << "#{diff.reason} at #{diff.path}"
|
|
170
168
|
else
|
|
171
169
|
parts << diff.reason.to_s
|
|
172
170
|
end
|
|
173
|
-
# rubocop:enable Layout/SpaceBeforeInterpolation,Style/ConditionalAssignment
|
|
174
171
|
|
|
175
172
|
if diff.serialized_before && diff.serialized_after
|
|
176
173
|
before_preview = truncate_preview(diff.serialized_before)
|