rapidyaml_rb 0.1.0

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.
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rapidyaml_rb"
4
+
5
+ class Object
6
+ def to_yaml
7
+ RapidyamlRb.dump(self)
8
+ end
9
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+ require "rapidyaml_rb"
5
+
6
+ module YAML
7
+ class << self
8
+ def load(yaml, symbolize_names: false, permitted_classes: [], **_)
9
+ RapidyamlRb.load(yaml, symbolize_names: symbolize_names, permitted_classes: permitted_classes)
10
+ end
11
+
12
+ def safe_load(yaml, symbolize_names: false, permitted_classes: [], **_)
13
+ RapidyamlRb.safe_load(yaml, symbolize_names: symbolize_names, permitted_classes: permitted_classes)
14
+ end
15
+
16
+ def unsafe_load(yaml, symbolize_names: false, permitted_classes: [], **_)
17
+ RapidyamlRb.unsafe_load(yaml, symbolize_names: symbolize_names, permitted_classes: permitted_classes)
18
+ end
19
+
20
+ def dump(obj, **)
21
+ RapidyamlRb.dump(obj)
22
+ end
23
+
24
+ def safe_dump(obj, **)
25
+ RapidyamlRb.safe_dump(obj)
26
+ end
27
+
28
+ def dump_stream(*objects)
29
+ RapidyamlRb.dump_stream(*objects)
30
+ end
31
+
32
+ def load_stream(yaml, symbolize_names: false, permitted_classes: [], **_, &block)
33
+ RapidyamlRb.load_stream(yaml, symbolize_names: symbolize_names, permitted_classes: permitted_classes, &block)
34
+ end
35
+
36
+ def safe_load_stream(yaml, symbolize_names: false, permitted_classes: [], **_, &block)
37
+ RapidyamlRb.safe_load_stream(yaml, symbolize_names: symbolize_names, permitted_classes: permitted_classes, &block)
38
+ end
39
+
40
+ def load_file(path, symbolize_names: false, permitted_classes: [], **_)
41
+ RapidyamlRb.load_file(path, symbolize_names: symbolize_names, permitted_classes: permitted_classes)
42
+ end
43
+
44
+ def unsafe_load_file(path, symbolize_names: false, permitted_classes: [], **_)
45
+ RapidyamlRb.unsafe_load_file(path, symbolize_names: symbolize_names, permitted_classes: permitted_classes)
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Optional core extensions. Load explicitly:
4
+ # require "rapidyaml_rb/core_ext"
5
+ #
6
+ # Provides:
7
+ # Object#to_yaml — serializes any object via RapidyamlRb.dump
8
+ # YAML.* — patches load/dump family to use RapidyamlRb
9
+
10
+ require_relative "core_ext/object"
11
+ require_relative "core_ext/yaml"
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RapidyamlRb
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,225 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "psych"
4
+ require_relative "rapidyaml_rb/version"
5
+
6
+ begin
7
+ RUBY_VERSION =~ /(\d+\.\d+)/
8
+ require_relative "rapidyaml_rb/#{Regexp.last_match(1)}/rapidyaml_rb"
9
+ rescue LoadError
10
+ require "rapidyaml_rb/rapidyaml_rb"
11
+ end
12
+
13
+ module RapidyamlRb
14
+ # Error and SyntaxError are defined in the C extension (Init_rapidyaml_rb).
15
+ # ParseError is kept as an alias for backward compatibility.
16
+ ParseError = SyntaxError
17
+
18
+ # Regex patterns for scalar coercion during load.
19
+ # Only applied when the corresponding class is in permitted_classes.
20
+ DATE_RE = /\A(\d{4})-(\d{2})-(\d{2})\z/
21
+ DATETIME_RE = /\A\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?: ?(?:Z|[+-]\d{2}:?\d{2}))?\z/
22
+
23
+ module_function
24
+
25
+ # Parses a YAML string and returns the corresponding Ruby object.
26
+ #
27
+ # @param yaml [String] YAML-formatted string
28
+ # @param symbolize_names [Boolean] if true, hash keys are returned as symbols
29
+ # @param permitted_classes [Array<Class>] classes allowed for type coercion
30
+ # (supports Symbol, Date, Time)
31
+ # @return [Object] parsed Ruby object
32
+ # @raise [RapidyamlRb::SyntaxError] if the YAML is invalid
33
+ def load(yaml, symbolize_names: false, permitted_classes: [])
34
+ result = Ext.parse(yaml)
35
+ result = coerce_scalars(result, permitted_classes) unless permitted_classes.empty?
36
+ symbolize_names ? deep_symbolize_keys(result) : result
37
+ end
38
+
39
+ # Alias matching Psych.safe_load; rapidyaml does not execute arbitrary code,
40
+ # so this is equivalent to load for all practical purposes.
41
+ #
42
+ # @param yaml [String] YAML-formatted string
43
+ # @param symbolize_names [Boolean] if true, hash keys are returned as symbols
44
+ # @param permitted_classes [Array<Class>] classes allowed for type coercion
45
+ # @return [Object] parsed Ruby object
46
+ # @raise [RapidyamlRb::SyntaxError] if the YAML is invalid
47
+ def safe_load(yaml, symbolize_names: false, permitted_classes: [])
48
+ load(yaml, symbolize_names: symbolize_names, permitted_classes: permitted_classes)
49
+ end
50
+
51
+ # Serializes a Ruby object to a YAML string.
52
+ #
53
+ # @param obj [Object] Ruby object to serialize
54
+ # @return [String] YAML representation
55
+ # @raise [RapidyamlRb::Error] if the object cannot be serialized
56
+ def dump(obj)
57
+ Ext.emit(obj)
58
+ end
59
+
60
+ # Serializes multiple Ruby objects into a multi-document YAML stream.
61
+ #
62
+ # @param objects [Array<Object>] Ruby objects to serialize
63
+ # @return [String] YAML stream with one document per object
64
+ # @raise [RapidyamlRb::Error] if any object cannot be serialized
65
+ def dump_stream(*objects)
66
+ objects.map { |obj|
67
+ s = dump(obj)
68
+ obj.is_a?(Hash) || obj.is_a?(Array) ? "---\n#{s}" : "--- #{s}"
69
+ }.join
70
+ end
71
+
72
+ # Equivalent to dump; rapidyaml does not serialize arbitrary Ruby objects,
73
+ # so there is no unsafe surface to restrict.
74
+ #
75
+ # @param obj [Object] Ruby object to serialize
76
+ # @return [String] YAML representation
77
+ def safe_dump(obj)
78
+ dump(obj)
79
+ end
80
+
81
+ # Returns the version string of the bundled rapidyaml C++ library.
82
+ # Psych exposes libyaml_version; this is the equivalent for rapidyaml.
83
+ #
84
+ # @return [String] rapidyaml version, e.g. "0.13.0"
85
+ def libyaml_version
86
+ Ext.ryml_version
87
+ end
88
+
89
+ # Parses all YAML documents in a string and yields each as a Ruby object.
90
+ # If no block given, returns an array of all documents.
91
+ #
92
+ # @param yaml [String] YAML-formatted string (may contain multiple documents)
93
+ # @param symbolize_names [Boolean] if true, hash keys are returned as symbols
94
+ # @param permitted_classes [Array<Class>] classes allowed for type coercion
95
+ # @yield [Object] each parsed document
96
+ # @return [Array, nil] array of documents if no block given
97
+ def load_stream(yaml, symbolize_names: false, permitted_classes: [], &block)
98
+ result = Ext.parse_stream(yaml)
99
+ docs = result.is_a?(Array) ? result : [result]
100
+ docs = docs.map { |d| coerce_scalars(d, permitted_classes) } unless permitted_classes.empty?
101
+ docs = docs.map { |d| deep_symbolize_keys(d) } if symbolize_names
102
+ if block
103
+ docs.each(&block)
104
+ nil
105
+ else
106
+ docs
107
+ end
108
+ end
109
+
110
+ # @see load_stream
111
+ def safe_load_stream(yaml, symbolize_names: false, permitted_classes: [], &block)
112
+ load_stream(yaml, symbolize_names: symbolize_names,
113
+ permitted_classes: permitted_classes, &block)
114
+ end
115
+
116
+ # Alias matching Psych.unsafe_load; rapidyaml never executes arbitrary Ruby
117
+ # object tags, so this is equivalent to load.
118
+ #
119
+ # @param yaml [String] YAML-formatted string
120
+ # @param symbolize_names [Boolean] if true, hash keys are returned as symbols
121
+ # @param permitted_classes [Array<Class>] classes allowed for type coercion
122
+ # @return [Object] parsed Ruby object
123
+ def unsafe_load(yaml, symbolize_names: false, permitted_classes: [])
124
+ load(yaml, symbolize_names: symbolize_names, permitted_classes: permitted_classes)
125
+ end
126
+
127
+ # Alias matching Psych.unsafe_load_file; rapidyaml never executes arbitrary
128
+ # Ruby object tags, so this is equivalent to load_file.
129
+ #
130
+ # @param path [String] path to the YAML file
131
+ # @param symbolize_names [Boolean] if true, hash keys are returned as symbols
132
+ # @param permitted_classes [Array<Class>] classes allowed for type coercion
133
+ # @return [Object] parsed Ruby object
134
+ def unsafe_load_file(path, symbolize_names: false, permitted_classes: [])
135
+ load_file(path, symbolize_names: symbolize_names, permitted_classes: permitted_classes)
136
+ end
137
+
138
+ # Parses YAML from a file and returns the corresponding Ruby object.
139
+ #
140
+ # @param path [String] path to the YAML file
141
+ # @param symbolize_names [Boolean] if true, hash keys are returned as symbols
142
+ # @param permitted_classes [Array<Class>] classes allowed for type coercion
143
+ # @return [Object] parsed Ruby object
144
+ # @raise [RapidyamlRb::SyntaxError] if the YAML is invalid
145
+ # @raise [Errno::ENOENT] if the file does not exist
146
+ def load_file(path, symbolize_names: false, permitted_classes: [])
147
+ load(File.read(path), symbolize_names: symbolize_names,
148
+ permitted_classes: permitted_classes)
149
+ end
150
+
151
+ # Serializes a Ruby object to a YAML file.
152
+ #
153
+ # @param obj [Object] Ruby object to serialize
154
+ # @param path [String] destination file path
155
+ # @return [void]
156
+ # @raise [RapidyamlRb::Error] if the object cannot be serialized
157
+ def dump_file(obj, path)
158
+ File.write(path, dump(obj))
159
+ end
160
+
161
+ # @api private
162
+ def coerce_scalars(obj, permitted_classes)
163
+ permit_symbol = permitted_classes.any? { |c| c == Symbol || c.to_s == "Symbol" }
164
+ permit_date = permitted_classes.any? { |c| c == Date || c.to_s == "Date" }
165
+ permit_time = permitted_classes.any? { |c| c == Time || c.to_s == "Time" }
166
+ coerce_scalars_inner(obj, permit_symbol, permit_date, permit_time)
167
+ end
168
+ private_class_method :coerce_scalars
169
+
170
+ # @api private
171
+ def coerce_scalars_inner(obj, permit_symbol, permit_date, permit_time)
172
+ case obj
173
+ when Hash
174
+ obj.transform_keys { |k| coerce_scalars_inner(k, permit_symbol, permit_date, permit_time) }
175
+ .transform_values { |v| coerce_scalars_inner(v, permit_symbol, permit_date, permit_time) }
176
+ when Array
177
+ obj.map { |v| coerce_scalars_inner(v, permit_symbol, permit_date, permit_time) }
178
+ when String
179
+ coerce_string(obj, permit_symbol, permit_date, permit_time)
180
+ else
181
+ obj
182
+ end
183
+ end
184
+ private_class_method :coerce_scalars_inner
185
+
186
+ # @api private
187
+ def coerce_string(str, permit_symbol, permit_date, permit_time)
188
+ if permit_symbol && str.start_with?(":")
189
+ name = str[1..]
190
+ return name.empty? ? :"" : name.to_sym
191
+ end
192
+ if permit_time && str =~ DATETIME_RE && str.include?(":")
193
+ begin
194
+ require "time"
195
+ t = Time.parse(str)
196
+ return t
197
+ rescue ArgumentError
198
+ # fall through
199
+ end
200
+ end
201
+ if permit_date && str =~ DATE_RE
202
+ begin
203
+ require "date"
204
+ return Date.new($1.to_i, $2.to_i, $3.to_i)
205
+ rescue ArgumentError
206
+ # fall through
207
+ end
208
+ end
209
+ str
210
+ end
211
+ private_class_method :coerce_string
212
+
213
+ # @api private
214
+ def deep_symbolize_keys(obj)
215
+ case obj
216
+ when Hash
217
+ obj.transform_keys(&:to_sym).transform_values { |v| deep_symbolize_keys(v) }
218
+ when Array
219
+ obj.map { |v| deep_symbolize_keys(v) }
220
+ else
221
+ obj
222
+ end
223
+ end
224
+ private_class_method :deep_symbolize_keys
225
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/rapidyaml_rb/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "rapidyaml_rb"
7
+ spec.version = RapidyamlRb::VERSION
8
+ spec.authors = ["Durable Programming LLC"]
9
+ spec.email = ["commercial@durableprogramming.com"]
10
+ spec.summary = "Fast YAML parsing and emission via rapidyaml — drop-in Psych replacement"
11
+ spec.description = "Ruby bindings to rapidyaml (ryml), a C++ YAML library. " \
12
+ "Exposes a Psych-compatible API so it can be used as a drop-in replacement."
13
+ spec.homepage = "https://github.com/durableprogramming/rapidyaml-rb"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = ">= 3.1.0"
16
+
17
+ spec.metadata = {
18
+ "homepage_uri" => spec.homepage,
19
+ "source_code_uri" => spec.homepage,
20
+ "changelog_uri" => "#{spec.homepage}/blob/main/CHANGELOG.md"
21
+ }
22
+
23
+ spec.files = Dir[
24
+ "lib/**/*",
25
+ "ext/**/*",
26
+ "LICENSE",
27
+ "rapidyaml_rb.gemspec"
28
+ ].reject { |f| File.directory?(f) }
29
+
30
+ spec.extensions = ["ext/rapidyaml_rb/extconf.rb"]
31
+
32
+ spec.add_development_dependency "rake", "~> 13.0"
33
+ spec.add_development_dependency "rake-compiler", "~> 1.2"
34
+ spec.add_development_dependency "rake-compiler-dock", "~> 1.4"
35
+ spec.add_development_dependency "minitest", "~> 5.0"
36
+ spec.add_development_dependency "rice", "~> 4.0"
37
+ spec.add_development_dependency "rubocop", "> 1.86.0"
38
+ end
metadata ADDED
@@ -0,0 +1,139 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rapidyaml_rb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Durable Programming LLC
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-01 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rake
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '13.0'
19
+ type: :development
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '13.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: rake-compiler
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '1.2'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.2'
40
+ - !ruby/object:Gem::Dependency
41
+ name: rake-compiler-dock
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '1.4'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '1.4'
54
+ - !ruby/object:Gem::Dependency
55
+ name: minitest
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '5.0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '5.0'
68
+ - !ruby/object:Gem::Dependency
69
+ name: rice
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '4.0'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '4.0'
82
+ - !ruby/object:Gem::Dependency
83
+ name: rubocop
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">"
87
+ - !ruby/object:Gem::Version
88
+ version: 1.86.0
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">"
94
+ - !ruby/object:Gem::Version
95
+ version: 1.86.0
96
+ description: Ruby bindings to rapidyaml (ryml), a C++ YAML library. Exposes a Psych-compatible
97
+ API so it can be used as a drop-in replacement.
98
+ email:
99
+ - commercial@durableprogramming.com
100
+ executables: []
101
+ extensions:
102
+ - ext/rapidyaml_rb/extconf.rb
103
+ extra_rdoc_files: []
104
+ files:
105
+ - LICENSE
106
+ - ext/rapidyaml_rb/extconf.rb
107
+ - ext/rapidyaml_rb/rapidyaml_rb.cpp
108
+ - ext/rapidyaml_rb/ryml_all.hpp
109
+ - lib/rapidyaml_rb.rb
110
+ - lib/rapidyaml_rb/core_ext.rb
111
+ - lib/rapidyaml_rb/core_ext/object.rb
112
+ - lib/rapidyaml_rb/core_ext/yaml.rb
113
+ - lib/rapidyaml_rb/version.rb
114
+ - rapidyaml_rb.gemspec
115
+ homepage: https://github.com/durableprogramming/rapidyaml-rb
116
+ licenses:
117
+ - MIT
118
+ metadata:
119
+ homepage_uri: https://github.com/durableprogramming/rapidyaml-rb
120
+ source_code_uri: https://github.com/durableprogramming/rapidyaml-rb
121
+ changelog_uri: https://github.com/durableprogramming/rapidyaml-rb/blob/main/CHANGELOG.md
122
+ rdoc_options: []
123
+ require_paths:
124
+ - lib
125
+ required_ruby_version: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - ">="
128
+ - !ruby/object:Gem::Version
129
+ version: 3.1.0
130
+ required_rubygems_version: !ruby/object:Gem::Requirement
131
+ requirements:
132
+ - - ">="
133
+ - !ruby/object:Gem::Version
134
+ version: '0'
135
+ requirements: []
136
+ rubygems_version: 3.7.2
137
+ specification_version: 4
138
+ summary: Fast YAML parsing and emission via rapidyaml — drop-in Psych replacement
139
+ test_files: []