yeptris 0.1.13.3-x86_64-linux
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 +7 -0
- data/README.adoc +126 -0
- data/ext/yeptris_native/extconf.rb +47 -0
- data/ext/yeptris_native/json_ruby.c +330 -0
- data/ext/yeptris_native/yeptris_native.c +438 -0
- data/lib/yeptris/document.rb +209 -0
- data/lib/yeptris/ffi.rb +256 -0
- data/lib/yeptris/json.rb +145 -0
- data/lib/yeptris/materializer.rb +347 -0
- data/lib/yeptris/native.so +0 -0
- data/lib/yeptris/node.rb +354 -0
- data/lib/yeptris/psych/coder_shim.rb +55 -0
- data/lib/yeptris/psych/encodable.rb +14 -0
- data/lib/yeptris/psych/handler.rb +90 -0
- data/lib/yeptris/psych/parser.rb +105 -0
- data/lib/yeptris/psych/visitors.rb +383 -0
- data/lib/yeptris/psych.rb +342 -0
- data/lib/yeptris/valueml.rb +442 -0
- data/lib/yeptris/yaml.rb +278 -0
- data/lib/yeptris.rb +70 -0
- data/libyeptris.so +0 -0
- metadata +82 -0
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "date"
|
|
4
|
+
require "time"
|
|
5
|
+
require "set"
|
|
6
|
+
|
|
7
|
+
# The Psych drop-in namespace (TODO.impl/15 phase C).
|
|
8
|
+
#
|
|
9
|
+
# `require "yeptris/psych"` rebinds the top-level Psych constant to
|
|
10
|
+
# this module (the original, if any, stays reachable as
|
|
11
|
+
# ::Psych::ORIGINAL). Semantics follow the Psych suite: load is
|
|
12
|
+
# SAFE by default (Psych 5 behavior — plain data only; anything
|
|
13
|
+
# tagged raises), unsafe_load materializes everything the yeptris
|
|
14
|
+
# loader understands, and parse returns the Nodes tree over the
|
|
15
|
+
# document without materializing.
|
|
16
|
+
module Yeptris
|
|
17
|
+
module Psych
|
|
18
|
+
# Children load via autoload declared HERE — the immediate parent
|
|
19
|
+
# namespace's file (never internal requires).
|
|
20
|
+
autoload :Handler, "yeptris/psych/handler"
|
|
21
|
+
# Handlers (the Recorder submodule) lives in handler.rb too — its
|
|
22
|
+
# own entry so referencing Psych::Handlers triggers the load
|
|
23
|
+
autoload :Handlers, "yeptris/psych/handler"
|
|
24
|
+
autoload :Parser, "yeptris/psych/parser"
|
|
25
|
+
autoload :CoderShim, "yeptris/psych/coder_shim"
|
|
26
|
+
autoload :Visitors, "yeptris/psych/visitors"
|
|
27
|
+
# The typed opt-in marker for arbitrary-object dump/load
|
|
28
|
+
# (TODO.restructure/23). Eager by intent: classes include it at
|
|
29
|
+
# declaration time, so the autoload must resolve before any
|
|
30
|
+
# object instance exists.
|
|
31
|
+
autoload :Encodable, "yeptris/psych/encodable"
|
|
32
|
+
class Error < StandardError; end
|
|
33
|
+
class SyntaxError < Error
|
|
34
|
+
attr_reader :line, :column
|
|
35
|
+
|
|
36
|
+
def initialize(message, line = 0, column = 0)
|
|
37
|
+
super(message)
|
|
38
|
+
@line = line
|
|
39
|
+
@column = column
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
class BadAlias < Error; end
|
|
43
|
+
class DisallowedClass < Error
|
|
44
|
+
attr_reader :name
|
|
45
|
+
|
|
46
|
+
def initialize(name)
|
|
47
|
+
super("Tried to load unspecified class: #{name}")
|
|
48
|
+
@name = name
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
class AliasNotEnabled < Error; end
|
|
52
|
+
|
|
53
|
+
class << self
|
|
54
|
+
# Psych 5: load is safe — plain data structures only. Tagged
|
|
55
|
+
# nodes raise DisallowedClass unless their class is permitted
|
|
56
|
+
# (Date/Time/Symbol are built in; they are plain data here).
|
|
57
|
+
def load(yaml, permitted_classes: [], aliases: false, **)
|
|
58
|
+
safe_load(yaml, permitted_classes: permitted_classes, aliases: aliases)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Full revival over the Nodes tree: !ruby/object, !ruby/struct,
|
|
62
|
+
# !ruby/set, encode_with/init_with, alias identity. Plain-data
|
|
63
|
+
# loads should use load/safe_load (the Materializer fast path).
|
|
64
|
+
def unsafe_load(yaml, **)
|
|
65
|
+
tree = parse(yaml)
|
|
66
|
+
return nil if tree.nil?
|
|
67
|
+
|
|
68
|
+
Visitors::ToRuby.visit(tree.children.first)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def safe_load(yaml, permitted_classes: [], aliases: false, **)
|
|
72
|
+
doc = Yeptris::Document.parse(yaml, schema: :compat_11)
|
|
73
|
+
begin
|
|
74
|
+
walk_safe(doc.root(0), permitted_classes, aliases)
|
|
75
|
+
ensure
|
|
76
|
+
doc.free
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# The first document's node tree (no Ruby materialization).
|
|
81
|
+
def parse(yaml)
|
|
82
|
+
doc = Yeptris::Document.parse(yaml, schema: :compat_11)
|
|
83
|
+
return nil if doc.document_count.zero?
|
|
84
|
+
|
|
85
|
+
Nodes::Builder.document(doc)
|
|
86
|
+
rescue Yeptris::ParseError => e
|
|
87
|
+
raise SyntaxError, e.message
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def parse_stream(yaml)
|
|
91
|
+
doc = Yeptris::Document.parse(yaml, schema: :compat_11)
|
|
92
|
+
return nil if doc.document_count.zero?
|
|
93
|
+
|
|
94
|
+
stream = Nodes::Stream.new
|
|
95
|
+
(0...doc.document_count).each do |i|
|
|
96
|
+
stream.children << Nodes::Builder.document_stream_child(doc, i)
|
|
97
|
+
end
|
|
98
|
+
stream.owner = doc
|
|
99
|
+
ObjectSpace.define_finalizer(
|
|
100
|
+
stream, proc { doc.free unless doc.freed? }
|
|
101
|
+
)
|
|
102
|
+
stream
|
|
103
|
+
rescue Yeptris::ParseError => e
|
|
104
|
+
raise SyntaxError, e.message
|
|
105
|
+
ensure
|
|
106
|
+
doc&.free if doc && !stream
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Arbitrary object graphs through the YAMLTree visitor
|
|
110
|
+
# (anchors, !ruby/ tags); plain data rides the fast builder.
|
|
111
|
+
def dump(obj, io = nil)
|
|
112
|
+
# scalars take the fast builder; EVERYTHING else (including
|
|
113
|
+
# plain containers — they may nest custom objects) goes
|
|
114
|
+
# through the visitor, which builds the same DOM for plain
|
|
115
|
+
# data anyway
|
|
116
|
+
out =
|
|
117
|
+
case obj
|
|
118
|
+
when nil, true, false, ::String, ::Integer, ::Float, ::Symbol, ::Date, ::Time
|
|
119
|
+
Yeptris::YAML.dump(obj)
|
|
120
|
+
else
|
|
121
|
+
Visitors::YAMLTree.new.push(obj).finish
|
|
122
|
+
end
|
|
123
|
+
return out unless io
|
|
124
|
+
|
|
125
|
+
io.write(out)
|
|
126
|
+
io
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
private
|
|
130
|
+
|
|
131
|
+
# yeptris materializes plain data only — there is nothing
|
|
132
|
+
# unsafe it COULD load. The safety walk enforces what Psych
|
|
133
|
+
# enforces on such documents: aliases need opt-in, and explicit
|
|
134
|
+
# non-core tags raise DisallowedClass (the only "classes" the
|
|
135
|
+
# loader can produce are core-schema types, all permitted).
|
|
136
|
+
def walk_safe(root, permitted, aliases_enabled)
|
|
137
|
+
check(root, aliases_enabled) if root
|
|
138
|
+
root.to_ruby
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def check(node, aliases_enabled)
|
|
142
|
+
case node.kind
|
|
143
|
+
when :alias
|
|
144
|
+
raise AliasNotEnabled, "Unknown alias" unless aliases_enabled
|
|
145
|
+
when :scalar, :mapping, :sequence
|
|
146
|
+
tag = node.tag
|
|
147
|
+
unless tag.nil?
|
|
148
|
+
name = tag.split(":").last
|
|
149
|
+
unless %w[str int float bool null timestamp seq map merge value
|
|
150
|
+
binary].include?(name)
|
|
151
|
+
raise DisallowedClass, name
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
case node.kind
|
|
155
|
+
when :mapping
|
|
156
|
+
node.each_pair do |k, v|
|
|
157
|
+
check(k, aliases_enabled)
|
|
158
|
+
check(v, aliases_enabled)
|
|
159
|
+
end
|
|
160
|
+
when :sequence
|
|
161
|
+
node.each { |e| check(e, aliases_enabled) }
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Psych::Nodes over the yeptris document: the tree IS the parsed
|
|
168
|
+
# document (children are node handles, not copies) — parse cost
|
|
169
|
+
# is the parse, and to_ruby reuses the Materializer.
|
|
170
|
+
module Nodes
|
|
171
|
+
class Node
|
|
172
|
+
include Enumerable
|
|
173
|
+
|
|
174
|
+
attr_reader :children
|
|
175
|
+
attr_reader :handle # @api private — the Yeptris::Node
|
|
176
|
+
# @api private — the tree builder attaches handles; a writer,
|
|
177
|
+
# never instance_variable_set from outside
|
|
178
|
+
attr_writer :handle
|
|
179
|
+
# The Document owning this tree's C memory; a plain writer —
|
|
180
|
+
# never instance_variable_set from outside (encapsulation law).
|
|
181
|
+
attr_accessor :owner
|
|
182
|
+
|
|
183
|
+
def initialize(handle = nil, children = [])
|
|
184
|
+
@handle = handle
|
|
185
|
+
@children = children
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Every node HAS an anchor concept (none by default) — the
|
|
189
|
+
# anchored search needs no type probe, just the model.
|
|
190
|
+
def anchor
|
|
191
|
+
nil
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def each(&block)
|
|
195
|
+
@children.each(&block)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# The Ruby object for this subtree (Materializer semantics).
|
|
199
|
+
def to_ruby
|
|
200
|
+
@handle.to_ruby
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
class Stream < Node
|
|
205
|
+
def free
|
|
206
|
+
@owner&.free
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Owns the underlying Yeptris::Document: node handles in the
|
|
211
|
+
# tree stay valid while the tree is reachable; a GC finalizer
|
|
212
|
+
# releases the C memory when it is not.
|
|
213
|
+
class Document < Node
|
|
214
|
+
attr_reader :version, :tags
|
|
215
|
+
|
|
216
|
+
def initialize(version = [], tags = {})
|
|
217
|
+
super(nil)
|
|
218
|
+
@version = version
|
|
219
|
+
@tags = tags
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# @api private — transfer of C ownership to this tree
|
|
223
|
+
def own(yeptris_doc)
|
|
224
|
+
@owner = yeptris_doc
|
|
225
|
+
ObjectSpace.define_finalizer(
|
|
226
|
+
self, proc { yeptris_doc.free unless yeptris_doc.freed? }
|
|
227
|
+
)
|
|
228
|
+
self
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def free
|
|
232
|
+
@owner&.free
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
class Scalar < Node
|
|
237
|
+
attr_reader :value, :tag, :anchor, :plain, :quoted, :style
|
|
238
|
+
|
|
239
|
+
def initialize(value = nil, anchor: nil, tag: nil, plain: true,
|
|
240
|
+
quoted: false, style: :plain)
|
|
241
|
+
super(nil)
|
|
242
|
+
@value = value
|
|
243
|
+
@anchor = anchor
|
|
244
|
+
@tag = tag
|
|
245
|
+
@plain = plain
|
|
246
|
+
@quoted = quoted
|
|
247
|
+
@style = style
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
class Sequence < Node
|
|
252
|
+
attr_reader :anchor, :tag, :style
|
|
253
|
+
|
|
254
|
+
def initialize(anchor: nil, tag: nil, style: :block)
|
|
255
|
+
super(nil, [])
|
|
256
|
+
@anchor = anchor
|
|
257
|
+
@tag = tag
|
|
258
|
+
@style = style
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
class Mapping < Node
|
|
263
|
+
attr_reader :anchor, :tag, :style
|
|
264
|
+
|
|
265
|
+
def initialize(anchor: nil, tag: nil, style: :block)
|
|
266
|
+
super(nil, [])
|
|
267
|
+
@anchor = anchor
|
|
268
|
+
@tag = tag
|
|
269
|
+
@style = style
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
class Alias < Node
|
|
274
|
+
attr_reader :anchor
|
|
275
|
+
|
|
276
|
+
def initialize(anchor)
|
|
277
|
+
super(nil)
|
|
278
|
+
@anchor = anchor
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
# Builds the Nodes tree from a parsed document.
|
|
283
|
+
module Builder
|
|
284
|
+
module_function
|
|
285
|
+
|
|
286
|
+
# parse(): one document, owning the yeptris document
|
|
287
|
+
def document(doc, index = 0)
|
|
288
|
+
document_stream_child(doc, index).own(doc)
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# parse_stream(): a child document sharing one owner (the
|
|
292
|
+
# stream owns the yeptris document)
|
|
293
|
+
def document_stream_child(doc, index)
|
|
294
|
+
root = doc.root(index)
|
|
295
|
+
d = Document.new
|
|
296
|
+
d.handle = root&.document
|
|
297
|
+
d.children << node(root) if root
|
|
298
|
+
d
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
def node(n)
|
|
302
|
+
case n.kind
|
|
303
|
+
when :mapping
|
|
304
|
+
m = Mapping.new(anchor: n.anchor, tag: n.tag)
|
|
305
|
+
n.each_pair do |k, v|
|
|
306
|
+
m.children << node(k)
|
|
307
|
+
m.children << node(v)
|
|
308
|
+
end
|
|
309
|
+
m.handle = n
|
|
310
|
+
m
|
|
311
|
+
when :sequence
|
|
312
|
+
s = Sequence.new(anchor: n.anchor, tag: n.tag)
|
|
313
|
+
n.each { |e| s.children << node(e) }
|
|
314
|
+
s.handle = n
|
|
315
|
+
s
|
|
316
|
+
when :alias
|
|
317
|
+
a = Alias.new(n.value)
|
|
318
|
+
a.handle = n
|
|
319
|
+
a
|
|
320
|
+
else
|
|
321
|
+
sc = Scalar.new(n.value, anchor: n.anchor, tag: n.tag,
|
|
322
|
+
plain: n.style == :plain, style: n.style)
|
|
323
|
+
sc.handle = n
|
|
324
|
+
sc
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
# The drop-in: rebind the top-level constant ( Psych-stdlib, if
|
|
333
|
+
# already loaded, stays reachable as Yeptris::Psych::ORIGINAL).
|
|
334
|
+
if defined?(::Psych) && !::Psych.equal?(Yeptris::Psych) &&
|
|
335
|
+
!Yeptris::Psych.const_defined?(:ORIGINAL, false)
|
|
336
|
+
Yeptris::Psych.const_set(:ORIGINAL, ::Psych)
|
|
337
|
+
end
|
|
338
|
+
# class_eval reaches Module-private methods WITHOUT send (the law:
|
|
339
|
+
# no send to private methods); remove_const has no public form, and
|
|
340
|
+
# the rebind is this namespace's whole purpose
|
|
341
|
+
Object.class_eval { remove_const(:Psych) } if defined?(::Psych)
|
|
342
|
+
::Psych = Yeptris::Psych
|