canon 0.1.7 → 0.1.9
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 +69 -92
- data/README.adoc +13 -13
- data/docs/.lycheeignore +69 -0
- data/docs/Gemfile +1 -0
- data/docs/_config.yml +90 -1
- data/docs/advanced/diff-classification.adoc +82 -2
- data/docs/advanced/extending-canon.adoc +193 -0
- data/docs/features/match-options/index.adoc +239 -1
- data/docs/internals/diffnode-enrichment.adoc +611 -0
- data/docs/internals/index.adoc +251 -0
- data/docs/lychee.toml +13 -6
- data/docs/understanding/architecture.adoc +749 -33
- data/docs/understanding/comparison-pipeline.adoc +122 -0
- data/lib/canon/cache.rb +129 -0
- data/lib/canon/comparison/dimensions/attribute_order_dimension.rb +68 -0
- data/lib/canon/comparison/dimensions/attribute_presence_dimension.rb +68 -0
- data/lib/canon/comparison/dimensions/attribute_values_dimension.rb +171 -0
- data/lib/canon/comparison/dimensions/base_dimension.rb +107 -0
- data/lib/canon/comparison/dimensions/comments_dimension.rb +121 -0
- data/lib/canon/comparison/dimensions/element_position_dimension.rb +90 -0
- data/lib/canon/comparison/dimensions/registry.rb +77 -0
- data/lib/canon/comparison/dimensions/structural_whitespace_dimension.rb +119 -0
- data/lib/canon/comparison/dimensions/text_content_dimension.rb +96 -0
- data/lib/canon/comparison/dimensions.rb +54 -0
- data/lib/canon/comparison/format_detector.rb +87 -0
- data/lib/canon/comparison/html_comparator.rb +70 -26
- data/lib/canon/comparison/html_compare_profile.rb +8 -2
- data/lib/canon/comparison/html_parser.rb +80 -0
- data/lib/canon/comparison/json_comparator.rb +12 -0
- data/lib/canon/comparison/json_parser.rb +19 -0
- data/lib/canon/comparison/markup_comparator.rb +293 -0
- data/lib/canon/comparison/match_options/base_resolver.rb +150 -0
- data/lib/canon/comparison/match_options/json_resolver.rb +82 -0
- data/lib/canon/comparison/match_options/xml_resolver.rb +151 -0
- data/lib/canon/comparison/match_options/yaml_resolver.rb +87 -0
- data/lib/canon/comparison/match_options.rb +68 -463
- data/lib/canon/comparison/profile_definition.rb +149 -0
- data/lib/canon/comparison/ruby_object_comparator.rb +180 -0
- data/lib/canon/comparison/strategies/semantic_tree_match_strategy.rb +7 -10
- data/lib/canon/comparison/whitespace_sensitivity.rb +208 -0
- data/lib/canon/comparison/xml_comparator/attribute_comparator.rb +177 -0
- data/lib/canon/comparison/xml_comparator/attribute_filter.rb +136 -0
- data/lib/canon/comparison/xml_comparator/child_comparison.rb +197 -0
- data/lib/canon/comparison/xml_comparator/diff_node_builder.rb +115 -0
- data/lib/canon/comparison/xml_comparator/namespace_comparator.rb +186 -0
- data/lib/canon/comparison/xml_comparator/node_parser.rb +79 -0
- data/lib/canon/comparison/xml_comparator/node_type_comparator.rb +102 -0
- data/lib/canon/comparison/xml_comparator.rb +97 -684
- data/lib/canon/comparison/xml_node_comparison.rb +319 -0
- data/lib/canon/comparison/xml_parser.rb +19 -0
- data/lib/canon/comparison/yaml_comparator.rb +3 -3
- data/lib/canon/comparison.rb +265 -110
- data/lib/canon/diff/diff_classifier.rb +101 -2
- data/lib/canon/diff/diff_node.rb +32 -2
- data/lib/canon/diff/formatting_detector.rb +1 -1
- data/lib/canon/diff/node_serializer.rb +191 -0
- data/lib/canon/diff/path_builder.rb +143 -0
- data/lib/canon/diff_formatter/by_line/base_formatter.rb +251 -0
- data/lib/canon/diff_formatter/by_line/html_formatter.rb +6 -248
- data/lib/canon/diff_formatter/by_line/xml_formatter.rb +38 -229
- data/lib/canon/diff_formatter/diff_detail_formatter/color_helper.rb +30 -0
- data/lib/canon/diff_formatter/diff_detail_formatter/dimension_formatter.rb +579 -0
- data/lib/canon/diff_formatter/diff_detail_formatter/location_extractor.rb +121 -0
- data/lib/canon/diff_formatter/diff_detail_formatter/node_utils.rb +253 -0
- data/lib/canon/diff_formatter/diff_detail_formatter/text_utils.rb +61 -0
- data/lib/canon/diff_formatter/diff_detail_formatter.rb +31 -1028
- data/lib/canon/diff_formatter.rb +1 -1
- data/lib/canon/rspec_matchers.rb +38 -9
- data/lib/canon/tree_diff/operation_converter.rb +92 -338
- data/lib/canon/tree_diff/operation_converter_helpers/metadata_enricher.rb +71 -0
- data/lib/canon/tree_diff/operation_converter_helpers/post_processor.rb +103 -0
- data/lib/canon/tree_diff/operation_converter_helpers/reason_builder.rb +168 -0
- data/lib/canon/tree_diff/operation_converter_helpers/update_change_handler.rb +188 -0
- data/lib/canon/version.rb +1 -1
- data/lib/canon/xml/data_model.rb +24 -13
- metadata +48 -2
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "match_options/base_resolver"
|
|
4
|
+
require_relative "match_options/xml_resolver"
|
|
5
|
+
require_relative "match_options/json_resolver"
|
|
6
|
+
require_relative "match_options/yaml_resolver"
|
|
7
|
+
|
|
3
8
|
module Canon
|
|
4
9
|
module Comparison
|
|
5
10
|
# Matching Options for Canon Comparison
|
|
@@ -127,226 +132,38 @@ module Canon
|
|
|
127
132
|
attribute_values
|
|
128
133
|
element_position
|
|
129
134
|
comments
|
|
130
|
-
element_structure
|
|
131
|
-
element_position
|
|
132
|
-
element_hierarchy
|
|
133
135
|
].freeze
|
|
134
136
|
|
|
135
|
-
#
|
|
136
|
-
FORMAT_DEFAULTS =
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
structural_whitespace: :normalize,
|
|
141
|
-
attribute_presence: :strict,
|
|
142
|
-
attribute_order: :ignore,
|
|
143
|
-
attribute_values: :strict,
|
|
144
|
-
element_position: :ignore,
|
|
145
|
-
comments: :ignore,
|
|
146
|
-
},
|
|
147
|
-
xml: {
|
|
148
|
-
preprocessing: :none,
|
|
149
|
-
text_content: :strict,
|
|
150
|
-
structural_whitespace: :strict,
|
|
151
|
-
attribute_presence: :strict,
|
|
152
|
-
attribute_order: :ignore,
|
|
153
|
-
attribute_values: :strict,
|
|
154
|
-
element_position: :strict,
|
|
155
|
-
comments: :strict,
|
|
156
|
-
},
|
|
157
|
-
}.freeze
|
|
158
|
-
|
|
159
|
-
# Predefined match profiles for XML/HTML
|
|
160
|
-
MATCH_PROFILES = {
|
|
161
|
-
# Strict: Match exactly as written in source (XML default)
|
|
162
|
-
strict: {
|
|
163
|
-
preprocessing: :none,
|
|
164
|
-
text_content: :strict,
|
|
165
|
-
structural_whitespace: :strict,
|
|
166
|
-
attribute_presence: :strict,
|
|
167
|
-
attribute_order: :strict,
|
|
168
|
-
attribute_values: :strict,
|
|
169
|
-
element_position: :strict,
|
|
170
|
-
comments: :strict,
|
|
171
|
-
},
|
|
172
|
-
|
|
173
|
-
# Rendered: Match rendered output (HTML default)
|
|
174
|
-
# Mimics CSS whitespace collapsing
|
|
175
|
-
rendered: {
|
|
176
|
-
preprocessing: :none,
|
|
177
|
-
text_content: :normalize,
|
|
178
|
-
structural_whitespace: :normalize,
|
|
179
|
-
attribute_presence: :strict,
|
|
180
|
-
attribute_order: :strict,
|
|
181
|
-
attribute_values: :strict,
|
|
182
|
-
element_position: :strict,
|
|
183
|
-
comments: :ignore,
|
|
184
|
-
},
|
|
185
|
-
|
|
186
|
-
# HTML4: Match HTML4 rendered output
|
|
187
|
-
# HTML4 rendering normalizes attribute whitespace
|
|
188
|
-
html4: {
|
|
189
|
-
preprocessing: :rendered,
|
|
190
|
-
text_content: :normalize,
|
|
191
|
-
structural_whitespace: :normalize,
|
|
192
|
-
attribute_presence: :strict,
|
|
193
|
-
attribute_order: :strict,
|
|
194
|
-
attribute_values: :normalize,
|
|
195
|
-
element_position: :ignore,
|
|
196
|
-
comments: :ignore,
|
|
197
|
-
},
|
|
198
|
-
|
|
199
|
-
# HTML5: Match HTML5 rendered output (same as rendered)
|
|
200
|
-
html5: {
|
|
201
|
-
preprocessing: :rendered,
|
|
202
|
-
text_content: :normalize,
|
|
203
|
-
structural_whitespace: :normalize,
|
|
204
|
-
attribute_presence: :strict,
|
|
205
|
-
attribute_order: :strict,
|
|
206
|
-
attribute_values: :strict,
|
|
207
|
-
element_position: :ignore,
|
|
208
|
-
comments: :ignore,
|
|
209
|
-
},
|
|
210
|
-
|
|
211
|
-
# Spec-friendly: Formatting doesn't matter
|
|
212
|
-
# Uses :rendered preprocessing for HTML which normalizes via to_html
|
|
213
|
-
spec_friendly: {
|
|
214
|
-
preprocessing: :rendered,
|
|
215
|
-
text_content: :normalize,
|
|
216
|
-
structural_whitespace: :ignore,
|
|
217
|
-
attribute_presence: :strict,
|
|
218
|
-
attribute_order: :ignore,
|
|
219
|
-
attribute_values: :normalize,
|
|
220
|
-
element_position: :ignore,
|
|
221
|
-
comments: :ignore,
|
|
222
|
-
},
|
|
223
|
-
|
|
224
|
-
# Content-only: Only content matters
|
|
225
|
-
content_only: {
|
|
226
|
-
preprocessing: :c14n,
|
|
227
|
-
text_content: :normalize,
|
|
228
|
-
structural_whitespace: :ignore,
|
|
229
|
-
attribute_presence: :strict,
|
|
230
|
-
attribute_order: :ignore,
|
|
231
|
-
attribute_values: :normalize,
|
|
232
|
-
element_position: :ignore,
|
|
233
|
-
comments: :ignore,
|
|
234
|
-
},
|
|
235
|
-
}.freeze
|
|
137
|
+
# Expose FORMAT_DEFAULTS from XmlResolver (for backward compatibility)
|
|
138
|
+
FORMAT_DEFAULTS = MatchOptions::XmlResolver.const_get(:FORMAT_DEFAULTS)
|
|
139
|
+
|
|
140
|
+
# Expose MATCH_PROFILES from XmlResolver (for backward compatibility)
|
|
141
|
+
MATCH_PROFILES = MatchOptions::XmlResolver.const_get(:MATCH_PROFILES)
|
|
236
142
|
|
|
237
143
|
class << self
|
|
238
|
-
#
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
# 1. Explicit match parameter
|
|
242
|
-
# 2. Profile from match_profile parameter
|
|
243
|
-
# 3. Global configuration
|
|
244
|
-
# 4. Format-specific defaults
|
|
245
|
-
#
|
|
246
|
-
# @param format [Symbol] Format type (:xml or :html)
|
|
247
|
-
# @param match_profile [Symbol, nil] Profile name
|
|
248
|
-
# @param match [Hash, nil] Explicit options per dimension
|
|
249
|
-
# @param preprocessing [Symbol, nil] Preprocessing option
|
|
250
|
-
# @param global_profile [Symbol, nil] Global configured profile
|
|
251
|
-
# @param global_options [Hash, nil] Global configured options
|
|
252
|
-
# @return [Hash] Resolved options for all dimensions
|
|
253
|
-
def resolve(
|
|
254
|
-
format:,
|
|
255
|
-
match_profile: nil,
|
|
256
|
-
match: nil,
|
|
257
|
-
preprocessing: nil,
|
|
258
|
-
global_profile: nil,
|
|
259
|
-
global_options: nil
|
|
260
|
-
)
|
|
261
|
-
# Start with format-specific defaults
|
|
262
|
-
options = FORMAT_DEFAULTS[format]&.dup || FORMAT_DEFAULTS[:xml].dup
|
|
263
|
-
|
|
264
|
-
# Apply global profile if specified
|
|
265
|
-
if global_profile
|
|
266
|
-
profile_opts = get_profile_options(global_profile)
|
|
267
|
-
options.merge!(profile_opts)
|
|
268
|
-
end
|
|
269
|
-
|
|
270
|
-
# Apply global options if specified
|
|
271
|
-
if global_options
|
|
272
|
-
validate_match_options!(global_options)
|
|
273
|
-
options.merge!(global_options)
|
|
274
|
-
end
|
|
275
|
-
|
|
276
|
-
# Apply per-call profile if specified (overrides global)
|
|
277
|
-
if match_profile
|
|
278
|
-
profile_opts = get_profile_options(match_profile)
|
|
279
|
-
options.merge!(profile_opts)
|
|
280
|
-
end
|
|
281
|
-
|
|
282
|
-
# Apply per-call preprocessing if specified (overrides profile)
|
|
283
|
-
if preprocessing
|
|
284
|
-
validate_preprocessing!(preprocessing)
|
|
285
|
-
options[:preprocessing] = preprocessing
|
|
286
|
-
end
|
|
287
|
-
|
|
288
|
-
# Apply per-call explicit options if specified (highest priority)
|
|
289
|
-
if match
|
|
290
|
-
validate_match_options!(match)
|
|
291
|
-
options.merge!(match)
|
|
292
|
-
end
|
|
293
|
-
|
|
294
|
-
options
|
|
144
|
+
# Delegate to XmlResolver
|
|
145
|
+
def resolve(**kwargs)
|
|
146
|
+
MatchOptions::XmlResolver.resolve(**kwargs)
|
|
295
147
|
end
|
|
296
148
|
|
|
297
|
-
#
|
|
298
|
-
#
|
|
299
|
-
# @param profile [Symbol] Profile name
|
|
300
|
-
# @return [Hash] Profile options
|
|
301
|
-
# @raise [Canon::Error] If profile is unknown
|
|
149
|
+
# Delegate to XmlResolver
|
|
302
150
|
def get_profile_options(profile)
|
|
303
|
-
|
|
304
|
-
raise Canon::Error,
|
|
305
|
-
"Unknown match profile: #{profile}. " \
|
|
306
|
-
"Valid profiles: #{MATCH_PROFILES.keys.join(', ')}"
|
|
307
|
-
end
|
|
308
|
-
MATCH_PROFILES[profile].dup
|
|
151
|
+
MatchOptions::XmlResolver.get_profile_options(profile)
|
|
309
152
|
end
|
|
310
153
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
#
|
|
314
|
-
def
|
|
315
|
-
|
|
316
|
-
raise Canon::Error,
|
|
317
|
-
"Unknown preprocessing option: #{preprocessing}. " \
|
|
318
|
-
"Valid options: #{MatchOptions::PREPROCESSING_OPTIONS.join(', ')}"
|
|
319
|
-
end
|
|
154
|
+
# Get valid match dimensions for XML/HTML
|
|
155
|
+
#
|
|
156
|
+
# @return [Array<Symbol>] Valid dimensions
|
|
157
|
+
def match_dimensions
|
|
158
|
+
MatchOptions::XmlResolver.match_dimensions
|
|
320
159
|
end
|
|
321
160
|
|
|
322
|
-
#
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
similarity_threshold
|
|
329
|
-
hash_matching
|
|
330
|
-
similarity_matching
|
|
331
|
-
propagation
|
|
332
|
-
]
|
|
333
|
-
|
|
334
|
-
match_options.each do |dimension, behavior|
|
|
335
|
-
# Skip special options (validated elsewhere or passed through)
|
|
336
|
-
next if special_options.include?(dimension)
|
|
337
|
-
|
|
338
|
-
unless MATCH_DIMENSIONS.include?(dimension)
|
|
339
|
-
raise Canon::Error,
|
|
340
|
-
"Unknown match dimension: #{dimension}. " \
|
|
341
|
-
"Valid dimensions: #{MATCH_DIMENSIONS.join(', ')}"
|
|
342
|
-
end
|
|
343
|
-
|
|
344
|
-
unless MatchOptions::MATCH_BEHAVIORS.include?(behavior)
|
|
345
|
-
raise Canon::Error,
|
|
346
|
-
"Unknown match behavior: #{behavior} for #{dimension}. " \
|
|
347
|
-
"Valid behaviors: #{MatchOptions::MATCH_BEHAVIORS.join(', ')}"
|
|
348
|
-
end
|
|
349
|
-
end
|
|
161
|
+
# Get format-specific default options
|
|
162
|
+
#
|
|
163
|
+
# @param format [Symbol] Format type
|
|
164
|
+
# @return [Hash] Default options for the format
|
|
165
|
+
def format_defaults(format)
|
|
166
|
+
MatchOptions::XmlResolver.format_defaults(format)
|
|
350
167
|
end
|
|
351
168
|
end
|
|
352
169
|
end
|
|
@@ -360,140 +177,36 @@ module Canon
|
|
|
360
177
|
key_order
|
|
361
178
|
].freeze
|
|
362
179
|
|
|
363
|
-
#
|
|
364
|
-
FORMAT_DEFAULTS =
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
structural_whitespace: :ignore,
|
|
369
|
-
key_order: :strict,
|
|
370
|
-
},
|
|
371
|
-
}.freeze
|
|
372
|
-
|
|
373
|
-
# Predefined match profiles for JSON
|
|
374
|
-
MATCH_PROFILES = {
|
|
375
|
-
# Strict: Match exactly
|
|
376
|
-
strict: {
|
|
377
|
-
preprocessing: :none,
|
|
378
|
-
text_content: :strict,
|
|
379
|
-
structural_whitespace: :strict,
|
|
380
|
-
key_order: :strict,
|
|
381
|
-
},
|
|
382
|
-
|
|
383
|
-
# Spec-friendly: Formatting and order don't matter
|
|
384
|
-
spec_friendly: {
|
|
385
|
-
preprocessing: :normalize,
|
|
386
|
-
text_content: :strict,
|
|
387
|
-
structural_whitespace: :ignore,
|
|
388
|
-
key_order: :ignore,
|
|
389
|
-
},
|
|
390
|
-
|
|
391
|
-
# Content-only: Only values matter
|
|
392
|
-
content_only: {
|
|
393
|
-
preprocessing: :normalize,
|
|
394
|
-
text_content: :normalize,
|
|
395
|
-
structural_whitespace: :ignore,
|
|
396
|
-
key_order: :ignore,
|
|
397
|
-
},
|
|
398
|
-
}.freeze
|
|
180
|
+
# Expose FORMAT_DEFAULTS from JsonResolver (for backward compatibility)
|
|
181
|
+
FORMAT_DEFAULTS = MatchOptions::JsonResolver.const_get(:FORMAT_DEFAULTS)
|
|
182
|
+
|
|
183
|
+
# Expose MATCH_PROFILES from JsonResolver (for backward compatibility)
|
|
184
|
+
MATCH_PROFILES = MatchOptions::JsonResolver.const_get(:MATCH_PROFILES)
|
|
399
185
|
|
|
400
186
|
class << self
|
|
401
|
-
#
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
# @param match_profile [Symbol, nil] Profile name
|
|
405
|
-
# @param match [Hash, nil] Explicit options per dimension
|
|
406
|
-
# @param preprocessing [Symbol, nil] Preprocessing option
|
|
407
|
-
# @param global_profile [Symbol, nil] Global configured profile
|
|
408
|
-
# @param global_options [Hash, nil] Global configured options
|
|
409
|
-
# @return [Hash] Resolved options for all dimensions
|
|
410
|
-
def resolve(
|
|
411
|
-
format:,
|
|
412
|
-
match_profile: nil,
|
|
413
|
-
match: nil,
|
|
414
|
-
preprocessing: nil,
|
|
415
|
-
global_profile: nil,
|
|
416
|
-
global_options: nil
|
|
417
|
-
)
|
|
418
|
-
# Start with format-specific defaults
|
|
419
|
-
options = FORMAT_DEFAULTS[:json].dup
|
|
420
|
-
|
|
421
|
-
# Apply global profile if specified
|
|
422
|
-
if global_profile
|
|
423
|
-
profile_opts = get_profile_options(global_profile)
|
|
424
|
-
options.merge!(profile_opts)
|
|
425
|
-
end
|
|
426
|
-
|
|
427
|
-
# Apply global options if specified
|
|
428
|
-
if global_options
|
|
429
|
-
validate_match_options!(global_options)
|
|
430
|
-
options.merge!(global_options)
|
|
431
|
-
end
|
|
432
|
-
|
|
433
|
-
# Apply per-call profile if specified (overrides global)
|
|
434
|
-
if match_profile
|
|
435
|
-
profile_opts = get_profile_options(match_profile)
|
|
436
|
-
options.merge!(profile_opts)
|
|
437
|
-
end
|
|
438
|
-
|
|
439
|
-
# Apply per-call preprocessing if specified (overrides profile)
|
|
440
|
-
if preprocessing
|
|
441
|
-
validate_preprocessing!(preprocessing)
|
|
442
|
-
options[:preprocessing] = preprocessing
|
|
443
|
-
end
|
|
444
|
-
|
|
445
|
-
# Apply per-call explicit options if specified (highest priority)
|
|
446
|
-
if match
|
|
447
|
-
validate_match_options!(match)
|
|
448
|
-
options.merge!(match)
|
|
449
|
-
end
|
|
450
|
-
|
|
451
|
-
options
|
|
187
|
+
# Delegate to JsonResolver
|
|
188
|
+
def resolve(**kwargs)
|
|
189
|
+
MatchOptions::JsonResolver.resolve(**kwargs)
|
|
452
190
|
end
|
|
453
191
|
|
|
454
|
-
#
|
|
455
|
-
#
|
|
456
|
-
# @param profile [Symbol] Profile name
|
|
457
|
-
# @return [Hash] Profile options
|
|
458
|
-
# @raise [Canon::Error] If profile is unknown
|
|
192
|
+
# Delegate to JsonResolver
|
|
459
193
|
def get_profile_options(profile)
|
|
460
|
-
|
|
461
|
-
raise Canon::Error,
|
|
462
|
-
"Unknown match profile: #{profile}. " \
|
|
463
|
-
"Valid profiles: #{MATCH_PROFILES.keys.join(', ')}"
|
|
464
|
-
end
|
|
465
|
-
MATCH_PROFILES[profile].dup
|
|
194
|
+
MatchOptions::JsonResolver.get_profile_options(profile)
|
|
466
195
|
end
|
|
467
196
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
#
|
|
471
|
-
def
|
|
472
|
-
|
|
473
|
-
raise Canon::Error,
|
|
474
|
-
"Unknown preprocessing option: #{preprocessing}. " \
|
|
475
|
-
"Valid options: #{MatchOptions::PREPROCESSING_OPTIONS.join(', ')}"
|
|
476
|
-
end
|
|
197
|
+
# Get valid match dimensions for JSON
|
|
198
|
+
#
|
|
199
|
+
# @return [Array<Symbol>] Valid dimensions
|
|
200
|
+
def match_dimensions
|
|
201
|
+
MatchOptions::JsonResolver.match_dimensions
|
|
477
202
|
end
|
|
478
203
|
|
|
479
|
-
#
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
unless MATCH_DIMENSIONS.include?(dimension)
|
|
486
|
-
raise Canon::Error,
|
|
487
|
-
"Unknown match dimension: #{dimension}. " \
|
|
488
|
-
"Valid dimensions: #{MATCH_DIMENSIONS.join(', ')}"
|
|
489
|
-
end
|
|
490
|
-
|
|
491
|
-
unless MatchOptions::MATCH_BEHAVIORS.include?(behavior)
|
|
492
|
-
raise Canon::Error,
|
|
493
|
-
"Unknown match behavior: #{behavior} for #{dimension}. " \
|
|
494
|
-
"Valid behaviors: #{MatchOptions::MATCH_BEHAVIORS.join(', ')}"
|
|
495
|
-
end
|
|
496
|
-
end
|
|
204
|
+
# Get format-specific default options
|
|
205
|
+
#
|
|
206
|
+
# @param format [Symbol] Format type
|
|
207
|
+
# @return [Hash] Default options for the format
|
|
208
|
+
def format_defaults(format)
|
|
209
|
+
MatchOptions::JsonResolver.format_defaults(format)
|
|
497
210
|
end
|
|
498
211
|
end
|
|
499
212
|
end
|
|
@@ -508,144 +221,36 @@ module Canon
|
|
|
508
221
|
comments
|
|
509
222
|
].freeze
|
|
510
223
|
|
|
511
|
-
#
|
|
512
|
-
FORMAT_DEFAULTS =
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
structural_whitespace: :ignore,
|
|
517
|
-
key_order: :strict,
|
|
518
|
-
comments: :ignore,
|
|
519
|
-
},
|
|
520
|
-
}.freeze
|
|
521
|
-
|
|
522
|
-
# Predefined match profiles for YAML
|
|
523
|
-
MATCH_PROFILES = {
|
|
524
|
-
# Strict: Match exactly
|
|
525
|
-
strict: {
|
|
526
|
-
preprocessing: :none,
|
|
527
|
-
text_content: :strict,
|
|
528
|
-
structural_whitespace: :strict,
|
|
529
|
-
key_order: :strict,
|
|
530
|
-
comments: :strict,
|
|
531
|
-
},
|
|
532
|
-
|
|
533
|
-
# Spec-friendly: Formatting and order don't matter
|
|
534
|
-
spec_friendly: {
|
|
535
|
-
preprocessing: :normalize,
|
|
536
|
-
text_content: :strict,
|
|
537
|
-
structural_whitespace: :ignore,
|
|
538
|
-
key_order: :ignore,
|
|
539
|
-
comments: :ignore,
|
|
540
|
-
},
|
|
541
|
-
|
|
542
|
-
# Content-only: Only values matter
|
|
543
|
-
content_only: {
|
|
544
|
-
preprocessing: :normalize,
|
|
545
|
-
text_content: :normalize,
|
|
546
|
-
structural_whitespace: :ignore,
|
|
547
|
-
key_order: :ignore,
|
|
548
|
-
comments: :ignore,
|
|
549
|
-
},
|
|
550
|
-
}.freeze
|
|
224
|
+
# Expose FORMAT_DEFAULTS from YamlResolver (for backward compatibility)
|
|
225
|
+
FORMAT_DEFAULTS = MatchOptions::YamlResolver.const_get(:FORMAT_DEFAULTS)
|
|
226
|
+
|
|
227
|
+
# Expose MATCH_PROFILES from YamlResolver (for backward compatibility)
|
|
228
|
+
MATCH_PROFILES = MatchOptions::YamlResolver.const_get(:MATCH_PROFILES)
|
|
551
229
|
|
|
552
230
|
class << self
|
|
553
|
-
#
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
# @param match_profile [Symbol, nil] Profile name
|
|
557
|
-
# @param match [Hash, nil] Explicit options per dimension
|
|
558
|
-
# @param preprocessing [Symbol, nil] Preprocessing option
|
|
559
|
-
# @param global_profile [Symbol, nil] Global configured profile
|
|
560
|
-
# @param global_options [Hash, nil] Global configured options
|
|
561
|
-
# @return [Hash] Resolved options for all dimensions
|
|
562
|
-
def resolve(
|
|
563
|
-
format:,
|
|
564
|
-
match_profile: nil,
|
|
565
|
-
match: nil,
|
|
566
|
-
preprocessing: nil,
|
|
567
|
-
global_profile: nil,
|
|
568
|
-
global_options: nil
|
|
569
|
-
)
|
|
570
|
-
# Start with format-specific defaults
|
|
571
|
-
options = FORMAT_DEFAULTS[:yaml].dup
|
|
572
|
-
|
|
573
|
-
# Apply global profile if specified
|
|
574
|
-
if global_profile
|
|
575
|
-
profile_opts = get_profile_options(global_profile)
|
|
576
|
-
options.merge!(profile_opts)
|
|
577
|
-
end
|
|
578
|
-
|
|
579
|
-
# Apply global options if specified
|
|
580
|
-
if global_options
|
|
581
|
-
validate_match_options!(global_options)
|
|
582
|
-
options.merge!(global_options)
|
|
583
|
-
end
|
|
584
|
-
|
|
585
|
-
# Apply per-call profile if specified (overrides global)
|
|
586
|
-
if match_profile
|
|
587
|
-
profile_opts = get_profile_options(match_profile)
|
|
588
|
-
options.merge!(profile_opts)
|
|
589
|
-
end
|
|
590
|
-
|
|
591
|
-
# Apply per-call preprocessing if specified (overrides profile)
|
|
592
|
-
if preprocessing
|
|
593
|
-
validate_preprocessing!(preprocessing)
|
|
594
|
-
options[:preprocessing] = preprocessing
|
|
595
|
-
end
|
|
596
|
-
|
|
597
|
-
# Apply per-call explicit options if specified (highest priority)
|
|
598
|
-
if match
|
|
599
|
-
validate_match_options!(match)
|
|
600
|
-
options.merge!(match)
|
|
601
|
-
end
|
|
602
|
-
|
|
603
|
-
options
|
|
231
|
+
# Delegate to YamlResolver
|
|
232
|
+
def resolve(**kwargs)
|
|
233
|
+
MatchOptions::YamlResolver.resolve(**kwargs)
|
|
604
234
|
end
|
|
605
235
|
|
|
606
|
-
#
|
|
607
|
-
#
|
|
608
|
-
# @param profile [Symbol] Profile name
|
|
609
|
-
# @return [Hash] Profile options
|
|
610
|
-
# @raise [Canon::Error] If profile is unknown
|
|
236
|
+
# Delegate to YamlResolver
|
|
611
237
|
def get_profile_options(profile)
|
|
612
|
-
|
|
613
|
-
raise Canon::Error,
|
|
614
|
-
"Unknown match profile: #{profile}. " \
|
|
615
|
-
"Valid profiles: #{MATCH_PROFILES.keys.join(', ')}"
|
|
616
|
-
end
|
|
617
|
-
MATCH_PROFILES[profile].dup
|
|
238
|
+
MatchOptions::YamlResolver.get_profile_options(profile)
|
|
618
239
|
end
|
|
619
240
|
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
#
|
|
623
|
-
def
|
|
624
|
-
|
|
625
|
-
raise Canon::Error,
|
|
626
|
-
"Unknown preprocessing option: #{preprocessing}. " \
|
|
627
|
-
"Valid options: #{MatchOptions::PREPROCESSING_OPTIONS.join(', ')}"
|
|
628
|
-
end
|
|
241
|
+
# Get valid match dimensions for YAML
|
|
242
|
+
#
|
|
243
|
+
# @return [Array<Symbol>] Valid dimensions
|
|
244
|
+
def match_dimensions
|
|
245
|
+
MatchOptions::YamlResolver.match_dimensions
|
|
629
246
|
end
|
|
630
247
|
|
|
631
|
-
#
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
unless MATCH_DIMENSIONS.include?(dimension)
|
|
638
|
-
raise Canon::Error,
|
|
639
|
-
"Unknown match dimension: #{dimension}. " \
|
|
640
|
-
"Valid dimensions: #{MATCH_DIMENSIONS.join(', ')}"
|
|
641
|
-
end
|
|
642
|
-
|
|
643
|
-
unless MatchOptions::MATCH_BEHAVIORS.include?(behavior)
|
|
644
|
-
raise Canon::Error,
|
|
645
|
-
"Unknown match behavior: #{behavior} for #{dimension}. " \
|
|
646
|
-
"Valid behaviors: #{MatchOptions::MATCH_BEHAVIORS.join(', ')}"
|
|
647
|
-
end
|
|
648
|
-
end
|
|
248
|
+
# Get format-specific default options
|
|
249
|
+
#
|
|
250
|
+
# @param format [Symbol] Format type
|
|
251
|
+
# @return [Hash] Default options for the format
|
|
252
|
+
def format_defaults(format)
|
|
253
|
+
MatchOptions::YamlResolver.format_defaults(format)
|
|
649
254
|
end
|
|
650
255
|
end
|
|
651
256
|
end
|