canon 0.2.8 → 0.2.11

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 (95) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec-opal +7 -0
  3. data/.rubocop_todo.yml +25 -73
  4. data/Rakefile +37 -0
  5. data/lib/canon/cache.rb +16 -27
  6. data/lib/canon/cli.rb +1 -1
  7. data/lib/canon/color_detector.rb +3 -5
  8. data/lib/canon/comparison/compare_profile.rb +1 -4
  9. data/lib/canon/comparison/dimensions/attribute_order_dimension.rb +2 -6
  10. data/lib/canon/comparison/dimensions/attribute_presence_dimension.rb +2 -6
  11. data/lib/canon/comparison/dimensions/attribute_values_dimension.rb +2 -6
  12. data/lib/canon/comparison/dimensions/comments_dimension.rb +2 -6
  13. data/lib/canon/comparison/dimensions/element_position_dimension.rb +2 -6
  14. data/lib/canon/comparison/dimensions/structural_whitespace_dimension.rb +2 -6
  15. data/lib/canon/comparison/dimensions/text_content_dimension.rb +3 -5
  16. data/lib/canon/comparison/format_detector.rb +29 -20
  17. data/lib/canon/comparison/html_comparator.rb +20 -29
  18. data/lib/canon/comparison/html_compare_profile.rb +3 -10
  19. data/lib/canon/comparison/html_parser.rb +1 -1
  20. data/lib/canon/comparison/json_comparator.rb +8 -0
  21. data/lib/canon/comparison/node_inspector.rb +117 -86
  22. data/lib/canon/comparison/strategies/semantic_tree_match_strategy.rb +6 -8
  23. data/lib/canon/comparison/whitespace_sensitivity.rb +55 -193
  24. data/lib/canon/comparison/xml_comparator/attribute_comparator.rb +19 -2
  25. data/lib/canon/comparison/xml_comparator/attribute_filter.rb +5 -10
  26. data/lib/canon/comparison/xml_comparator/child_comparison.rb +4 -4
  27. data/lib/canon/comparison/xml_comparator/diff_node_builder.rb +40 -8
  28. data/lib/canon/comparison/xml_comparator/namespace_comparator.rb +14 -28
  29. data/lib/canon/comparison/xml_comparator/node_parser.rb +14 -13
  30. data/lib/canon/comparison/xml_comparator/node_type_comparator.rb +30 -58
  31. data/lib/canon/comparison/xml_comparator.rb +63 -85
  32. data/lib/canon/comparison/xml_node_comparison.rb +15 -15
  33. data/lib/canon/comparison/yaml_comparator.rb +8 -0
  34. data/lib/canon/comparison.rb +24 -24
  35. data/lib/canon/config/profile_loader.rb +13 -13
  36. data/lib/canon/config.rb +29 -5
  37. data/lib/canon/diff/diff_classifier.rb +7 -41
  38. data/lib/canon/diff/diff_line.rb +1 -1
  39. data/lib/canon/diff/diff_line_builder.rb +2 -0
  40. data/lib/canon/diff/diff_node_enricher.rb +22 -24
  41. data/lib/canon/diff/diff_node_mapper.rb +10 -8
  42. data/lib/canon/diff/formatting_detector.rb +3 -2
  43. data/lib/canon/diff/node_serializer.rb +23 -30
  44. data/lib/canon/diff/path_builder.rb +24 -37
  45. data/lib/canon/diff/source_locator.rb +0 -3
  46. data/lib/canon/diff/xml_serialization_formatter.rb +8 -84
  47. data/lib/canon/diff_formatter/by_line/base_formatter.rb +7 -7
  48. data/lib/canon/diff_formatter/by_line/json_formatter.rb +1 -1
  49. data/lib/canon/diff_formatter/by_line/simple_formatter.rb +1 -1
  50. data/lib/canon/diff_formatter/by_line/xml_formatter.rb +2 -2
  51. data/lib/canon/diff_formatter/by_line/yaml_formatter.rb +1 -1
  52. data/lib/canon/diff_formatter/by_line_formatter.rb +1 -1
  53. data/lib/canon/diff_formatter/by_object/base_formatter.rb +23 -17
  54. data/lib/canon/diff_formatter/by_object/xml_formatter.rb +127 -11
  55. data/lib/canon/diff_formatter/by_object_formatter.rb +2 -6
  56. data/lib/canon/diff_formatter/debug_output.rb +12 -24
  57. data/lib/canon/diff_formatter/diff_detail_formatter/color_helper.rb +2 -2
  58. data/lib/canon/diff_formatter/diff_detail_formatter/dimension_formatter.rb +3 -3
  59. data/lib/canon/diff_formatter/diff_detail_formatter/location_extractor.rb +26 -27
  60. data/lib/canon/diff_formatter/diff_detail_formatter/node_utils.rb +146 -318
  61. data/lib/canon/diff_formatter/diff_detail_formatter.rb +28 -20
  62. data/lib/canon/diff_formatter/legend.rb +2 -2
  63. data/lib/canon/diff_formatter/pretty_diff_formatter.rb +2 -2
  64. data/lib/canon/diff_formatter/theme.rb +4 -4
  65. data/lib/canon/diff_formatter.rb +17 -13
  66. data/lib/canon/formatters/html_formatter.rb +1 -1
  67. data/lib/canon/formatters/html_formatter_base.rb +1 -1
  68. data/lib/canon/formatters/xml_formatter.rb +7 -32
  69. data/lib/canon/html/data_model.rb +2 -2
  70. data/lib/canon/pretty_printer/html.rb +1 -1
  71. data/lib/canon/pretty_printer/xml.rb +16 -7
  72. data/lib/canon/pretty_printer/xml_normalized.rb +9 -3
  73. data/lib/canon/rspec_matchers.rb +2 -2
  74. data/lib/canon/tree_diff/adapters/html_adapter.rb +1 -1
  75. data/lib/canon/tree_diff/adapters/xml_adapter.rb +1 -1
  76. data/lib/canon/tree_diff/core/tree_node.rb +1 -3
  77. data/lib/canon/tree_diff/operation_converter.rb +7 -7
  78. data/lib/canon/tree_diff/operations/operation_detector.rb +4 -0
  79. data/lib/canon/validators/base_validator.rb +5 -8
  80. data/lib/canon/validators/html_validator.rb +3 -8
  81. data/lib/canon/validators/xml_validator.rb +3 -8
  82. data/lib/canon/version.rb +1 -1
  83. data/lib/canon/xml/data_model.rb +132 -138
  84. data/lib/canon/xml/namespace_helper.rb +5 -0
  85. data/lib/canon/xml/node.rb +2 -1
  86. data/lib/canon/xml/nodes/root_node.rb +4 -0
  87. data/lib/canon/xml/nodes/text_node.rb +6 -1
  88. data/lib/canon/xml/sax_builder.rb +5 -7
  89. data/lib/canon/xml/whitespace_normalizer.rb +2 -2
  90. data/lib/canon/xml_backend.rb +49 -0
  91. data/lib/canon/xml_parsing.rb +283 -0
  92. data/lib/canon.rb +3 -1
  93. data/lib/tasks/benchmark_runner.rb +1 -1
  94. data/lib/tasks/performance_helpers.rb +1 -1
  95. metadata +9 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b5218e18de7c596c5875ee1cf906331269cd58475a1f00de5c20af398bb07f08
4
- data.tar.gz: 0dedd6f9e8ca265d37c610a183ed0e695ba68a9d8c9f0766b890f3d8db7d1f66
3
+ metadata.gz: 8fcacddf719d0cb69472bc55b2a23d724c938fdf76f995eb77be962aeda42182
4
+ data.tar.gz: 3a1d14c811209e8353c7539d12c2f7a4d23d44c1e6742b6f48a8d94f65282bd5
5
5
  SHA512:
6
- metadata.gz: c24944e5600684e24f4b32cd16d90d68f64ca07671da7cd30a4cc7e13e818e98f86ab849ee28f7780f40ae3514df1ba3087cb32f55c7923d5303c65819aa8d59
7
- data.tar.gz: 13a3c944492c29a916569b86829cefd8bf7baaf247eea105ee3f608d25789ef7c79ab0b0a95a3806e66b85348dd629169f8e19bef127e3ca79685a0e13d1bca9
6
+ metadata.gz: f4685b05d13d6cedad780751a8011a722511a9a5623a3a3fa14ce548b95d036c8fd36cabb10d8793ede0a97cf5a1e72d8f53729022c90c8b316c510dc133983e
7
+ data.tar.gz: 5201625d93a1ed9c2efb929905c1d14f4a292e914604149f8e5902a61d1ea6059b987c8875b073ec71340adb1833a33bbce450f58006f29736a37d810f326d6d
data/.rspec-opal ADDED
@@ -0,0 +1,7 @@
1
+ --default-path=spec
2
+ --pattern='spec/canon/opal_xml_smoke_spec.rb'
3
+ -I lib
4
+ --opal-opt=-g,canon
5
+ -I spec
6
+ --require=spec_helper
7
+ --require=support/opal
data/.rubocop_todo.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  # This configuration was generated by
2
- # `rubocop --auto-gen-config`
3
- # on 2026-05-05 13:09:45 UTC using RuboCop version 1.86.0.
2
+ # `rubocop --auto-gen-config --no-auto-gen-timestamp`
3
+ # using RuboCop version 1.86.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,58 +11,14 @@ Gemspec/RequiredRubyVersion:
11
11
  Exclude:
12
12
  - 'canon.gemspec'
13
13
 
14
- # Offense count: 5
15
- # This cop supports safe autocorrection (--autocorrect).
16
- # Configuration parameters: EnforcedStyle, IndentationWidth.
17
- # SupportedStyles: with_first_argument, with_fixed_indentation
18
- Layout/ArgumentAlignment:
19
- Exclude:
20
- - 'lib/canon/comparison/child_realignment.rb'
21
- - 'lib/canon/comparison/xml_comparator/child_comparison.rb'
22
- - 'spec/canon/diff_formatter/diff_detail_formatter_spec.rb'
23
-
24
- # Offense count: 5
25
- # This cop supports safe autocorrection (--autocorrect).
26
- # Configuration parameters: EnforcedStyleAlignWith.
27
- # SupportedStylesAlignWith: either, start_of_block, start_of_line
28
- Layout/BlockAlignment:
29
- Exclude:
30
- - 'spec/canon/comparison/comments_asymmetry_spec.rb'
31
- - 'spec/canon/comparison/whitespace_adjacency_spec.rb'
32
-
33
- # Offense count: 5
34
- # This cop supports safe autocorrection (--autocorrect).
35
- Layout/BlockEndNewline:
36
- Exclude:
37
- - 'spec/canon/comparison/comments_asymmetry_spec.rb'
38
- - 'spec/canon/comparison/whitespace_adjacency_spec.rb'
39
-
40
- # Offense count: 10
41
- # This cop supports safe autocorrection (--autocorrect).
42
- # Configuration parameters: Width, EnforcedStyleAlignWith, AllowedPatterns.
43
- # SupportedStylesAlignWith: start_of_line, relative_to_receiver
44
- Layout/IndentationWidth:
45
- Exclude:
46
- - 'spec/canon/comparison/comments_asymmetry_spec.rb'
47
- - 'spec/canon/comparison/whitespace_adjacency_spec.rb'
48
-
49
- # Offense count: 1386
14
+ # Offense count: 1357
50
15
  # This cop supports safe autocorrection (--autocorrect).
51
16
  # Configuration parameters: Max, AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, AllowRBSInlineAnnotation, AllowCopDirectives, AllowedPatterns, SplitStrings.
52
17
  # URISchemes: http, https
53
18
  Layout/LineLength:
54
19
  Enabled: false
55
20
 
56
- # Offense count: 6
57
- # This cop supports safe autocorrection (--autocorrect).
58
- # Configuration parameters: AllowInHeredoc.
59
- Layout/TrailingWhitespace:
60
- Exclude:
61
- - 'lib/canon/comparison/child_realignment.rb'
62
- - 'lib/canon/comparison/xml_comparator/child_comparison.rb'
63
- - 'spec/canon/diff_formatter/diff_detail_formatter_spec.rb'
64
-
65
- # Offense count: 63
21
+ # Offense count: 58
66
22
  # Configuration parameters: IgnoreLiteralBranches, IgnoreConstantBranches, IgnoreDuplicateElseBranch.
67
23
  Lint/DuplicateBranch:
68
24
  Enabled: false
@@ -75,13 +31,12 @@ Lint/EmptyConditionalBody:
75
31
  - 'spec/canon/comparison/html_comparator_spec.rb'
76
32
  - 'spec/canon/comparison_spec.rb'
77
33
 
78
- # Offense count: 6
34
+ # Offense count: 5
79
35
  # Configuration parameters: MaximumRangeSize.
80
36
  Lint/MissingCopEnableDirective:
81
37
  Exclude:
82
38
  - 'lib/canon/commands/format_command.rb'
83
39
  - 'lib/canon/xml/attribute_handler.rb'
84
- - 'lib/canon/xml/data_model.rb'
85
40
  - 'lib/canon/xml/namespace_handler.rb'
86
41
  - 'lib/canon/xml/processor.rb'
87
42
  - 'lib/canon/xml/xml_base_handler.rb'
@@ -107,7 +62,7 @@ Lint/UselessConstantScoping:
107
62
  Exclude:
108
63
  - 'lib/canon/diff_formatter/theme.rb'
109
64
 
110
- # Offense count: 321
65
+ # Offense count: 316
111
66
  # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max.
112
67
  Metrics/AbcSize:
113
68
  Enabled: false
@@ -123,12 +78,12 @@ Metrics/BlockLength:
123
78
  Metrics/BlockNesting:
124
79
  Max: 4
125
80
 
126
- # Offense count: 285
81
+ # Offense count: 276
127
82
  # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
128
83
  Metrics/CyclomaticComplexity:
129
84
  Enabled: false
130
85
 
131
- # Offense count: 529
86
+ # Offense count: 527
132
87
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
133
88
  Metrics/MethodLength:
134
89
  Max: 146
@@ -138,7 +93,7 @@ Metrics/MethodLength:
138
93
  Metrics/ParameterLists:
139
94
  Max: 10
140
95
 
141
- # Offense count: 221
96
+ # Offense count: 214
142
97
  # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
143
98
  Metrics/PerceivedComplexity:
144
99
  Enabled: false
@@ -152,6 +107,14 @@ Naming/MethodParameterName:
152
107
  - 'lib/canon/comparison/xml_comparator/attribute_comparator.rb'
153
108
  - 'lib/canon/xml/namespace_handler.rb'
154
109
 
110
+ # Offense count: 1
111
+ # Configuration parameters: Mode, AllowedMethods, AllowedPatterns, AllowBangMethods, WaywardPredicates.
112
+ # AllowedMethods: call
113
+ # WaywardPredicates: infinite?, nonzero?
114
+ Naming/PredicateMethod:
115
+ Exclude:
116
+ - 'spec/canon/comparison/xml_attribute_diff_spec.rb'
117
+
155
118
  # Offense count: 6
156
119
  # Configuration parameters: EnforcedStyle, CheckMethodNames, CheckSymbols, AllowedIdentifiers, AllowedPatterns.
157
120
  # SupportedStyles: snake_case, normalcase, non_integer
@@ -177,7 +140,7 @@ Performance/CollectionLiteralInLoop:
177
140
  RSpec/ContextWording:
178
141
  Enabled: false
179
142
 
180
- # Offense count: 46
143
+ # Offense count: 48
181
144
  # Configuration parameters: IgnoredMetadata.
182
145
  RSpec/DescribeClass:
183
146
  Enabled: false
@@ -188,7 +151,7 @@ RSpec/DescribeMethod:
188
151
  - 'spec/canon/comparison/multiple_differences_spec.rb'
189
152
  - 'spec/canon/diff_formatter/character_map_customization_spec.rb'
190
153
 
191
- # Offense count: 876
154
+ # Offense count: 893
192
155
  # Configuration parameters: CountAsOne.
193
156
  RSpec/ExampleLength:
194
157
  Max: 44
@@ -240,7 +203,7 @@ RSpec/MultipleDescribes:
240
203
  Exclude:
241
204
  - 'spec/canon/comparison/match_options_spec.rb'
242
205
 
243
- # Offense count: 735
206
+ # Offense count: 749
244
207
  RSpec/MultipleExpectations:
245
208
  Max: 15
246
209
 
@@ -263,12 +226,13 @@ RSpec/NamedSubject:
263
226
  RSpec/NestedGroups:
264
227
  Max: 4
265
228
 
266
- # Offense count: 10
229
+ # Offense count: 11
267
230
  # Configuration parameters: AllowedPatterns.
268
231
  # AllowedPatterns: ^expect_, ^assert_
269
232
  RSpec/NoExpectationExample:
270
233
  Exclude:
271
234
  - 'spec/canon/context_grouping_spec.rb'
235
+ - 'spec/canon/diff_formatter/diff_detail_formatter_spec.rb'
272
236
  - 'spec/canon/informative_diffs_debug_spec.rb'
273
237
  - 'spec/canon/isodoc_blockquotes_spec.rb'
274
238
  - 'spec/canon/match_scenarios_spec.rb'
@@ -292,30 +256,17 @@ RSpec/SpecFilePathFormat:
292
256
  - 'spec/canon/yaml/formatter_spec.rb'
293
257
  - 'spec/xml_c14n_spec.rb'
294
258
 
295
- # Offense count: 100
259
+ # Offense count: 18
296
260
  # Configuration parameters: IgnoreNameless, IgnoreSymbolicNames.
297
261
  RSpec/VerifiedDoubles:
298
262
  Exclude:
299
263
  - 'spec/canon/comparison/diff_node_builder_spec.rb'
300
264
  - 'spec/canon/comparison/whitespace_sensitivity_spec.rb'
301
265
  - 'spec/canon/diff/diff_classifier_spec.rb'
302
- - 'spec/canon/diff/path_builder_spec.rb'
303
266
  - 'spec/canon/diff/xml_serialization_formatter_spec.rb'
304
267
  - 'spec/canon/diff_formatter/diff_detail_formatter_spec.rb'
305
268
  - 'spec/canon/tree_diff/operation_converter_spec.rb'
306
269
 
307
- # Offense count: 8
308
- # This cop supports safe autocorrection (--autocorrect).
309
- # Configuration parameters: EnforcedStyle, ProceduralMethods, FunctionalMethods, AllowedMethods, AllowedPatterns, AllowBracesOnProceduralOneLiners, BracesRequiredMethods.
310
- # SupportedStyles: line_count_based, semantic, braces_for_chaining, always_braces
311
- # ProceduralMethods: benchmark, bm, bmbm, create, each_with_object, measure, new, realtime, tap, with_object
312
- # FunctionalMethods: let, let!, subject, watch
313
- # AllowedMethods: lambda, proc, it
314
- Style/BlockDelimiters:
315
- Exclude:
316
- - 'spec/canon/comparison/comments_asymmetry_spec.rb'
317
- - 'spec/canon/comparison/whitespace_adjacency_spec.rb'
318
-
319
270
  # Offense count: 1
320
271
  # This cop supports safe autocorrection (--autocorrect).
321
272
  # Configuration parameters: EnforcedStyle, AllowComments.
@@ -331,12 +282,13 @@ Style/HashLikeCase:
331
282
  - 'lib/canon/diff/diff_block_builder.rb'
332
283
  - 'lib/canon/xml/character_encoder.rb'
333
284
 
334
- # Offense count: 4
285
+ # Offense count: 6
335
286
  # This cop supports unsafe autocorrection (--autocorrect-all).
336
287
  Style/IdenticalConditionalBranches:
337
288
  Exclude:
338
289
  - 'lib/canon/diff_formatter/by_object/base_formatter.rb'
339
290
  - 'lib/canon/diff_formatter/legend.rb'
291
+ - 'lib/canon/xml/whitespace_normalizer.rb'
340
292
 
341
293
  # Offense count: 1
342
294
  # Configuration parameters: AllowedMethods.
data/Rakefile CHANGED
@@ -5,10 +5,47 @@ require "rspec/core/rake_task"
5
5
 
6
6
  RSpec::Core::RakeTask.new(:spec)
7
7
 
8
+ begin
9
+ require "opal/rspec/rake_task"
10
+
11
+ # Configure Opal load paths at load time (same pattern as moxml)
12
+ if defined?(Opal)
13
+ Opal.append_path File.expand_path("lib", __dir__)
14
+
15
+ # moxml gem
16
+ begin
17
+ moxml_spec = Gem::Specification.find_by_name("moxml")
18
+ moxml_lib = moxml_spec.full_require_paths.first
19
+ Opal.append_path moxml_lib
20
+ moxml_compat = File.expand_path("compat/opal", moxml_lib)
21
+ Opal.append_path moxml_compat if File.directory?(moxml_compat)
22
+ rescue Gem::MissingSpecError
23
+ # moxml not installed
24
+ end
25
+
26
+ # REXML: bundled gem since Ruby 3.4
27
+ rexml_lib = $LOAD_PATH.find { |p| File.exist?(File.join(p, "rexml", "document.rb")) }
28
+ Opal.append_path rexml_lib if rexml_lib
29
+ end
30
+ rescue LoadError
31
+ # Opal not available or incompatible with current Ruby version
32
+ end
33
+
8
34
  require "rubocop/rake_task"
9
35
 
10
36
  RuboCop::RakeTask.new
11
37
 
12
38
  Dir.glob("lib/tasks/**/*.rake").each { |r| load r }
13
39
 
40
+ namespace :spec do
41
+ if defined?(Opal::RSpec::RakeTask)
42
+ desc "Run Opal (JavaScript) tests"
43
+ Opal::RSpec::RakeTask.new(:opal) do |_server, runner|
44
+ runner.default_path = "spec"
45
+ runner.requires = %w[rexml_compat rexml/document rexml/xpath moxml/adapter/rexml spec_helper]
46
+ runner.pattern = "spec/canon/opal_xml_smoke_spec.rb"
47
+ end
48
+ end
49
+ end
50
+
14
51
  task default: %i[spec rubocop]
data/lib/canon/cache.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "digest"
3
+ require "digest" unless RUBY_ENGINE == "opal"
4
4
 
5
5
  module Canon
6
6
  # Cache for expensive operations during document comparison
@@ -73,50 +73,39 @@ module Canon
73
73
  end
74
74
 
75
75
  # Generate cache key for document parsing
76
- #
77
- # @param content [String] Document content
78
- # @param format [Symbol] Document format
79
- # @param preprocessing [Symbol] Preprocessing option
80
- # @return [String] Cache key
81
76
  def key_for_document(content, format, preprocessing)
82
- digest = Digest::SHA256.hexdigest(content)
83
- "doc:#{format}:#{preprocessing}:#{digest[0..16]}"
77
+ "doc:#{format}:#{preprocessing}:#{content_hash(content)}"
84
78
  end
85
79
 
86
80
  # Generate cache key for format detection
87
- #
88
- # @param content [String] Document content
89
- # @return [String] Cache key
90
81
  def key_for_format_detection(content)
91
- # Use first 100 chars for quick key, plus length
92
- # Force to binary to avoid encoding compatibility issues
93
82
  preview = content[0..100].b
94
- digest = Digest::SHA256.hexdigest(preview + content.length.to_s)
95
- "fmt:#{digest[0..16]}"
83
+ "fmt:#{content_hash(preview + content.length.to_s)}"
96
84
  end
97
85
 
98
86
  # Generate cache key for XML canonicalization
99
- #
100
- # @param content [String] XML content
101
- # @param with_comments [Boolean] Whether to include comments
102
- # @return [String] Cache key
103
87
  def key_for_c14n(content, with_comments)
104
- digest = Digest::SHA256.hexdigest(content)
105
- "c14n:#{with_comments}:#{digest[0..16]}"
88
+ "c14n:#{with_comments}:#{content_hash(content)}"
106
89
  end
107
90
 
108
91
  # Generate cache key for preprocessing
109
- #
110
- # @param content [String] Original content
111
- # @param preprocessing [Symbol] Preprocessing type
112
- # @return [String] Cache key
113
92
  def key_for_preprocessing(content, preprocessing)
114
- digest = Digest::SHA256.hexdigest(content)
115
- "pre:#{preprocessing}:#{digest[0..16]}"
93
+ "pre:#{preprocessing}:#{content_hash(content)}"
116
94
  end
117
95
 
118
96
  private
119
97
 
98
+ # Generate a hash string for cache keys
99
+ def content_hash(content)
100
+ if defined?(Digest::SHA256)
101
+ Digest::SHA256.hexdigest(content)[0..16]
102
+ else
103
+ # Opal fallback: simple string hash
104
+ h = content.each_char.reduce(0) { |acc, c| ((acc * 31) + c.ord) & 0xFFFFFFFF }
105
+ h.to_s(16).rjust(8, "0")
106
+ end
107
+ end
108
+
120
109
  # Get or create cache for a category
121
110
  #
122
111
  # @param category [Symbol] Cache category
data/lib/canon/cli.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "thor"
3
+ require "thor" unless RUBY_ENGINE == "opal"
4
4
  require_relative "commands/format_command"
5
5
  require_relative "commands/diff_command"
6
6
  require_relative "options/registry"
@@ -67,11 +67,9 @@ module Canon
67
67
  # @param io [IO] Output stream
68
68
  # @return [Boolean] true if the stream is a TTY
69
69
  def tty?(io)
70
- return false unless io.respond_to?(:tty?)
71
- return false unless io.respond_to?(:isatty)
72
-
73
- # Ruby 2.5+ uses tty?, older uses isatty
74
- io.tty? || io.isatty
70
+ io.tty?
71
+ rescue NoMethodError
72
+ false
75
73
  rescue ArgumentError, IOError
76
74
  # Stream might be closed or invalid
77
75
  false
@@ -82,14 +82,11 @@ module Canon
82
82
  %i[text_content structural_whitespace].include?(dimension)
83
83
  end
84
84
 
85
- private
86
-
87
85
  # Get the behavior setting for a dimension
88
86
  # @param dimension [Symbol] The match dimension
89
87
  # @return [Symbol] The behavior (:strict, :normalize, :ignore)
90
88
  def behavior_for(dimension)
91
- # Handle both ResolvedMatchOptions and Hash
92
- if match_options.respond_to?(:behavior_for)
89
+ if match_options.is_a?(ResolvedMatchOptions)
93
90
  match_options.behavior_for(dimension)
94
91
  elsif match_options.is_a?(Hash)
95
92
  match_options[dimension] || :strict
@@ -21,14 +21,10 @@ module Canon
21
21
  def extract_data(node)
22
22
  return [] unless node
23
23
 
24
- # Handle Moxml nodes
25
- if node.is_a?(Moxml::Node)
26
- extract_from_moxml(node)
27
- # Handle Nokogiri nodes
28
- elsif node.is_a?(Nokogiri::XML::Node)
24
+ if Canon::XmlBackend.nokogiri?
29
25
  extract_from_nokogiri(node)
30
26
  else
31
- []
27
+ extract_from_moxml(node)
32
28
  end
33
29
  end
34
30
 
@@ -21,14 +21,10 @@ module Canon
21
21
  def extract_data(node)
22
22
  return [] unless node
23
23
 
24
- # Handle Moxml nodes
25
- if node.is_a?(Moxml::Node)
26
- extract_from_moxml(node)
27
- # Handle Nokogiri nodes
28
- elsif node.is_a?(Nokogiri::XML::Node)
24
+ if Canon::XmlBackend.nokogiri?
29
25
  extract_from_nokogiri(node)
30
26
  else
31
- []
27
+ extract_from_moxml(node)
32
28
  end
33
29
  end
34
30
 
@@ -27,14 +27,10 @@ module Canon
27
27
  def extract_data(node)
28
28
  return {} unless node
29
29
 
30
- # Handle Moxml nodes
31
- if node.is_a?(Moxml::Node)
32
- extract_from_moxml(node)
33
- # Handle Nokogiri nodes
34
- elsif node.is_a?(Nokogiri::XML::Node)
30
+ if Canon::XmlBackend.nokogiri?
35
31
  extract_from_nokogiri(node)
36
32
  else
37
- {}
33
+ extract_from_moxml(node)
38
34
  end
39
35
  end
40
36
 
@@ -21,14 +21,10 @@ module Canon
21
21
  def extract_data(node)
22
22
  return [] unless node
23
23
 
24
- # Handle Moxml nodes
25
- if node.is_a?(Moxml::Node)
26
- extract_from_moxml(node)
27
- # Handle Nokogiri nodes
28
- elsif node.is_a?(Nokogiri::XML::Node)
24
+ if Canon::XmlBackend.nokogiri?
29
25
  extract_from_nokogiri(node)
30
26
  else
31
- []
27
+ extract_from_moxml(node)
32
28
  end
33
29
  end
34
30
 
@@ -23,14 +23,10 @@ module Canon
23
23
  def extract_data(node)
24
24
  return 0 unless node
25
25
 
26
- # Handle Moxml nodes
27
- if node.is_a?(Moxml::Node)
28
- extract_from_moxml(node)
29
- # Handle Nokogiri nodes
30
- elsif node.is_a?(Nokogiri::XML::Node)
26
+ if Canon::XmlBackend.nokogiri?
31
27
  extract_from_nokogiri(node)
32
28
  else
33
- 0
29
+ extract_from_moxml(node)
34
30
  end
35
31
  end
36
32
 
@@ -25,14 +25,10 @@ module Canon
25
25
  def extract_data(node)
26
26
  return [] unless node
27
27
 
28
- # Handle Moxml nodes
29
- if node.is_a?(Moxml::Node)
30
- extract_from_moxml(node)
31
- # Handle Nokogiri nodes
32
- elsif node.is_a?(Nokogiri::XML::Node)
28
+ if Canon::XmlBackend.nokogiri?
33
29
  extract_from_nokogiri(node)
34
30
  else
35
- []
31
+ extract_from_moxml(node)
36
32
  end
37
33
  end
38
34
 
@@ -23,12 +23,10 @@ module Canon
23
23
  def extract_data(node)
24
24
  return nil unless node
25
25
 
26
- # Handle Moxml nodes
27
- if node.is_a?(Moxml::Node)
28
- extract_from_moxml(node)
29
- # Handle Nokogiri nodes
30
- elsif node.is_a?(Nokogiri::XML::Node)
26
+ if Canon::XmlBackend.nokogiri?
31
27
  extract_from_nokogiri(node)
28
+ else
29
+ extract_from_moxml(node)
32
30
  end
33
31
  end
34
32
 
@@ -22,27 +22,36 @@ module Canon
22
22
  # @param obj [Object] Object to detect format of
23
23
  # @return [Symbol] Format type (:xml, :html, :json, :yaml, :ruby_object, :string)
24
24
  def detect(obj)
25
- case obj
26
- when Moxml::Node, Moxml::Document
27
- :xml
28
- when Nokogiri::HTML::DocumentFragment, Nokogiri::HTML5::DocumentFragment
29
- # HTML DocumentFragments
30
- :html
31
- when Nokogiri::XML::DocumentFragment
32
- # XML DocumentFragments - check if it's actually HTML
33
- obj.document&.html? ? :html : :xml
34
- when Nokogiri::XML::Document, Nokogiri::XML::Node
35
- # Check if it's HTML by looking at the document type
36
- obj.html? ? :html : :xml
37
- when Nokogiri::HTML::Document, Nokogiri::HTML5::Document
38
- :html
39
- when String
40
- detect_string(obj)
41
- when Hash, Array
42
- # Raw Ruby objects (from parsed JSON/YAML)
43
- :ruby_object
25
+ if XmlBackend.moxml?
26
+ case obj
27
+ when Moxml::Node, Moxml::Document
28
+ :xml
29
+ when String
30
+ detect_string(obj)
31
+ when Hash, Array
32
+ :ruby_object
33
+ else
34
+ raise Canon::Error, "Unknown format for object: #{obj.class}"
35
+ end
44
36
  else
45
- raise Canon::Error, "Unknown format for object: #{obj.class}"
37
+ case obj
38
+ when Moxml::Node, Moxml::Document
39
+ :xml
40
+ when Nokogiri::HTML::DocumentFragment, Nokogiri::HTML5::DocumentFragment
41
+ :html
42
+ when Nokogiri::XML::DocumentFragment
43
+ obj.document&.html? ? :html : :xml
44
+ when Nokogiri::XML::Document, Nokogiri::XML::Node
45
+ obj.html? ? :html : :xml
46
+ when Nokogiri::HTML::Document, Nokogiri::HTML5::Document
47
+ :html
48
+ when String
49
+ detect_string(obj)
50
+ when Hash, Array
51
+ :ruby_object
52
+ else
53
+ raise Canon::Error, "Unknown format for object: #{obj.class}"
54
+ end
46
55
  end
47
56
  end
48
57