canon 0.3.22 → 0.3.23

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4a369d56aae65493ad94c6352f01a15915eb0af020cde2c98868b7d51a8b0658
4
- data.tar.gz: f667f5bf42ed4f1e581ee32951d561653187c06b74b6b996f9bcd1bb9fb0f073
3
+ metadata.gz: 7e3fda4e37d9ca6cf584389c0ffec06c70811ec488910377151ed8ee29f20a35
4
+ data.tar.gz: cb73f6f95b16ce10b9ada9f13d1e91eaad0a4bce416d84ddfb49439227f270d5
5
5
  SHA512:
6
- metadata.gz: 6741dac3a19b779fbbd9fe49edaa7f3d6c71497fe4a1cc6339eb65f0d749bcfe9e0800323cb321f4cfd35a756e0ebfab5f8740f8c3aa5e895b70d7c71adc3a84
7
- data.tar.gz: 8a360875b2c5597c9e87e81eb0a8037979efa6b8166fe18155a68efc432626d4efebc1aa8ad82ea66ec398681aa1d03f83d35aa1c8fe0684e74a616f0ee96913
6
+ metadata.gz: 48a6dacc76c75c234a34583264f0cd3816c643bda7152ccbeee62cb4057fd370c70b168414bca515addc400de5ea12dacb82b9a356d35e4421596bff0109806b
7
+ data.tar.gz: a33a5b36fbd98f43d4832dbb275737726f5e9dc1786b91fc34313684a8e431724e3f169a426c18af3d90a15be1fb9a4970e561ef25d2f576ff52229cfebc1829
data/CLAUDE.md CHANGED
@@ -143,6 +143,10 @@ Engine parity is complete through libleptris 1.9.8 / leptris-ruby 1.9.33 / moxml
143
143
 
144
144
  Engine A/B testing: `CANON_XML_BACKEND=nokogiri bundle exec rspec` (or `=moxml` to force leptris when it isn't the resolved default). The default suite must stay green under BOTH values; the only expected pendings are the upstream-tracked ones. The benchmark header (`rake performance:quick`) reports the active engine.
145
145
 
146
+ ### YAML Engines
147
+
148
+ `Canon::YamlBackend` selects the YAML engine: `:psych` (default) or `:yeptris` (FFI over libyeptris, the YAML counterpart of the leptris XML stack — `CANON_YAML_BACKEND=yeptris` opts in; the optional `yeptris` Gemfile group must be enabled). `Canon::YamlParsing` is the single gateway for string loads; `YAML.dump` stays on Psych everywhere — canonical output bytes are canon's product and the writers differ. The default stays `:psych` until the yeptris Psych-parity gaps close (yeptris-ruby#29 empty documents, #30 sexagesimal scalars, #31 >64-bit integers); `spec/canon/yaml_engine_parity_spec.rb` is the executable gate, with upstream-tracked cases pending. Never `require "yeptris/psych"` — it rebinds the global `::Psych` constant for the whole process; only the namespaced `Yeptris::YAML` API is used.
149
+
146
150
  ### Format Detection
147
151
 
148
152
  `Canon::Comparison::FormatDetector` auto-detects format from string content or object type (Moxml::Node → XML, Nokogiri::HTML → HTML, Hash → JSON, etc.). HTML4 vs HTML5 is determined by DOCTYPE.
@@ -90,7 +90,7 @@ module Canon
90
90
  def parse_yaml(obj)
91
91
  return obj unless obj.is_a?(String)
92
92
 
93
- YAML.safe_load(obj, aliases: true)
93
+ Canon::YamlParsing.safe_load(obj, aliases: true)
94
94
  end
95
95
  end
96
96
  end
@@ -17,7 +17,7 @@ module Canon
17
17
  # Return as-is if already parsed
18
18
  return yaml if yaml.is_a?(Hash) || yaml.is_a?(Array)
19
19
 
20
- YAML.safe_load(yaml, permitted_classes: [Symbol, Date, Time])
20
+ Canon::YamlParsing.safe_load(yaml)
21
21
  end
22
22
 
23
23
  def self.sort_yaml_keys(obj)
@@ -22,7 +22,7 @@ module Canon
22
22
  return if input.is_a?(Hash) || input.is_a?(Array) # Already parsed
23
23
  return if input.strip.empty?
24
24
 
25
- YAML.safe_load(input, permitted_classes: [Symbol, Date, Time])
25
+ Canon::YamlParsing.safe_load(input)
26
26
  rescue Psych::SyntaxError => e
27
27
  location = extract_location(e)
28
28
 
data/lib/canon/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Canon
4
- VERSION = "0.3.22"
4
+ VERSION = "0.3.23"
5
5
  end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Canon
4
+ # Selection of the YAML engine: :psych (stdlib) or :yeptris
5
+ # (FFI over libyeptris — the YAML counterpart of the leptris XML
6
+ # stack).
7
+ #
8
+ # Mirrors XmlBackend's discipline (MECE — this module owns selection,
9
+ # YamlParsing owns the calls). The default stays :psych until the
10
+ # yeptris Psych-safe_load parity gaps close (yeptris-ruby#29 empty
11
+ # documents crash, #30 sexagesimal scalars, #31 >64-bit integers);
12
+ # yeptris is 3.1x faster at loading (measured, 2,000-item document),
13
+ # so CANON_YAML_BACKEND=yeptris opts in early and the default flips
14
+ # once the parity spec runs clean.
15
+ #
16
+ # Only the namespaced API (Yeptris::YAML) is ever used — requiring
17
+ # "yeptris/psych" rebinds the global ::Psych constant for the whole
18
+ # process, which a library must never do.
19
+ module YamlBackend
20
+ VALID_BACKENDS = %i[psych yeptris].freeze
21
+
22
+ class << self
23
+ def active
24
+ @active ||= begin
25
+ wanted = forced || :psych
26
+ # A forced yeptris without a loadable gem/native lib must
27
+ # degrade to Psych, not NameError in the gateway.
28
+ wanted = :psych if wanted == :yeptris && !yeptris_available?
29
+ wanted
30
+ end
31
+ end
32
+
33
+ def psych?
34
+ active == :psych
35
+ end
36
+
37
+ def yeptris?
38
+ active == :yeptris
39
+ end
40
+
41
+ def reset!
42
+ @active = nil
43
+ end
44
+
45
+ def yeptris_available?
46
+ return false if RUBY_ENGINE == "opal"
47
+
48
+ require "yeptris"
49
+ Yeptris::YAML.respond_to?(:load)
50
+ rescue LoadError, StandardError
51
+ false
52
+ end
53
+
54
+ private
55
+
56
+ def forced
57
+ value = ENV["CANON_YAML_BACKEND"].to_s.downcase
58
+ return nil unless VALID_BACKENDS.include?(value.to_sym)
59
+
60
+ value.to_sym
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+
5
+ module Canon
6
+ # The only place canon talks to a YAML engine. All string loads go
7
+ # through safe_load here (see XmlParsing for the XML equivalent);
8
+ # YAML.dump stays on Psych everywhere — canonical output bytes are
9
+ # canon's product and the writers differ.
10
+ module YamlParsing
11
+ module_function
12
+
13
+ # Psych::safe_load semantics. yeptris is safe-by-default plain
14
+ # data with Symbol/Date/Time built in (aliases resolve), so the
15
+ # permitted-class list is inherently satisfied; parse failures are
16
+ # normalized to Psych::SyntaxError so canon's rescues hold on both
17
+ # engines.
18
+ def safe_load(yaml, permitted_classes: [Symbol, Date, Time],
19
+ aliases: false)
20
+ if YamlBackend.yeptris?
21
+ begin
22
+ ::Yeptris::YAML.load(yaml)
23
+ rescue ::Yeptris::ParseError => e
24
+ raise Psych::SyntaxError.new(nil, 0, 0, 0, e.message, "")
25
+ rescue ::FFI::NullPointerError
26
+ # yeptris-ruby#29: empty/comment-only input. Psych returns
27
+ # nil; mirror that until the upstream fix lands.
28
+ nil
29
+ end
30
+ else
31
+ YAML.safe_load(yaml, permitted_classes: permitted_classes,
32
+ aliases: aliases)
33
+ end
34
+ end
35
+ end
36
+ end
data/lib/canon.rb CHANGED
@@ -5,6 +5,8 @@ require "canon/errors"
5
5
  require "nokogiri" unless RUBY_ENGINE == "opal"
6
6
  require "canon/xml_backend"
7
7
  require "canon/xml_parsing"
8
+ require "canon/yaml_backend"
9
+ require "canon/yaml_parsing"
8
10
  require "canon/config"
9
11
  require "canon/data_model"
10
12
  require "canon/xml"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: canon
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.22
4
+ version: 0.3.23
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
@@ -407,6 +407,8 @@ files:
407
407
  - lib/canon/xml/xpath_engine.rb
408
408
  - lib/canon/xml_backend.rb
409
409
  - lib/canon/xml_parsing.rb
410
+ - lib/canon/yaml_backend.rb
411
+ - lib/canon/yaml_parsing.rb
410
412
  - lib/tasks/benchmark_runner.rb
411
413
  - lib/tasks/performance.rake
412
414
  - lib/tasks/performance_comparator.rb