canon 0.2.11 → 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 (148) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +12 -22
  3. data/Rakefile +5 -2
  4. data/lib/canon/cache.rb +3 -1
  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 +18 -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/pipeline.rb +269 -0
  28. data/lib/canon/comparison/profile_definition.rb +0 -2
  29. data/lib/canon/comparison/ruby_object_comparator.rb +1 -1
  30. data/lib/canon/comparison/strategies/match_strategy_factory.rb +9 -58
  31. data/lib/canon/comparison/strategies/semantic_tree_match_strategy.rb +4 -11
  32. data/lib/canon/comparison/strategies.rb +16 -0
  33. data/lib/canon/comparison/xml_comparator/attribute_comparator.rb +0 -3
  34. data/lib/canon/comparison/xml_comparator/attribute_filter.rb +0 -3
  35. data/lib/canon/comparison/xml_comparator/child_comparison.rb +0 -6
  36. data/lib/canon/comparison/xml_comparator/namespace_comparator.rb +1 -6
  37. data/lib/canon/comparison/xml_comparator/node_parser.rb +0 -4
  38. data/lib/canon/comparison/xml_comparator.rb +4 -492
  39. data/lib/canon/comparison/xml_comparator_helpers.rb +21 -0
  40. data/lib/canon/comparison/xml_node_comparison.rb +4 -119
  41. data/lib/canon/comparison/yaml_comparator.rb +0 -3
  42. data/lib/canon/comparison.rb +143 -266
  43. data/lib/canon/config/config_dsl.rb +159 -0
  44. data/lib/canon/config/env_provider.rb +0 -3
  45. data/lib/canon/config/env_schema.rb +48 -58
  46. data/lib/canon/config/profile_loader.rb +0 -1
  47. data/lib/canon/config.rb +116 -468
  48. data/lib/canon/diff/diff_block_builder.rb +0 -2
  49. data/lib/canon/diff/diff_classifier.rb +0 -5
  50. data/lib/canon/diff/diff_context.rb +0 -2
  51. data/lib/canon/diff/diff_context_builder.rb +0 -2
  52. data/lib/canon/diff/diff_line_builder.rb +0 -3
  53. data/lib/canon/diff/diff_node_enricher.rb +0 -4
  54. data/lib/canon/diff/diff_node_mapper.rb +0 -4
  55. data/lib/canon/diff/diff_report_builder.rb +0 -4
  56. data/lib/canon/diff/formatting_detector.rb +0 -1
  57. data/lib/canon/diff/node_serializer.rb +0 -7
  58. data/lib/canon/diff.rb +39 -0
  59. data/lib/canon/diff_formatter/by_line/base_formatter.rb +4 -17
  60. data/lib/canon/diff_formatter/by_line/html_formatter.rb +7 -19
  61. data/lib/canon/diff_formatter/by_line/json_formatter.rb +0 -3
  62. data/lib/canon/diff_formatter/by_line/simple_formatter.rb +0 -3
  63. data/lib/canon/diff_formatter/by_line/xml_formatter.rb +7 -26
  64. data/lib/canon/diff_formatter/by_line/yaml_formatter.rb +0 -3
  65. data/lib/canon/diff_formatter/by_object/base_formatter.rb +8 -15
  66. data/lib/canon/diff_formatter/by_object/json_formatter.rb +0 -2
  67. data/lib/canon/diff_formatter/by_object/xml_formatter.rb +0 -2
  68. data/lib/canon/diff_formatter/by_object/yaml_formatter.rb +0 -2
  69. data/lib/canon/diff_formatter/debug_output.rb +0 -2
  70. data/lib/canon/diff_formatter/diff_detail_formatter/dimension_formatter.rb +24 -58
  71. data/lib/canon/diff_formatter/diff_detail_formatter/location_extractor.rb +0 -2
  72. data/lib/canon/diff_formatter/diff_detail_formatter/node_utils.rb +1 -2
  73. data/lib/canon/diff_formatter/diff_detail_formatter/text_utils.rb +1 -7
  74. data/lib/canon/diff_formatter/diff_detail_formatter.rb +0 -7
  75. data/lib/canon/diff_formatter/diff_detail_formatter_helpers.rb +23 -0
  76. data/lib/canon/diff_formatter.rb +11 -9
  77. data/lib/canon/formatters/html4_formatter.rb +0 -2
  78. data/lib/canon/formatters/html5_formatter.rb +0 -2
  79. data/lib/canon/formatters/html_formatter.rb +0 -3
  80. data/lib/canon/formatters/json_formatter.rb +0 -1
  81. data/lib/canon/formatters/xml_formatter.rb +0 -4
  82. data/lib/canon/formatters/yaml_formatter.rb +0 -1
  83. data/lib/canon/formatters.rb +16 -0
  84. data/lib/canon/html/data_model.rb +0 -10
  85. data/lib/canon/html.rb +4 -3
  86. data/lib/canon/options/cli_generator.rb +0 -2
  87. data/lib/canon/options/registry.rb +0 -2
  88. data/lib/canon/options.rb +9 -0
  89. data/lib/canon/pretty_printer/html.rb +0 -1
  90. data/lib/canon/pretty_printer/xml_normalized.rb +0 -2
  91. data/lib/canon/pretty_printer.rb +12 -0
  92. data/lib/canon/tree_diff/adapters/html_adapter.rb +1 -1
  93. data/lib/canon/tree_diff/adapters.rb +14 -0
  94. data/lib/canon/tree_diff/core/attribute_comparator.rb +0 -6
  95. data/lib/canon/tree_diff/core/node_signature.rb +1 -1
  96. data/lib/canon/tree_diff/core/tree_node.rb +12 -5
  97. data/lib/canon/tree_diff/core.rb +17 -0
  98. data/lib/canon/tree_diff/matchers/hash_matcher.rb +0 -7
  99. data/lib/canon/tree_diff/matchers/similarity_matcher.rb +1 -5
  100. data/lib/canon/tree_diff/matchers/structural_propagator.rb +1 -5
  101. data/lib/canon/tree_diff/matchers.rb +15 -0
  102. data/lib/canon/tree_diff/operation_converter.rb +0 -8
  103. data/lib/canon/tree_diff/operation_converter_helpers/metadata_enricher.rb +2 -12
  104. data/lib/canon/tree_diff/operation_converter_helpers/post_processor.rb +13 -7
  105. data/lib/canon/tree_diff/operation_converter_helpers/reason_builder.rb +2 -2
  106. data/lib/canon/tree_diff/operation_converter_helpers/update_change_handler.rb +4 -6
  107. data/lib/canon/tree_diff/operation_converter_helpers.rb +18 -0
  108. data/lib/canon/tree_diff/operations/operation_detector.rb +2 -5
  109. data/lib/canon/tree_diff/operations.rb +13 -0
  110. data/lib/canon/tree_diff.rb +26 -27
  111. data/lib/canon/validators/base_validator.rb +0 -2
  112. data/lib/canon/validators/html_validator.rb +0 -1
  113. data/lib/canon/validators/json_validator.rb +0 -1
  114. data/lib/canon/validators/xml_validator.rb +0 -1
  115. data/lib/canon/validators/yaml_validator.rb +0 -1
  116. data/lib/canon/validators.rb +12 -0
  117. data/lib/canon/version.rb +1 -1
  118. data/lib/canon/xml/c14n.rb +0 -4
  119. data/lib/canon/xml/data_model.rb +0 -10
  120. data/lib/canon/xml/line_range_mapper.rb +0 -2
  121. data/lib/canon/xml/nodes/attribute_node.rb +0 -2
  122. data/lib/canon/xml/nodes/comment_node.rb +0 -2
  123. data/lib/canon/xml/nodes/element_node.rb +0 -2
  124. data/lib/canon/xml/nodes/namespace_node.rb +0 -2
  125. data/lib/canon/xml/nodes/processing_instruction_node.rb +0 -2
  126. data/lib/canon/xml/nodes/root_node.rb +0 -2
  127. data/lib/canon/xml/nodes/text_node.rb +0 -2
  128. data/lib/canon/xml/nodes.rb +19 -0
  129. data/lib/canon/xml/processor.rb +0 -5
  130. data/lib/canon/xml/sax_builder.rb +0 -7
  131. data/lib/canon/xml.rb +33 -0
  132. data/lib/canon/xml_backend.rb +50 -14
  133. data/lib/canon/xml_parsing.rb +4 -2
  134. data/lib/canon.rb +25 -15
  135. data/lib/tasks/performance.rake +0 -58
  136. data/lib/tasks/performance_comparator.rb +132 -65
  137. data/lib/tasks/performance_helpers.rb +4 -249
  138. data/lib/tasks/performance_report.rb +309 -0
  139. metadata +24 -11
  140. data/lib/canon/comparison/dimensions/attribute_order_dimension.rb +0 -64
  141. data/lib/canon/comparison/dimensions/attribute_presence_dimension.rb +0 -64
  142. data/lib/canon/comparison/dimensions/attribute_values_dimension.rb +0 -167
  143. data/lib/canon/comparison/dimensions/base_dimension.rb +0 -107
  144. data/lib/canon/comparison/dimensions/comments_dimension.rb +0 -117
  145. data/lib/canon/comparison/dimensions/element_position_dimension.rb +0 -86
  146. data/lib/canon/comparison/dimensions/structural_whitespace_dimension.rb +0 -115
  147. data/lib/canon/comparison/dimensions/text_content_dimension.rb +0 -102
  148. data/lib/canon/comparison/xml_comparator/diff_node_builder.rb +0 -300
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Canon
4
+ class Config
5
+ # DSL for declaring config-backed attributes on a config class.
6
+ #
7
+ # A config class (DiffConfig, MatchConfig, FormatConfig) extends this
8
+ # module and declares each user-tunable attribute with {config_key}.
9
+ # The DSL generates the matching getter/setter pair and registers
10
+ # the attribute's metadata (type, enum, default, coercion) in a
11
+ # per-class registry so other components (EnvSchema, TypeConverter)
12
+ # can discover it without duplicating the schema.
13
+ #
14
+ # Goals (lutaml/canon TODO.improve/07):
15
+ # - Eliminate the 5-line getter/setter boilerplate per attribute
16
+ # - Provide a single source of truth for attribute types/enums
17
+ # - Keep the public API stable (methods behave exactly as before)
18
+ #
19
+ # @example Declare a simple boolean key
20
+ # class DiffConfig
21
+ # extend ConfigDSL
22
+ # config_key :verbose_diff, type: :boolean, default: false
23
+ # end
24
+ #
25
+ # @example Declare an enum-constrained symbol key
26
+ # config_key :mode, type: :symbol,
27
+ # enum: %i[by_line by_object pretty_diff],
28
+ # default: :by_line
29
+ #
30
+ # @example Declare a key with setter coercion
31
+ # config_key :preserve_whitespace_elements,
32
+ # type: :string_array,
33
+ # default: [],
34
+ # coerce: ->(v) { Array(v).map(&:to_s) }
35
+ module ConfigDSL
36
+ # Per-class attribute registry, lazily initialized on first
37
+ # +config_key+ declaration. Stored on the extending class so
38
+ # each config class owns its own map without registry leakage.
39
+ def config_keys
40
+ @config_keys ||= {}
41
+ end
42
+
43
+ # Declare a config-backed attribute.
44
+ #
45
+ # Generates a getter and setter on the extending class. The
46
+ # getter reads through the resolver; the setter validates against
47
+ # +enum+ (if provided), applies +coerce+ (if provided), and
48
+ # writes through the resolver.
49
+ #
50
+ # @param name [Symbol] Attribute name
51
+ # @param type [Symbol] Type tag for ENV conversion
52
+ # (:boolean, :integer, :symbol, :string, :string_array,
53
+ # :pass_through)
54
+ # @param enum [Array, nil] Allowed values; setter validates
55
+ # @param default [Object, Proc] Default value, or a callable that
56
+ # returns the default. Callables are evaluated each time the
57
+ # resolver is (re)built, so they pick up runtime state such as
58
+ # +ColorDetector.supports_color?+ when stubs are installed by
59
+ # the test suite.
60
+ # @param coerce [Proc, nil] Setter coercion proc (value → value)
61
+ # @param getter_coerce [Proc, nil] Getter coercion proc
62
+ # @return [void]
63
+ def config_key(name, type: :pass_through, enum: nil, default: nil,
64
+ coerce: nil, getter_coerce: nil)
65
+ sym = name.to_sym
66
+ config_keys[sym] = {
67
+ type: type,
68
+ enum: enum,
69
+ default: default,
70
+ default_proc: default.is_a?(Proc) ? default : nil,
71
+ coerce: coerce,
72
+ getter_coerce: getter_coerce,
73
+ }.freeze
74
+
75
+ define_getter(sym, getter_coerce)
76
+ define_setter(sym, coerce)
77
+ end
78
+
79
+ # Resolve a declared default to a concrete value.
80
+ #
81
+ # If the declared default is a callable (e.g. a proc or method
82
+ # object), it is invoked each time this method is called so the
83
+ # result reflects the current runtime state. Otherwise the
84
+ # stored default value is returned as-is.
85
+ #
86
+ # @param key [Symbol] Attribute name
87
+ # @return [Object] Resolved default value (may be +nil+)
88
+ def resolve_default(key)
89
+ meta = config_keys[key]
90
+ return nil unless meta
91
+
92
+ proc_form = meta[:default_proc]
93
+ return proc_form.call if proc_form
94
+
95
+ meta[:default]
96
+ end
97
+
98
+ # Validate a value against an attribute's enum, if any.
99
+ #
100
+ # Mirrors the original +DiffConfig.validate_config_value!+ API
101
+ # so existing call sites (and specs) keep working.
102
+ #
103
+ # @param key [Symbol] Attribute name
104
+ # @param value [Object] Value to validate
105
+ # @raise [ArgumentError] if value is not in the enum
106
+ # @return [void]
107
+ def validate_config_value!(key, value)
108
+ meta = config_keys[key]
109
+ return unless meta&.dig(:enum)
110
+ return if meta[:enum].include?(value)
111
+
112
+ raise ArgumentError,
113
+ "Invalid value #{value.inspect} for #{key}. " \
114
+ "Valid values: #{meta[:enum].map(&:inspect).join(', ')}"
115
+ end
116
+
117
+ # Enum constraint map, keyed by attribute name.
118
+ #
119
+ # Derived from declared config_keys for backward compatibility
120
+ # with the original +VALID_ENUM_VALUES+ constant.
121
+ #
122
+ # @return [Hash{Symbol => Array}] Enum values per attribute
123
+ def enum_values
124
+ config_keys.filter_map { |k, m| [k, m[:enum]] if m[:enum] }.to_h
125
+ end
126
+
127
+ private
128
+
129
+ # Define the reader method for +name+.
130
+ def define_getter(name, getter_coerce)
131
+ if getter_coerce
132
+ define_method(name) do
133
+ instance_exec(@resolver.resolve(name), &getter_coerce)
134
+ end
135
+ else
136
+ define_method(name) do
137
+ @resolver.resolve(name)
138
+ end
139
+ end
140
+ end
141
+
142
+ # Define the writer method for +name+.
143
+ def define_setter(name, coerce)
144
+ if coerce
145
+ define_method("#{name}=") do |value|
146
+ coerced = instance_exec(value, &coerce)
147
+ self.class.validate_config_value!(name, coerced)
148
+ @resolver.set_programmatic(name, coerced)
149
+ end
150
+ else
151
+ define_method("#{name}=") do |value|
152
+ self.class.validate_config_value!(name, value)
153
+ @resolver.set_programmatic(name, value)
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "env_schema"
4
- require_relative "type_converter"
5
-
6
3
  module Canon
7
4
  class Config
8
5
  # Provides environment variable values for configuration
@@ -2,59 +2,31 @@
2
2
 
3
3
  module Canon
4
4
  class Config
5
- # Schema definition for configuration attributes
6
- # Defines attribute types and ENV variable mappings
5
+ # Schema definition for configuration attributes.
6
+ #
7
+ # Diff attribute types and names are derived lazily from
8
+ # +Canon::Config::DiffConfig.config_keys+ (the single source of
9
+ # truth declared via {ConfigDSL}). Match and Format attribute
10
+ # types are declared here because those config classes do not yet
11
+ # use the DSL.
12
+ #
13
+ # The lookup is intentionally lazy so that {EnvSchema} can be
14
+ # loaded before {DiffConfig} is defined — the methods are only
15
+ # called once a {DiffConfig} instance is being built, by which
16
+ # time the DSL registry is populated.
7
17
  class EnvSchema
8
- ATTRIBUTE_TYPES = {
9
- # DiffConfig attributes
10
- mode: :symbol,
11
- use_color: :boolean,
12
- context_lines: :integer,
13
- grouping_lines: :integer,
14
- show_diffs: :symbol,
15
- verbose_diff: :boolean,
16
- algorithm: :symbol,
17
- parser: :symbol,
18
- show_raw_inputs: :boolean,
19
- show_raw_expected: :boolean,
20
- show_raw_received: :boolean,
21
- show_preprocessed_inputs: :boolean,
22
- show_preprocessed_expected: :boolean,
23
- show_preprocessed_received: :boolean,
24
- show_prettyprint_inputs: :boolean,
25
- show_prettyprint_expected: :boolean,
26
- show_prettyprint_received: :boolean,
27
- show_line_numbered_inputs: :boolean,
28
- character_visualization: :symbol, # true, false, :content_only
29
- display_format: :symbol,
30
- display_preprocessing: :symbol, # :none, :pretty_print, :normalize_pretty_print, :c14n
31
- pretty_printer_indent: :integer,
32
- pretty_printer_indent_type: :symbol, # :space or :tab
33
- preserve_whitespace_elements: :string_array,
34
- collapse_whitespace_elements: :string_array,
35
- strip_whitespace_elements: :string_array,
36
- pretty_printed_expected: :boolean,
37
- pretty_printed_received: :boolean,
38
- pretty_printer_sort_attributes: :boolean,
39
- compact_semantic_report: :boolean,
40
- expand_difference: :boolean,
41
- theme: :symbol,
42
-
43
- # MatchConfig attributes
18
+ MATCH_ATTRIBUTE_TYPES = {
44
19
  profile: :symbol,
20
+ }.freeze
45
21
 
46
- # FormatConfig attributes
22
+ FORMAT_ATTRIBUTE_TYPES = {
47
23
  preprocessing: :string,
48
-
49
- # Size limits to prevent hangs on large files
50
- max_file_size: :integer,
51
- max_node_count: :integer,
52
- max_diff_lines: :integer,
53
24
  }.freeze
54
25
 
55
26
  class << self
56
27
  def type_for(attribute)
57
- ATTRIBUTE_TYPES[attribute.to_sym]
28
+ key = attribute.to_sym
29
+ diff_type(key) || match_type(key) || format_type(key)
58
30
  end
59
31
 
60
32
  def env_key(format, config_type, attribute)
@@ -65,29 +37,47 @@ module Canon
65
37
  "CANON_#{attribute.to_s.upcase}"
66
38
  end
67
39
 
40
+ # All diff attributes declared via {ConfigDSL} on {DiffConfig}.
41
+ #
42
+ # @return [Array<Symbol>]
68
43
  def all_diff_attributes
69
- %i[mode use_color context_lines grouping_lines show_diffs
70
- verbose_diff algorithm parser show_raw_inputs show_raw_expected show_raw_received
71
- show_preprocessed_inputs show_preprocessed_expected show_preprocessed_received
72
- show_prettyprint_inputs show_prettyprint_expected show_prettyprint_received
73
- show_line_numbered_inputs character_visualization
74
- display_format display_preprocessing
75
- pretty_printer_indent pretty_printer_indent_type
76
- preserve_whitespace_elements collapse_whitespace_elements strip_whitespace_elements
77
- pretty_printed_expected pretty_printed_received
78
- pretty_printer_sort_attributes
79
- compact_semantic_report expand_difference
80
- max_file_size max_node_count max_diff_lines theme]
44
+ diff_config_keys.keys
81
45
  end
82
46
 
47
+ # Match attributes subject to ENV override.
48
+ #
49
+ # @return [Array<Symbol>]
83
50
  def all_match_attributes
84
51
  %i[profile
85
- preserve_whitespace_elements collapse_whitespace_elements strip_whitespace_elements]
52
+ preserve_whitespace_elements collapse_whitespace_elements
53
+ strip_whitespace_elements]
86
54
  end
87
55
 
56
+ # Format attributes subject to ENV override.
57
+ #
58
+ # @return [Array<Symbol>]
88
59
  def all_format_attributes
89
60
  %i[preprocessing]
90
61
  end
62
+
63
+ private
64
+
65
+ def diff_type(key)
66
+ meta = diff_config_keys[key]
67
+ meta&.fetch(:type)
68
+ end
69
+
70
+ def match_type(key)
71
+ MATCH_ATTRIBUTE_TYPES[key]
72
+ end
73
+
74
+ def format_type(key)
75
+ FORMAT_ATTRIBUTE_TYPES[key]
76
+ end
77
+
78
+ def diff_config_keys
79
+ Canon::Config::DiffConfig.config_keys
80
+ end
91
81
  end
92
82
  end
93
83
  end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "yaml"
4
- require_relative "env_schema"
5
4
 
6
5
  module Canon
7
6
  class Config