yeptris 0.6.18.2-arm-linux-eabihf
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 +141 -0
- data/ext/build_windows_native.rb +41 -0
- data/ext/libyeptris/extconf.rb +57 -0
- data/ext/yeptris_native/cbor_ruby.c +193 -0
- data/ext/yeptris_native/extconf.rb +47 -0
- data/ext/yeptris_native/json_ruby.c +354 -0
- data/ext/yeptris_native/yeptris_native.c +497 -0
- data/lib/yeptris/cbor.rb +130 -0
- data/lib/yeptris/document.rb +255 -0
- data/lib/yeptris/ffi.rb +426 -0
- data/lib/yeptris/json/descriptor.rb +266 -0
- data/lib/yeptris/json.rb +394 -0
- data/lib/yeptris/materializer.rb +354 -0
- data/lib/yeptris/native-3.4.so +0 -0
- data/lib/yeptris/node.rb +453 -0
- data/lib/yeptris/psych/class_loader.rb +24 -0
- data/lib/yeptris/psych/coder_shim.rb +55 -0
- data/lib/yeptris/psych/drop_in.rb +63 -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/scalar_scanner.rb +149 -0
- data/lib/yeptris/psych/visitors.rb +545 -0
- data/lib/yeptris/psych.rb +622 -0
- data/lib/yeptris/schema.rb +253 -0
- data/lib/yeptris/valueml.rb +455 -0
- data/lib/yeptris/yaml/descriptor.rb +56 -0
- data/lib/yeptris/yaml.rb +422 -0
- data/lib/yeptris.rb +113 -0
- data/libyeptris.so +0 -0
- data/vendor/libyeptris/CMakeLists.txt +254 -0
- data/vendor/libyeptris/cmake/yeptris-config.cmake.in +5 -0
- data/vendor/libyeptris/cmake/yeptris.pc.in +11 -0
- data/vendor/libyeptris/src/CMakeLists.txt +167 -0
- data/vendor/libyeptris/src/include/yeptris/api.h +32 -0
- data/vendor/libyeptris/src/include/yeptris/cbor.h +101 -0
- data/vendor/libyeptris/src/include/yeptris/dom.h +190 -0
- data/vendor/libyeptris/src/include/yeptris/emit.h +81 -0
- data/vendor/libyeptris/src/include/yeptris/error.h +41 -0
- data/vendor/libyeptris/src/include/yeptris/events.h +157 -0
- data/vendor/libyeptris/src/include/yeptris/json.h +45 -0
- data/vendor/libyeptris/src/include/yeptris/json.hpp +258 -0
- data/vendor/libyeptris/src/include/yeptris/jsonc_compat.h +115 -0
- data/vendor/libyeptris/src/include/yeptris/marshal.h +52 -0
- data/vendor/libyeptris/src/include/yeptris/parse.h +48 -0
- data/vendor/libyeptris/src/include/yeptris/plan.h +100 -0
- data/vendor/libyeptris/src/include/yeptris/resolve.h +71 -0
- data/vendor/libyeptris/src/include/yeptris/schema.h +121 -0
- data/vendor/libyeptris/src/include/yeptris/tape.h +123 -0
- data/vendor/libyeptris/src/include/yeptris/types.h +38 -0
- data/vendor/libyeptris/src/include/yeptris/values.h +102 -0
- data/vendor/libyeptris/src/include/yeptris/version.h.in +29 -0
- data/vendor/libyeptris/src/include/yeptris/visit.h +77 -0
- data/vendor/libyeptris/src/include/yeptris/yajl_compat.h +135 -0
- data/vendor/libyeptris/src/include/yeptris.h +23 -0
- data/vendor/libyeptris/src/yeptris/build.c +376 -0
- data/vendor/libyeptris/src/yeptris/cbor/cbor.h +25 -0
- data/vendor/libyeptris/src/yeptris/cbor/decode.c +981 -0
- data/vendor/libyeptris/src/yeptris/cbor/encode.c +835 -0
- data/vendor/libyeptris/src/yeptris/cbor/sink.h +58 -0
- data/vendor/libyeptris/src/yeptris/common/chartype.c +41 -0
- data/vendor/libyeptris/src/yeptris/common/chartype.h +77 -0
- data/vendor/libyeptris/src/yeptris/common/cpu.c +81 -0
- data/vendor/libyeptris/src/yeptris/common/cpu.h +40 -0
- data/vendor/libyeptris/src/yeptris/common/error.c +65 -0
- data/vendor/libyeptris/src/yeptris/common/error.h +79 -0
- data/vendor/libyeptris/src/yeptris/common/mutex.h +45 -0
- data/vendor/libyeptris/src/yeptris/common/nametab.c +275 -0
- data/vendor/libyeptris/src/yeptris/common/nametab.h +68 -0
- data/vendor/libyeptris/src/yeptris/common/port.h +74 -0
- data/vendor/libyeptris/src/yeptris/common/simd_text.c +49 -0
- data/vendor/libyeptris/src/yeptris/common/simd_text.h +295 -0
- data/vendor/libyeptris/src/yeptris/common/simd_text_avx2.c +534 -0
- data/vendor/libyeptris/src/yeptris/common/simd_text_neon.c +662 -0
- data/vendor/libyeptris/src/yeptris/common/simd_text_scalar.c +298 -0
- data/vendor/libyeptris/src/yeptris/common/string_view.c +34 -0
- data/vendor/libyeptris/src/yeptris/common/string_view.h +93 -0
- data/vendor/libyeptris/src/yeptris/doc.h +56 -0
- data/vendor/libyeptris/src/yeptris/dom/dom.c +1192 -0
- data/vendor/libyeptris/src/yeptris/dom/dom.h +312 -0
- data/vendor/libyeptris/src/yeptris/dom/hpool.c +107 -0
- data/vendor/libyeptris/src/yeptris/dom/mapindex.c +173 -0
- data/vendor/libyeptris/src/yeptris/dom/mapindex.h +61 -0
- data/vendor/libyeptris/src/yeptris/dom/mutate.c +431 -0
- data/vendor/libyeptris/src/yeptris/emit/emit.c +434 -0
- data/vendor/libyeptris/src/yeptris/emit/float/api.h +38 -0
- data/vendor/libyeptris/src/yeptris/emit/float/dragon.c +405 -0
- data/vendor/libyeptris/src/yeptris/emit/float/floatint.h +231 -0
- data/vendor/libyeptris/src/yeptris/emit/float/print.c +396 -0
- data/vendor/libyeptris/src/yeptris/emit/style.c +81 -0
- data/vendor/libyeptris/src/yeptris/emit/style.h +24 -0
- data/vendor/libyeptris/src/yeptris/emit/writer.c +816 -0
- data/vendor/libyeptris/src/yeptris/emit/writer.h +76 -0
- data/vendor/libyeptris/src/yeptris/encoding/bom.c +31 -0
- data/vendor/libyeptris/src/yeptris/encoding/encoding.h +63 -0
- data/vendor/libyeptris/src/yeptris/encoding/transcode.c +148 -0
- data/vendor/libyeptris/src/yeptris/encoding/utf8_validate.c +178 -0
- data/vendor/libyeptris/src/yeptris/events/capture.c +150 -0
- data/vendor/libyeptris/src/yeptris/events/capture.h +39 -0
- data/vendor/libyeptris/src/yeptris/events/iterparse.c +208 -0
- data/vendor/libyeptris/src/yeptris/events/pull.c +97 -0
- data/vendor/libyeptris/src/yeptris/events/push.c +104 -0
- data/vendor/libyeptris/src/yeptris/events/recorder.c +107 -0
- data/vendor/libyeptris/src/yeptris/events/values.c +527 -0
- data/vendor/libyeptris/src/yeptris/events/values_priv.h +51 -0
- data/vendor/libyeptris/src/yeptris/events/yaml_compat.c +86 -0
- data/vendor/libyeptris/src/yeptris/events/yaml_compat.h +90 -0
- data/vendor/libyeptris/src/yeptris/jsonapi/jsonc_compat.c +687 -0
- data/vendor/libyeptris/src/yeptris/jsonapi/yajl_compat.c +728 -0
- data/vendor/libyeptris/src/yeptris/marshal.c +782 -0
- data/vendor/libyeptris/src/yeptris/memory/allocator.c +20 -0
- data/vendor/libyeptris/src/yeptris/memory/allocator.h +43 -0
- data/vendor/libyeptris/src/yeptris/memory/arena.c +134 -0
- data/vendor/libyeptris/src/yeptris/memory/arena.h +52 -0
- data/vendor/libyeptris/src/yeptris/memory/pool.c +122 -0
- data/vendor/libyeptris/src/yeptris/memory/pool.h +36 -0
- data/vendor/libyeptris/src/yeptris/parse/engine.c +3698 -0
- data/vendor/libyeptris/src/yeptris/parse/engine.h +65 -0
- data/vendor/libyeptris/src/yeptris/parse/events.h +124 -0
- data/vendor/libyeptris/src/yeptris/parse/numbers.c +349 -0
- data/vendor/libyeptris/src/yeptris/parse/numbers.h +33 -0
- data/vendor/libyeptris/src/yeptris/parse/scalars.c +416 -0
- data/vendor/libyeptris/src/yeptris/parse/scalars.h +75 -0
- data/vendor/libyeptris/src/yeptris/parse.c +737 -0
- data/vendor/libyeptris/src/yeptris/plan.c +824 -0
- data/vendor/libyeptris/src/yeptris/resolve/compat11.c +334 -0
- data/vendor/libyeptris/src/yeptris/resolve/core12.c +143 -0
- data/vendor/libyeptris/src/yeptris/resolve/resolver.h +56 -0
- data/vendor/libyeptris/src/yeptris/resolve/tags.c +58 -0
- data/vendor/libyeptris/src/yeptris/scan/json.c +792 -0
- data/vendor/libyeptris/src/yeptris/scan/json.h +109 -0
- data/vendor/libyeptris/src/yeptris/scan/scan.c +463 -0
- data/vendor/libyeptris/src/yeptris/scan/scan.h +150 -0
- data/vendor/libyeptris/src/yeptris/schema.c +308 -0
- data/vendor/libyeptris/src/yeptris/tape.c +1308 -0
- data/vendor/libyeptris/src/yeptris/tape_in.h +19 -0
- data/vendor/libyeptris/src/yeptris/version.c +7 -0
- data/vendor/libyeptris/src/yeptris/visit/dom_visit.c +115 -0
- data/vendor/libyeptris/src/yeptris/visit/json_visit.c +293 -0
- data/vendor/libyeptris/src/yeptris/visit/yaml_visit.c +127 -0
- metadata +199 -0
|
@@ -0,0 +1,622 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "date"
|
|
4
|
+
require "time"
|
|
5
|
+
require "set"
|
|
6
|
+
|
|
7
|
+
# Self-sufficient (the #95 yaml.rb fix, same class): the standalone
|
|
8
|
+
# require "yeptris/psych" must register the Document/FFI machinery —
|
|
9
|
+
# consumers load this face directly under the drop-in.
|
|
10
|
+
require "yeptris"
|
|
11
|
+
|
|
12
|
+
# The Psych drop-in namespace (TODO.impl/15 phase C).
|
|
13
|
+
#
|
|
14
|
+
# `require "yeptris/psych"` loads this namespace WITHOUT touching the
|
|
15
|
+
# top-level Psych constant (co-existence, issue #69); the process-
|
|
16
|
+
# exclusive drop-in rebind is `require "yeptris/psych/drop_in"` (the
|
|
17
|
+
# original stdlib, if loaded first, stays reachable as
|
|
18
|
+
# ::Psych::ORIGINAL). Semantics follow the Psych suite: load is
|
|
19
|
+
# SAFE by default (Psych 5 behavior — plain data only; anything
|
|
20
|
+
# tagged raises), unsafe_load materializes everything the yeptris
|
|
21
|
+
# loader understands, and parse returns the Nodes tree over the
|
|
22
|
+
# document without materializing.
|
|
23
|
+
module Yeptris
|
|
24
|
+
module Psych
|
|
25
|
+
autoload :ClassLoader, "yeptris/psych/class_loader"
|
|
26
|
+
autoload :ScalarScanner, "yeptris/psych/scalar_scanner"
|
|
27
|
+
|
|
28
|
+
# The tag registries (Psych's class-level API, #95 bug 4):
|
|
29
|
+
# load_tags maps a serialized tag to the Class that revives it;
|
|
30
|
+
# dump_tags overrides the emitted tag for a Class. Consulted by
|
|
31
|
+
# the revival visitor (custom tags) and the dumper's tag choice.
|
|
32
|
+
class << self
|
|
33
|
+
def load_tags
|
|
34
|
+
@load_tags ||= {}
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def load_tags=(tags)
|
|
38
|
+
@load_tags = tags
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def dump_tags
|
|
42
|
+
@dump_tags ||= {}
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def dump_tags=(tags)
|
|
46
|
+
@dump_tags = tags
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# The third registry (stdlib psych carries all three; the rebind
|
|
50
|
+
# broke relaton/lutaml callers that wrote it — the 26x
|
|
51
|
+
# undefined-method report). Keys are normalized tag strings
|
|
52
|
+
# ("tag:DOMAIN:TYPE"), values are [key, block] post-processors.
|
|
53
|
+
def domain_types
|
|
54
|
+
@domain_types ||= {}
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def domain_types=(types)
|
|
58
|
+
@domain_types = types
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def add_domain_type(domain, type_tag, &block)
|
|
62
|
+
key = ["tag", domain, type_tag].join(":")
|
|
63
|
+
domain_types[key] = [key, block]
|
|
64
|
+
domain_types["tag:#{type_tag}"] = [key, block]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def add_builtin_type(type_tag, &block)
|
|
68
|
+
key = ["tag", "yaml.org,2002", type_tag].join(":")
|
|
69
|
+
domain_types[key] = [key, block]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def remove_type(type_tag)
|
|
73
|
+
domain_types.delete type_tag
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def add_tag(tag, klass)
|
|
77
|
+
load_tags[tag] = klass.name
|
|
78
|
+
dump_tags[klass] = tag
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Children load via autoload declared HERE — the immediate parent
|
|
83
|
+
# namespace's file (never internal requires).
|
|
84
|
+
autoload :Handler, "yeptris/psych/handler"
|
|
85
|
+
# Handlers (the Recorder submodule) lives in handler.rb too — its
|
|
86
|
+
# own entry so referencing Psych::Handlers triggers the load
|
|
87
|
+
autoload :Handlers, "yeptris/psych/handler"
|
|
88
|
+
autoload :Parser, "yeptris/psych/parser"
|
|
89
|
+
autoload :CoderShim, "yeptris/psych/coder_shim"
|
|
90
|
+
autoload :Visitors, "yeptris/psych/visitors"
|
|
91
|
+
# The typed opt-in marker for arbitrary-object dump/load
|
|
92
|
+
# (TODO.restructure/23). Eager by intent: classes include it at
|
|
93
|
+
# declaration time, so the autoload must resolve before any
|
|
94
|
+
# object instance exists.
|
|
95
|
+
autoload :Encodable, "yeptris/psych/encodable"
|
|
96
|
+
class Error < StandardError; end
|
|
97
|
+
|
|
98
|
+
# stdlib psych 5: a *foo with no matching &foo anchor
|
|
99
|
+
class AnchorNotDefined < Error; end
|
|
100
|
+
# Psych's exact interface (issue #32): same constructor arity,
|
|
101
|
+
# same reader set (file/line/column/offset/problem/context), same
|
|
102
|
+
# message shape — drop-in consumers' rescues and constructors
|
|
103
|
+
# keep working after the rebind.
|
|
104
|
+
class SyntaxError < Error
|
|
105
|
+
attr_reader :file, :line, :column, :offset, :problem, :context
|
|
106
|
+
|
|
107
|
+
def initialize(file = nil, line = 0, column = 0, offset = 0, problem = nil, context = nil)
|
|
108
|
+
@file = file
|
|
109
|
+
@line = line
|
|
110
|
+
@column = column
|
|
111
|
+
@offset = offset
|
|
112
|
+
@problem = problem
|
|
113
|
+
@context = context
|
|
114
|
+
where = file ? "(#{file})" : "(<unknown>)"
|
|
115
|
+
detail = context ? "#{problem} #{context}" : problem.to_s
|
|
116
|
+
super("#{where}: #{detail} at line #{line} column #{column}")
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Structured lift from the C parser's message (carries
|
|
120
|
+
# "line L, column C" detail).
|
|
121
|
+
def self.from_parse_error(error)
|
|
122
|
+
md = /\bline (\d+),? column (\d+)/.match(error.message)
|
|
123
|
+
new(nil, md ? md[1].to_i : 0, md ? md[2].to_i : 0, 0, error.message)
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
class BadAlias < Error; end
|
|
127
|
+
class DisallowedClass < Error
|
|
128
|
+
attr_reader :name
|
|
129
|
+
|
|
130
|
+
def initialize(name)
|
|
131
|
+
super("Tried to load unspecified class: #{name}")
|
|
132
|
+
@name = name
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
# Psych spells it Psych::AliasesError; the older name stays as an
|
|
136
|
+
# alias for existing rescues.
|
|
137
|
+
class AliasesError < Error; end
|
|
138
|
+
AliasNotEnabled = AliasesError
|
|
139
|
+
|
|
140
|
+
class << self
|
|
141
|
+
# Psych 5: load is safe — plain data structures only. Tagged
|
|
142
|
+
# nodes raise DisallowedClass unless their class is permitted
|
|
143
|
+
# (Date/Time/Symbol are built in; they are plain data here).
|
|
144
|
+
def load(yaml, permitted_classes: [::Date, ::Time], aliases: false, **)
|
|
145
|
+
safe_load(yaml, permitted_classes: permitted_classes, aliases: aliases)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Full revival over the Nodes tree: !ruby/object, !ruby/struct,
|
|
149
|
+
# !ruby/set, encode_with/init_with, alias identity. Plain-data
|
|
150
|
+
# loads should use load/safe_load (the Materializer fast path).
|
|
151
|
+
def unsafe_load(yaml, **)
|
|
152
|
+
# #178: the marshal fast path. Nodes::Builder's tree is per-node
|
|
153
|
+
# FFI (kind/tag/anchor/value + a walk per container) — the whole
|
|
154
|
+
# 11 MB relaton index paid ~9.6 s there. When the document is
|
|
155
|
+
# plain data (no timestamps/merge keys/tagged revivals), ONE C
|
|
156
|
+
# call materializes the tree as Marshal bytes and Marshal.load
|
|
157
|
+
# builds the objects in C (~1 s end to end). Construct-heavy
|
|
158
|
+
# documents keep the full revival walk below, byte for byte.
|
|
159
|
+
if Yeptris::FFI::MARSHAL && ::Yeptris::Psych.domain_types.empty?
|
|
160
|
+
begin
|
|
161
|
+
doc = ::Yeptris::Document.parse(yaml, schema: :compat_11)
|
|
162
|
+
return nil if doc.nil? || doc.document_count.zero?
|
|
163
|
+
|
|
164
|
+
root = doc.root(0)
|
|
165
|
+
fast = root&.marshal_fast
|
|
166
|
+
unless fast.nil?
|
|
167
|
+
doc.free
|
|
168
|
+
return fast
|
|
169
|
+
end
|
|
170
|
+
tree = Nodes::Builder.document(doc) # ownership: the tree frees
|
|
171
|
+
return Visitors::ToRuby.visit(tree.children.first)
|
|
172
|
+
rescue ::Yeptris::ParseError => e
|
|
173
|
+
doc&.free unless doc&.freed?
|
|
174
|
+
translate_parse_error(e)
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
tree = parse(yaml)
|
|
178
|
+
return nil if tree.nil?
|
|
179
|
+
|
|
180
|
+
Visitors::ToRuby.visit(tree.children.first)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def safe_load(yaml, permitted_classes: [::Date, ::Time], aliases: false, **)
|
|
184
|
+
begin
|
|
185
|
+
doc = Yeptris::Document.parse(yaml, schema: :compat_11)
|
|
186
|
+
return nil if doc.nil? # the legal empty stream
|
|
187
|
+
|
|
188
|
+
force_utf8_scalars(walk_safe(doc.root(0), permitted_classes, aliases))
|
|
189
|
+
rescue ::Yeptris::ParseError => e
|
|
190
|
+
doc&.free unless doc&.freed?
|
|
191
|
+
translate_parse_error(e)
|
|
192
|
+
ensure
|
|
193
|
+
doc&.free
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# A YAML stream is a Unicode character stream: stdlib psych tags
|
|
198
|
+
# non-ASCII scalars UTF-8 even when the input String is BINARY
|
|
199
|
+
# (#135). ASCII-only values keep their (binary) encoding — a
|
|
200
|
+
# re-tag would be observable there and stdlib does not either.
|
|
201
|
+
def force_utf8_scalars(obj)
|
|
202
|
+
case obj
|
|
203
|
+
when ::String
|
|
204
|
+
# valid_encoding? on an ASCII-8BIT string is ALWAYS true
|
|
205
|
+
# (it is bytes) — the discriminator is whether the bytes
|
|
206
|
+
# READ as UTF-8: #135's case (UTF-8 scalars the C side
|
|
207
|
+
# materialized as BINARY) re-tags; DECODED !binary bytes
|
|
208
|
+
# that are not UTF-8 stay BINARY (#168). Binary content that
|
|
209
|
+
# happens to be valid UTF-8 re-tags — noted divergence from
|
|
210
|
+
# stdlib, which keeps BINARY; real payloads (zips, bodies)
|
|
211
|
+
# never are.
|
|
212
|
+
if obj.encoding == ::Encoding::ASCII_8BIT && !obj.ascii_only?
|
|
213
|
+
probe = obj.dup.force_encoding(::Encoding::UTF_8)
|
|
214
|
+
obj.force_encoding(::Encoding::UTF_8) if probe.valid_encoding?
|
|
215
|
+
end
|
|
216
|
+
obj
|
|
217
|
+
when ::Hash
|
|
218
|
+
obj.each { |k, v| force_utf8_scalars(k); force_utf8_scalars(v) }
|
|
219
|
+
when ::Array
|
|
220
|
+
obj.each { |v| force_utf8_scalars(v) }
|
|
221
|
+
else
|
|
222
|
+
obj
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# stdlib's file-level faces (#135): BOM-tolerant UTF-8 reads with
|
|
227
|
+
# the fallback contract (load_file path, fallback: false default).
|
|
228
|
+
def load_file(path, fallback: false, **kwargs)
|
|
229
|
+
File.open(path, "r:bom|utf-8") { |f| load(f, **kwargs) }
|
|
230
|
+
rescue Errno::ENOENT
|
|
231
|
+
raise unless fallback
|
|
232
|
+
|
|
233
|
+
false
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def safe_load_file(path, fallback: false, **kwargs)
|
|
237
|
+
File.open(path, "r:bom|utf-8") { |f| safe_load(f, **kwargs) }
|
|
238
|
+
rescue Errno::ENOENT
|
|
239
|
+
raise unless fallback
|
|
240
|
+
|
|
241
|
+
false
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def unsafe_load_file(path, fallback: false, **kwargs)
|
|
245
|
+
File.open(path, "r:bom|utf-8") { |f| unsafe_load(f, **kwargs) }
|
|
246
|
+
rescue Errno::ENOENT
|
|
247
|
+
raise unless fallback
|
|
248
|
+
|
|
249
|
+
false
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def parse_file(path, **kwargs)
|
|
253
|
+
File.open(path, "r:bom|utf-8") { |f| parse(f, **kwargs) }
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
# stdlib's deprecated safe/dump split — same output here (every
|
|
259
|
+
# value this loader produces is safe data)
|
|
260
|
+
def safe_dump(obj, io = nil, options = {})
|
|
261
|
+
::Yeptris::Psych.dump(obj, io, options)
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def load_stream(yaml, **kwargs, &block)
|
|
265
|
+
# materialize each document's root directly — the stream
|
|
266
|
+
# children share one C document, so their handles would all
|
|
267
|
+
# resolve to the first document's tree. stdlib's block form
|
|
268
|
+
# yields each loaded document's object (#179 round 4).
|
|
269
|
+
doc = Yeptris::Document.parse(yaml, schema: :compat_11)
|
|
270
|
+
return nil if doc.nil? # the legal empty stream
|
|
271
|
+
|
|
272
|
+
begin
|
|
273
|
+
docs = (0...doc.document_count).map { |i| doc.root(i).to_ruby }
|
|
274
|
+
ensure
|
|
275
|
+
doc.free
|
|
276
|
+
end
|
|
277
|
+
docs.each { |d| block.call(d) } if block
|
|
278
|
+
docs
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# safe_load_stream: stdlib's surface — the stream form of
|
|
282
|
+
# safe_load. Our load_stream is already safe-by-default
|
|
283
|
+
# (Psych 5 semantics), so this is the yielding wrapper.
|
|
284
|
+
def safe_load_stream(yaml, **kwargs, &block)
|
|
285
|
+
load_stream(yaml, **kwargs, &block)
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
# The first document's node tree (no Ruby materialization).
|
|
289
|
+
def translate_parse_error(e)
|
|
290
|
+
# yeptris rejects *foo without &foo at PARSE time (stdlib
|
|
291
|
+
# raises at visit) — surface it as stdlib's class so
|
|
292
|
+
# consumers' rescues keep working
|
|
293
|
+
raise AnchorNotDefined, e.message if e.message.include?("undefined anchor")
|
|
294
|
+
|
|
295
|
+
raise SyntaxError.from_parse_error(e)
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
def parse(yaml)
|
|
299
|
+
doc = Yeptris::Document.parse(yaml, schema: :compat_11)
|
|
300
|
+
return nil if doc.nil? || doc.document_count.zero?
|
|
301
|
+
|
|
302
|
+
Nodes::Builder.document(doc)
|
|
303
|
+
rescue Yeptris::ParseError => e
|
|
304
|
+
raise SyntaxError.from_parse_error(e)
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
def parse_stream(yaml, &block)
|
|
308
|
+
# stdlib yields each document to a block if one was given (and
|
|
309
|
+
# the stream it returns holds the same children either way).
|
|
310
|
+
# #182: the wrapper is the sole owner of the C memory — the
|
|
311
|
+
# stream merely REFERENCES it; the wrapper's own finalizer
|
|
312
|
+
# handles GC-free. (The block-yielding form is tracked under
|
|
313
|
+
# #179: the current implementation materializes eagerly; the
|
|
314
|
+
# block is only honored as a no-op convenience.)
|
|
315
|
+
doc = Yeptris::Document.parse(yaml, schema: :compat_11)
|
|
316
|
+
return nil if doc.nil? || doc.document_count.zero?
|
|
317
|
+
|
|
318
|
+
stream = Nodes::Stream.new
|
|
319
|
+
(0...doc.document_count).each do |i|
|
|
320
|
+
child = Nodes::Builder.document_stream_child(doc, i)
|
|
321
|
+
stream.children << child
|
|
322
|
+
block.call(child) if block # stdlib's yielding form (#179 round 4)
|
|
323
|
+
end
|
|
324
|
+
# ownership: the DOCUMENT wrapper is the sole owner — its own
|
|
325
|
+
# finalizer (pointer-only closure) frees the C memory; the
|
|
326
|
+
# stream merely references it. A tree-side finalizer closing
|
|
327
|
+
# over the wrapper raced the wrapper's sweep (#182's abort).
|
|
328
|
+
stream.owner = doc
|
|
329
|
+
stream
|
|
330
|
+
rescue Yeptris::ParseError => e
|
|
331
|
+
raise SyntaxError.from_parse_error(e)
|
|
332
|
+
ensure
|
|
333
|
+
# the returned stream OWNS the doc's lifetime (its free delegates
|
|
334
|
+
# to the owner); only free here when the stream wasn't built
|
|
335
|
+
doc&.free if doc && !stream
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
# Arbitrary object graphs through the YAMLTree visitor
|
|
339
|
+
# (anchors, !ruby/ tags); plain data rides the fast builder.
|
|
340
|
+
def dump(obj, io = nil, options = {})
|
|
341
|
+
# stdlib's Object#to_yaml calls Psych.dump(self, options) —
|
|
342
|
+
# the options hash lands in the io slot positionally; treat a
|
|
343
|
+
# Hash there as options (the options themselves ride the
|
|
344
|
+
# visitor's defaults, #95 thread)
|
|
345
|
+
if io.is_a?(::Hash)
|
|
346
|
+
options = io
|
|
347
|
+
io = nil
|
|
348
|
+
end
|
|
349
|
+
# scalars take the fast builder; EVERYTHING else (including
|
|
350
|
+
# plain containers — they may nest custom objects) goes
|
|
351
|
+
# through the visitor, which builds the same DOM for plain
|
|
352
|
+
# data anyway
|
|
353
|
+
out =
|
|
354
|
+
case obj
|
|
355
|
+
# DateTime < Date in ruby's hierarchy — the fast scalar
|
|
356
|
+
# path would emit it tag-less iso8601 (re-loading as Time);
|
|
357
|
+
# the visitor carries stdlib's !ruby/object:DateTime tag
|
|
358
|
+
when ::DateTime
|
|
359
|
+
Visitors::YAMLTree.new.push(obj).finish
|
|
360
|
+
when nil, true, false, ::String, ::Integer, ::Float, ::Symbol, ::Date, ::Time
|
|
361
|
+
Yeptris::YAML.dump(obj, header: true)
|
|
362
|
+
else
|
|
363
|
+
Visitors::YAMLTree.new.push(obj).finish
|
|
364
|
+
end
|
|
365
|
+
return out unless io
|
|
366
|
+
|
|
367
|
+
io.write(out)
|
|
368
|
+
io
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
private
|
|
372
|
+
|
|
373
|
+
# yeptris materializes plain data only — there is nothing
|
|
374
|
+
# unsafe it COULD load. The safety walk enforces Psych's
|
|
375
|
+
# contract: aliases need opt-in, explicit non-core tags raise
|
|
376
|
+
# DisallowedClass, and a scalar whose IMPLICIT typing yields a
|
|
377
|
+
# class outside the permitted set raises too (issue #69: a
|
|
378
|
+
# compat_11 date must not become a Date unless Date is
|
|
379
|
+
# permitted — Psych::DisallowedClass semantics).
|
|
380
|
+
def walk_safe(root, permitted, aliases_enabled)
|
|
381
|
+
check(root, permitted, aliases_enabled) if root
|
|
382
|
+
root.to_ruby
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
PERMITTED_BY_DEFAULT = [TrueClass, FalseClass, NilClass, Integer, Float,
|
|
386
|
+
String, Array, Hash].freeze
|
|
387
|
+
|
|
388
|
+
def check(node, permitted, aliases_enabled)
|
|
389
|
+
case node.kind
|
|
390
|
+
when :alias
|
|
391
|
+
raise AliasesError, "Unknown alias" unless aliases_enabled
|
|
392
|
+
when :scalar, :mapping, :sequence
|
|
393
|
+
tag = node.tag
|
|
394
|
+
unless tag.nil?
|
|
395
|
+
# both tag spellings pass: the URI form's last segment and
|
|
396
|
+
# the short "!binary" shorthand (#168's cassettes carry it)
|
|
397
|
+
name = tag.split(":").last.sub(/\A!/, "")
|
|
398
|
+
unless %w[str int float bool null timestamp seq map merge value
|
|
399
|
+
binary].include?(name)
|
|
400
|
+
raise DisallowedClass, name
|
|
401
|
+
end
|
|
402
|
+
end
|
|
403
|
+
if node.kind == :scalar && node.tag_id == :timestamp &&
|
|
404
|
+
!permitted.include?(Date) && !permitted.include?(Time)
|
|
405
|
+
raise DisallowedClass, "Date"
|
|
406
|
+
end
|
|
407
|
+
case node.kind
|
|
408
|
+
when :mapping
|
|
409
|
+
node.each_pair do |k, v|
|
|
410
|
+
check(k, permitted, aliases_enabled)
|
|
411
|
+
check(v, permitted, aliases_enabled)
|
|
412
|
+
end
|
|
413
|
+
when :sequence
|
|
414
|
+
node.each { |e| check(e, permitted, aliases_enabled) }
|
|
415
|
+
end
|
|
416
|
+
end
|
|
417
|
+
end
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
# Psych::Nodes over the yeptris document: the tree IS the parsed
|
|
421
|
+
# document (children are node handles, not copies) — parse cost
|
|
422
|
+
# is the parse, and to_ruby reuses the Materializer.
|
|
423
|
+
module Nodes
|
|
424
|
+
class Node
|
|
425
|
+
include Enumerable
|
|
426
|
+
|
|
427
|
+
attr_reader :children
|
|
428
|
+
attr_reader :handle # @api private — the Yeptris::Node
|
|
429
|
+
# @api private — the tree builder attaches handles; a writer,
|
|
430
|
+
# never instance_variable_set from outside
|
|
431
|
+
attr_writer :handle
|
|
432
|
+
# The Document owning this tree's C memory; a plain writer —
|
|
433
|
+
# never instance_variable_set from outside (encapsulation law).
|
|
434
|
+
attr_accessor :owner
|
|
435
|
+
|
|
436
|
+
def initialize(handle = nil, children = [])
|
|
437
|
+
@handle = handle
|
|
438
|
+
@children = children
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
# Every node HAS an anchor concept (none by default) — the
|
|
442
|
+
# anchored search needs no type probe, just the model.
|
|
443
|
+
def anchor
|
|
444
|
+
nil
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
def each(&block)
|
|
448
|
+
@children.each(&block)
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
# The Ruby object for this subtree (Materializer semantics).
|
|
452
|
+
def to_ruby
|
|
453
|
+
@handle.to_ruby
|
|
454
|
+
end
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
class Stream < Node
|
|
458
|
+
def free
|
|
459
|
+
@owner&.free
|
|
460
|
+
end
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
# Owns the underlying Yeptris::Document: node handles in the
|
|
464
|
+
# tree stay valid while the tree is reachable; a GC finalizer
|
|
465
|
+
# releases the C memory when it is not.
|
|
466
|
+
class Document < Node
|
|
467
|
+
# The document's root as a Ruby object (the stream-yield face).
|
|
468
|
+
def to_ruby
|
|
469
|
+
children.first&.to_ruby
|
|
470
|
+
end
|
|
471
|
+
attr_reader :version, :tags
|
|
472
|
+
|
|
473
|
+
def initialize(version = [], tags = {})
|
|
474
|
+
super(nil)
|
|
475
|
+
@version = version
|
|
476
|
+
@tags = tags
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
# @api private — attach the document: the tree REFERENCES it
|
|
480
|
+
# (keeping the C memory alive while the tree is reachable);
|
|
481
|
+
# the wrapper's own finalizer is the sole freer (#182: a
|
|
482
|
+
# tree-side finalizer closing over the wrapper raced the
|
|
483
|
+
# wrapper's sweep — SIGABRT under document churn)
|
|
484
|
+
def own(yeptris_doc)
|
|
485
|
+
@owner = yeptris_doc
|
|
486
|
+
self
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
def free
|
|
490
|
+
@owner&.free
|
|
491
|
+
end
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
class Scalar < Node
|
|
495
|
+
attr_reader :value, :tag, :anchor, :plain, :quoted, :style
|
|
496
|
+
|
|
497
|
+
def initialize(value = nil, anchor: nil, tag: nil, plain: true,
|
|
498
|
+
quoted: false, style: :plain)
|
|
499
|
+
super(nil)
|
|
500
|
+
@value = value
|
|
501
|
+
@anchor = anchor
|
|
502
|
+
@tag = tag
|
|
503
|
+
@plain = plain
|
|
504
|
+
@quoted = quoted
|
|
505
|
+
@style = style
|
|
506
|
+
end
|
|
507
|
+
end
|
|
508
|
+
|
|
509
|
+
class Sequence < Node
|
|
510
|
+
attr_reader :anchor, :tag, :style
|
|
511
|
+
|
|
512
|
+
def initialize(anchor: nil, tag: nil, style: :block)
|
|
513
|
+
super(nil, [])
|
|
514
|
+
@anchor = anchor
|
|
515
|
+
@tag = tag
|
|
516
|
+
@style = style
|
|
517
|
+
end
|
|
518
|
+
end
|
|
519
|
+
|
|
520
|
+
class Mapping < Node
|
|
521
|
+
attr_reader :anchor, :tag, :style
|
|
522
|
+
|
|
523
|
+
def initialize(anchor: nil, tag: nil, style: :block)
|
|
524
|
+
super(nil, [])
|
|
525
|
+
@anchor = anchor
|
|
526
|
+
@tag = tag
|
|
527
|
+
@style = style
|
|
528
|
+
end
|
|
529
|
+
end
|
|
530
|
+
|
|
531
|
+
class Alias < Node
|
|
532
|
+
attr_reader :anchor
|
|
533
|
+
|
|
534
|
+
def initialize(anchor)
|
|
535
|
+
super(nil)
|
|
536
|
+
@anchor = anchor
|
|
537
|
+
end
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
# Builds the Nodes tree from a parsed document.
|
|
541
|
+
module Builder
|
|
542
|
+
module_function
|
|
543
|
+
|
|
544
|
+
# parse(): one document, owning the yeptris document
|
|
545
|
+
def document(doc, index = 0)
|
|
546
|
+
document_stream_child(doc, index).own(doc)
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
# parse_stream(): a child document sharing one owner (the
|
|
550
|
+
# stream owns the yeptris document)
|
|
551
|
+
def document_stream_child(doc, index)
|
|
552
|
+
root = doc.root(index)
|
|
553
|
+
# #182's root cause: three per-stream Document wrappers all
|
|
554
|
+
# capture the same c_ptr in their finalizers → three frees.
|
|
555
|
+
# The stream-children WRAPPER owns nothing (the owner wrapper
|
|
556
|
+
# is the real Document; this one just references it). Build
|
|
557
|
+
# a non-owning wrapper with no finalizer.
|
|
558
|
+
d = Document.send(:new, root&.document, false)
|
|
559
|
+
d.children << node(root) if root
|
|
560
|
+
d
|
|
561
|
+
end
|
|
562
|
+
|
|
563
|
+
def node(n)
|
|
564
|
+
case n.kind
|
|
565
|
+
when :mapping
|
|
566
|
+
m = Mapping.new(anchor: n.anchor, tag: n.tag)
|
|
567
|
+
n.each_pair do |k, v|
|
|
568
|
+
m.children << node(k)
|
|
569
|
+
m.children << node(v)
|
|
570
|
+
end
|
|
571
|
+
m.handle = n
|
|
572
|
+
m
|
|
573
|
+
when :sequence
|
|
574
|
+
s = Sequence.new(anchor: n.anchor, tag: n.tag)
|
|
575
|
+
n.each { |e| s.children << node(e) }
|
|
576
|
+
s.handle = n
|
|
577
|
+
s
|
|
578
|
+
when :alias
|
|
579
|
+
a = Alias.new(n.value)
|
|
580
|
+
a.handle = n
|
|
581
|
+
a
|
|
582
|
+
else
|
|
583
|
+
sc = Scalar.new(n.value, anchor: n.anchor, tag: n.tag,
|
|
584
|
+
plain: n.style == :plain, style: n.style)
|
|
585
|
+
sc.handle = n
|
|
586
|
+
sc
|
|
587
|
+
end
|
|
588
|
+
end
|
|
589
|
+
end
|
|
590
|
+
end
|
|
591
|
+
end
|
|
592
|
+
end
|
|
593
|
+
|
|
594
|
+
# The drop-in is OPT-IN (issue #69): `require "yeptris/psych"` loads
|
|
595
|
+
# the namespace only and coexists with stdlib psych in ANY load
|
|
596
|
+
# order (this file never touches ::Psych). The rebind lives in
|
|
597
|
+
# yeptris/psych/drop_in — process-exclusive by nature, since the
|
|
598
|
+
# stdlib cannot be prevented from re-opening whatever ::Psych points
|
|
599
|
+
# at once IT loads.
|
|
600
|
+
# The core extension (stdlib psych/core_ext parity): Object#to_yaml
|
|
601
|
+
# and Object.yaml_tag exist whether the consumer came through stdlib
|
|
602
|
+
# psych first or pure drop-in. Both definitions are compatible —
|
|
603
|
+
# whoever loads last wins, and both call the rebound Psych.dump.
|
|
604
|
+
class Object
|
|
605
|
+
def self.yaml_tag(url)
|
|
606
|
+
::Yeptris::Psych.add_tag(url, self)
|
|
607
|
+
end
|
|
608
|
+
|
|
609
|
+
def to_yaml(options = {})
|
|
610
|
+
::Yeptris::Psych.dump(self, options)
|
|
611
|
+
end
|
|
612
|
+
end
|
|
613
|
+
|
|
614
|
+
# 0.4-contract migration signal (issue #95): this require used to
|
|
615
|
+
# rebind ::Psych. It does not anymore, and re-binding it here would
|
|
616
|
+
# regress #69 (any stdlib psych loaded afterwards would explode with
|
|
617
|
+
# a superclass mismatch), so the old path warns instead of acting.
|
|
618
|
+
if defined?(::Psych) && !::Psych.equal?(Yeptris::Psych)
|
|
619
|
+
warn "yeptris: \"yeptris/psych\" defines the namespace only — it no " \
|
|
620
|
+
"longer rebinds ::Psych. Require \"yeptris/psych/drop_in\" for the " \
|
|
621
|
+
"drop-in rebind, or call Yeptris::Psych explicitly."
|
|
622
|
+
end
|