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
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Canon
|
|
4
|
+
module Comparison
|
|
5
|
+
module MatchOptions
|
|
6
|
+
# Base class for match option resolvers
|
|
7
|
+
# Provides common resolve logic with format-specific customization
|
|
8
|
+
class BaseResolver
|
|
9
|
+
class << self
|
|
10
|
+
# Resolve match options with precedence handling
|
|
11
|
+
#
|
|
12
|
+
# Precedence order (highest to lowest):
|
|
13
|
+
# 1. Explicit match parameter
|
|
14
|
+
# 2. Profile from match_profile parameter
|
|
15
|
+
# 3. Global configuration
|
|
16
|
+
# 4. Format-specific defaults
|
|
17
|
+
#
|
|
18
|
+
# @param format [Symbol] Format type
|
|
19
|
+
# @param match_profile [Symbol, nil] Profile name
|
|
20
|
+
# @param match [Hash, nil] Explicit options per dimension
|
|
21
|
+
# @param preprocessing [Symbol, nil] Preprocessing option
|
|
22
|
+
# @param global_profile [Symbol, nil] Global configured profile
|
|
23
|
+
# @param global_options [Hash, nil] Global configured options
|
|
24
|
+
# @return [Hash] Resolved options for all dimensions
|
|
25
|
+
def resolve(format:, match_profile: nil, match: nil, preprocessing: nil,
|
|
26
|
+
global_profile: nil, global_options: nil)
|
|
27
|
+
# Start with format-specific defaults
|
|
28
|
+
options = format_defaults(format).dup
|
|
29
|
+
|
|
30
|
+
# Store format for later use (e.g., WhitespaceSensitivity needs it)
|
|
31
|
+
options[:format] = format
|
|
32
|
+
|
|
33
|
+
# Apply global profile if specified
|
|
34
|
+
if global_profile
|
|
35
|
+
profile_opts = get_profile_options(global_profile)
|
|
36
|
+
options.merge!(profile_opts)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Apply global options if specified
|
|
40
|
+
if global_options
|
|
41
|
+
validate_match_options!(global_options)
|
|
42
|
+
options.merge!(global_options)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Apply per-call profile if specified (overrides global)
|
|
46
|
+
if match_profile
|
|
47
|
+
profile_opts = get_profile_options(match_profile)
|
|
48
|
+
options.merge!(profile_opts)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Apply per-call preprocessing if specified (overrides profile)
|
|
52
|
+
if preprocessing
|
|
53
|
+
validate_preprocessing!(preprocessing)
|
|
54
|
+
options[:preprocessing] = preprocessing
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Apply per-call explicit options if specified (highest priority)
|
|
58
|
+
if match
|
|
59
|
+
validate_match_options!(match)
|
|
60
|
+
options.merge!(match)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
options
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Get format-specific default options
|
|
67
|
+
# Subclasses should override this
|
|
68
|
+
#
|
|
69
|
+
# @param format [Symbol] Format type
|
|
70
|
+
# @return [Hash] Default options for the format
|
|
71
|
+
def format_defaults(format)
|
|
72
|
+
raise NotImplementedError,
|
|
73
|
+
"#{self.class} must implement #format_defaults"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Get options for a named profile
|
|
77
|
+
# Subclasses should override this
|
|
78
|
+
#
|
|
79
|
+
# @param profile [Symbol] Profile name
|
|
80
|
+
# @return [Hash] Profile options
|
|
81
|
+
# @raise [Canon::Error] If profile is unknown
|
|
82
|
+
def get_profile_options(profile)
|
|
83
|
+
raise NotImplementedError,
|
|
84
|
+
"#{self.class} must implement #get_profile_options"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Get valid match dimensions for this format
|
|
88
|
+
# Subclasses should override this
|
|
89
|
+
#
|
|
90
|
+
# @return [Array<Symbol>] Valid dimensions
|
|
91
|
+
def match_dimensions
|
|
92
|
+
raise NotImplementedError,
|
|
93
|
+
"#{self.class} must implement #match_dimensions"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
protected
|
|
97
|
+
|
|
98
|
+
# Validate preprocessing option
|
|
99
|
+
#
|
|
100
|
+
# @param preprocessing [Symbol] Preprocessing option
|
|
101
|
+
# @raise [Canon::Error] If invalid
|
|
102
|
+
def validate_preprocessing!(preprocessing)
|
|
103
|
+
unless MatchOptions::PREPROCESSING_OPTIONS.include?(preprocessing)
|
|
104
|
+
raise Canon::Error,
|
|
105
|
+
"Unknown preprocessing option: #{preprocessing}. " \
|
|
106
|
+
"Valid options: #{MatchOptions::PREPROCESSING_OPTIONS.join(', ')}"
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Validate match options
|
|
111
|
+
#
|
|
112
|
+
# @param match_options [Hash] Options to validate
|
|
113
|
+
# @raise [Canon::Error] If invalid dimension or behavior
|
|
114
|
+
def validate_match_options!(match_options)
|
|
115
|
+
# Special options that don't need validation as dimensions
|
|
116
|
+
special_options = %i[
|
|
117
|
+
format
|
|
118
|
+
preprocessing
|
|
119
|
+
semantic_diff
|
|
120
|
+
similarity_threshold
|
|
121
|
+
hash_matching
|
|
122
|
+
similarity_matching
|
|
123
|
+
propagation
|
|
124
|
+
whitespace_sensitive_elements
|
|
125
|
+
whitespace_insensitive_elements
|
|
126
|
+
respect_xml_space
|
|
127
|
+
]
|
|
128
|
+
|
|
129
|
+
match_options.each do |dimension, behavior|
|
|
130
|
+
# Skip special options (validated elsewhere or passed through)
|
|
131
|
+
next if special_options.include?(dimension)
|
|
132
|
+
|
|
133
|
+
unless match_dimensions.include?(dimension)
|
|
134
|
+
raise Canon::Error,
|
|
135
|
+
"Unknown match dimension: #{dimension}. " \
|
|
136
|
+
"Valid dimensions: #{match_dimensions.join(', ')}"
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
unless MatchOptions::MATCH_BEHAVIORS.include?(behavior)
|
|
140
|
+
raise Canon::Error,
|
|
141
|
+
"Unknown match behavior: #{behavior} for #{dimension}. " \
|
|
142
|
+
"Valid behaviors: #{MatchOptions::MATCH_BEHAVIORS.join(', ')}"
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base_resolver"
|
|
4
|
+
|
|
5
|
+
module Canon
|
|
6
|
+
module Comparison
|
|
7
|
+
module MatchOptions
|
|
8
|
+
# JSON-specific match options resolver
|
|
9
|
+
class JsonResolver < BaseResolver
|
|
10
|
+
# Format defaults for JSON
|
|
11
|
+
FORMAT_DEFAULTS = {
|
|
12
|
+
json: {
|
|
13
|
+
preprocessing: :none,
|
|
14
|
+
text_content: :strict,
|
|
15
|
+
structural_whitespace: :ignore,
|
|
16
|
+
key_order: :ignore,
|
|
17
|
+
},
|
|
18
|
+
}.freeze
|
|
19
|
+
|
|
20
|
+
# Predefined match profiles for JSON
|
|
21
|
+
MATCH_PROFILES = {
|
|
22
|
+
# Strict: Match exactly
|
|
23
|
+
strict: {
|
|
24
|
+
preprocessing: :none,
|
|
25
|
+
text_content: :strict,
|
|
26
|
+
structural_whitespace: :strict,
|
|
27
|
+
key_order: :strict,
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
# Spec-friendly: Formatting and order don't matter
|
|
31
|
+
spec_friendly: {
|
|
32
|
+
preprocessing: :normalize,
|
|
33
|
+
text_content: :strict,
|
|
34
|
+
structural_whitespace: :ignore,
|
|
35
|
+
key_order: :ignore,
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
# Content-only: Only values matter
|
|
39
|
+
content_only: {
|
|
40
|
+
preprocessing: :normalize,
|
|
41
|
+
text_content: :normalize,
|
|
42
|
+
structural_whitespace: :ignore,
|
|
43
|
+
key_order: :ignore,
|
|
44
|
+
},
|
|
45
|
+
}.freeze
|
|
46
|
+
|
|
47
|
+
class << self
|
|
48
|
+
# Matching dimensions for JSON (collectively exhaustive)
|
|
49
|
+
def match_dimensions
|
|
50
|
+
%i[
|
|
51
|
+
text_content
|
|
52
|
+
structural_whitespace
|
|
53
|
+
key_order
|
|
54
|
+
].freeze
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Get format-specific default options
|
|
58
|
+
#
|
|
59
|
+
# @param format [Symbol] Format type (:json)
|
|
60
|
+
# @return [Hash] Default options for the format
|
|
61
|
+
def format_defaults(format)
|
|
62
|
+
FORMAT_DEFAULTS[format]&.dup || FORMAT_DEFAULTS[:json].dup
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Get options for a named profile
|
|
66
|
+
#
|
|
67
|
+
# @param profile [Symbol] Profile name
|
|
68
|
+
# @return [Hash] Profile options
|
|
69
|
+
# @raise [Canon::Error] If profile is unknown
|
|
70
|
+
def get_profile_options(profile)
|
|
71
|
+
unless MATCH_PROFILES.key?(profile)
|
|
72
|
+
raise Canon::Error,
|
|
73
|
+
"Unknown match profile: #{profile}. " \
|
|
74
|
+
"Valid profiles: #{MATCH_PROFILES.keys.join(', ')}"
|
|
75
|
+
end
|
|
76
|
+
MATCH_PROFILES[profile].dup
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base_resolver"
|
|
4
|
+
|
|
5
|
+
module Canon
|
|
6
|
+
module Comparison
|
|
7
|
+
module MatchOptions
|
|
8
|
+
# XML/HTML-specific match options resolver
|
|
9
|
+
class XmlResolver < BaseResolver
|
|
10
|
+
# Format-specific defaults for XML/HTML
|
|
11
|
+
FORMAT_DEFAULTS = {
|
|
12
|
+
html: {
|
|
13
|
+
preprocessing: :rendered,
|
|
14
|
+
text_content: :normalize,
|
|
15
|
+
structural_whitespace: :normalize,
|
|
16
|
+
attribute_presence: :strict,
|
|
17
|
+
attribute_order: :ignore,
|
|
18
|
+
attribute_values: :strict,
|
|
19
|
+
element_position: :ignore,
|
|
20
|
+
comments: :ignore,
|
|
21
|
+
},
|
|
22
|
+
xml: {
|
|
23
|
+
preprocessing: :none,
|
|
24
|
+
text_content: :strict,
|
|
25
|
+
structural_whitespace: :strict,
|
|
26
|
+
attribute_presence: :strict,
|
|
27
|
+
attribute_order: :ignore,
|
|
28
|
+
attribute_values: :strict,
|
|
29
|
+
element_position: :strict,
|
|
30
|
+
comments: :strict,
|
|
31
|
+
},
|
|
32
|
+
}.freeze
|
|
33
|
+
|
|
34
|
+
# Predefined match profiles for XML/HTML
|
|
35
|
+
MATCH_PROFILES = {
|
|
36
|
+
# Strict: Match exactly as written in source (XML default)
|
|
37
|
+
strict: {
|
|
38
|
+
preprocessing: :none,
|
|
39
|
+
text_content: :strict,
|
|
40
|
+
structural_whitespace: :strict,
|
|
41
|
+
attribute_presence: :strict,
|
|
42
|
+
attribute_order: :strict,
|
|
43
|
+
attribute_values: :strict,
|
|
44
|
+
element_position: :strict,
|
|
45
|
+
comments: :strict,
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
# Rendered: Match rendered output (HTML default)
|
|
49
|
+
# Mimics CSS whitespace collapsing
|
|
50
|
+
rendered: {
|
|
51
|
+
preprocessing: :none,
|
|
52
|
+
text_content: :normalize,
|
|
53
|
+
structural_whitespace: :normalize,
|
|
54
|
+
attribute_presence: :strict,
|
|
55
|
+
attribute_order: :strict,
|
|
56
|
+
attribute_values: :strict,
|
|
57
|
+
element_position: :strict,
|
|
58
|
+
comments: :ignore,
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
# HTML4: Match HTML4 rendered output
|
|
62
|
+
# HTML4 rendering normalizes attribute whitespace
|
|
63
|
+
html4: {
|
|
64
|
+
preprocessing: :rendered,
|
|
65
|
+
text_content: :normalize,
|
|
66
|
+
structural_whitespace: :normalize,
|
|
67
|
+
attribute_presence: :strict,
|
|
68
|
+
attribute_order: :strict,
|
|
69
|
+
attribute_values: :normalize,
|
|
70
|
+
element_position: :ignore,
|
|
71
|
+
comments: :ignore,
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
# HTML5: Match HTML5 rendered output (same as rendered)
|
|
75
|
+
html5: {
|
|
76
|
+
preprocessing: :rendered,
|
|
77
|
+
text_content: :normalize,
|
|
78
|
+
structural_whitespace: :normalize,
|
|
79
|
+
attribute_presence: :strict,
|
|
80
|
+
attribute_order: :strict,
|
|
81
|
+
attribute_values: :strict,
|
|
82
|
+
element_position: :ignore,
|
|
83
|
+
comments: :ignore,
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
# Spec-friendly: Formatting doesn't matter
|
|
87
|
+
# Uses :rendered preprocessing for HTML which normalizes via to_html
|
|
88
|
+
spec_friendly: {
|
|
89
|
+
preprocessing: :rendered,
|
|
90
|
+
text_content: :normalize,
|
|
91
|
+
structural_whitespace: :ignore,
|
|
92
|
+
attribute_presence: :strict,
|
|
93
|
+
attribute_order: :ignore,
|
|
94
|
+
attribute_values: :normalize,
|
|
95
|
+
element_position: :ignore,
|
|
96
|
+
comments: :ignore,
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
# Content-only: Only content matters
|
|
100
|
+
content_only: {
|
|
101
|
+
preprocessing: :c14n,
|
|
102
|
+
text_content: :normalize,
|
|
103
|
+
structural_whitespace: :ignore,
|
|
104
|
+
attribute_presence: :strict,
|
|
105
|
+
attribute_order: :ignore,
|
|
106
|
+
attribute_values: :normalize,
|
|
107
|
+
element_position: :ignore,
|
|
108
|
+
comments: :ignore,
|
|
109
|
+
},
|
|
110
|
+
}.freeze
|
|
111
|
+
|
|
112
|
+
class << self
|
|
113
|
+
# Matching dimensions for XML/HTML (collectively exhaustive)
|
|
114
|
+
def match_dimensions
|
|
115
|
+
%i[
|
|
116
|
+
text_content
|
|
117
|
+
structural_whitespace
|
|
118
|
+
attribute_presence
|
|
119
|
+
attribute_order
|
|
120
|
+
attribute_values
|
|
121
|
+
element_position
|
|
122
|
+
comments
|
|
123
|
+
].freeze
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Get format-specific default options
|
|
127
|
+
#
|
|
128
|
+
# @param format [Symbol] Format type (:xml or :html)
|
|
129
|
+
# @return [Hash] Default options for the format
|
|
130
|
+
def format_defaults(format)
|
|
131
|
+
FORMAT_DEFAULTS[format]&.dup || FORMAT_DEFAULTS[:xml].dup
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Get options for a named profile
|
|
135
|
+
#
|
|
136
|
+
# @param profile [Symbol] Profile name
|
|
137
|
+
# @return [Hash] Profile options
|
|
138
|
+
# @raise [Canon::Error] If profile is unknown
|
|
139
|
+
def get_profile_options(profile)
|
|
140
|
+
unless MATCH_PROFILES.key?(profile)
|
|
141
|
+
raise Canon::Error,
|
|
142
|
+
"Unknown match profile: #{profile}. " \
|
|
143
|
+
"Valid profiles: #{MATCH_PROFILES.keys.join(', ')}"
|
|
144
|
+
end
|
|
145
|
+
MATCH_PROFILES[profile].dup
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base_resolver"
|
|
4
|
+
|
|
5
|
+
module Canon
|
|
6
|
+
module Comparison
|
|
7
|
+
module MatchOptions
|
|
8
|
+
# YAML-specific match options resolver
|
|
9
|
+
class YamlResolver < BaseResolver
|
|
10
|
+
# Format defaults for YAML
|
|
11
|
+
FORMAT_DEFAULTS = {
|
|
12
|
+
yaml: {
|
|
13
|
+
preprocessing: :none,
|
|
14
|
+
text_content: :strict,
|
|
15
|
+
structural_whitespace: :ignore,
|
|
16
|
+
key_order: :ignore,
|
|
17
|
+
comments: :ignore,
|
|
18
|
+
},
|
|
19
|
+
}.freeze
|
|
20
|
+
|
|
21
|
+
# Predefined match profiles for YAML
|
|
22
|
+
MATCH_PROFILES = {
|
|
23
|
+
# Strict: Match exactly
|
|
24
|
+
strict: {
|
|
25
|
+
preprocessing: :none,
|
|
26
|
+
text_content: :strict,
|
|
27
|
+
structural_whitespace: :strict,
|
|
28
|
+
key_order: :strict,
|
|
29
|
+
comments: :strict,
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
# Spec-friendly: Formatting and order don't matter
|
|
33
|
+
spec_friendly: {
|
|
34
|
+
preprocessing: :normalize,
|
|
35
|
+
text_content: :strict,
|
|
36
|
+
structural_whitespace: :ignore,
|
|
37
|
+
key_order: :ignore,
|
|
38
|
+
comments: :ignore,
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
# Content-only: Only values matter
|
|
42
|
+
content_only: {
|
|
43
|
+
preprocessing: :normalize,
|
|
44
|
+
text_content: :normalize,
|
|
45
|
+
structural_whitespace: :ignore,
|
|
46
|
+
key_order: :ignore,
|
|
47
|
+
comments: :ignore,
|
|
48
|
+
},
|
|
49
|
+
}.freeze
|
|
50
|
+
|
|
51
|
+
class << self
|
|
52
|
+
# Matching dimensions for YAML (collectively exhaustive)
|
|
53
|
+
def match_dimensions
|
|
54
|
+
%i[
|
|
55
|
+
text_content
|
|
56
|
+
structural_whitespace
|
|
57
|
+
key_order
|
|
58
|
+
comments
|
|
59
|
+
].freeze
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Get format-specific default options
|
|
63
|
+
#
|
|
64
|
+
# @param format [Symbol] Format type (:yaml)
|
|
65
|
+
# @return [Hash] Default options for the format
|
|
66
|
+
def format_defaults(format)
|
|
67
|
+
FORMAT_DEFAULTS[format]&.dup || FORMAT_DEFAULTS[:yaml].dup
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Get options for a named profile
|
|
71
|
+
#
|
|
72
|
+
# @param profile [Symbol] Profile name
|
|
73
|
+
# @return [Hash] Profile options
|
|
74
|
+
# @raise [Canon::Error] If profile is unknown
|
|
75
|
+
def get_profile_options(profile)
|
|
76
|
+
unless MATCH_PROFILES.key?(profile)
|
|
77
|
+
raise Canon::Error,
|
|
78
|
+
"Unknown match profile: #{profile}. " \
|
|
79
|
+
"Valid profiles: #{MATCH_PROFILES.keys.join(', ')}"
|
|
80
|
+
end
|
|
81
|
+
MATCH_PROFILES[profile].dup
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|