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,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# The YAML leg of the Descriptor plan walk (yeptris#293 slice three,
|
|
4
|
+
# over the C DOM plan walk of libyeptris v0.6.4): the same compiled
|
|
5
|
+
# row shape as Yeptris::JSON::Descriptor, applied to a parsed YAML
|
|
6
|
+
# document — block YAML hydrates to typed columns with no
|
|
7
|
+
# intermediate Ruby tree.
|
|
8
|
+
#
|
|
9
|
+
# descriptor = Yeptris::YAML::Descriptor.build(
|
|
10
|
+
# kind: :map, path: ["data", "items"], # segmented paths work
|
|
11
|
+
# children: [{ name: "id", kind: :int }, { name: "name", kind: :str }])
|
|
12
|
+
# descriptor.walk(yaml).column("id") # => [1, 2, ...]
|
|
13
|
+
#
|
|
14
|
+
# Typed extraction rides the parse-time tag ids (schema: selects the
|
|
15
|
+
# resolver — :compat_11 keeps the Psych/libyaml implicit typing
|
|
16
|
+
# YAML.load uses); string columns are materialized from the
|
|
17
|
+
# document's regions before the result returns (nothing borrowed).
|
|
18
|
+
class Yeptris::YAML::Descriptor < ::Yeptris::JSON::Descriptor
|
|
19
|
+
# schema: :core_12 (YAML 1.2) or :compat_11 (the Psych-compatible
|
|
20
|
+
# implicit typing Yeptris::YAML.load uses).
|
|
21
|
+
def initialize(handle, names, schema)
|
|
22
|
+
super(handle, names)
|
|
23
|
+
@schema = schema
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# @api private — the compile rides the JSON Descriptor's grammar
|
|
27
|
+
# (inherited private class method); schema selects the resolver
|
|
28
|
+
def self.build(kind:, path: nil, children:, schema: :compat_11)
|
|
29
|
+
handle, names = compile_handle(kind, path, children)
|
|
30
|
+
new(handle, names, schema)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def walk(yaml)
|
|
34
|
+
src = ::Yeptris.read_input(yaml).to_s
|
|
35
|
+
doc = ::Yeptris::Document.parse(src, schema: @schema)
|
|
36
|
+
return [] if doc.nil? # the legal empty stream
|
|
37
|
+
|
|
38
|
+
begin
|
|
39
|
+
st = ::FFI::MemoryPointer.new(:int)
|
|
40
|
+
raw = ::Yeptris::FFI.yeptris_document_plan_walk(doc.c_ptr, @handle, st)
|
|
41
|
+
if raw.null?
|
|
42
|
+
raise ::Yeptris::JSON::Descriptor::Error,
|
|
43
|
+
"plan walk failed (status=#{st.read_int}) — the document shape " \
|
|
44
|
+
"disagrees with the plan"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
begin
|
|
48
|
+
::Yeptris::JSON::Descriptor::PlanResult.read_dom(raw, @names)
|
|
49
|
+
ensure
|
|
50
|
+
::Yeptris::FFI.yeptris_plan_result_free(raw)
|
|
51
|
+
end
|
|
52
|
+
ensure
|
|
53
|
+
doc.free
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
data/lib/yeptris/yaml.rb
ADDED
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Self-sufficient (issue #95): the neutral surface is a valid entry
|
|
4
|
+
# point on its own — lutaml-model requires exactly this file.
|
|
5
|
+
require "yeptris"
|
|
6
|
+
|
|
7
|
+
module Yeptris
|
|
8
|
+
# The neutral Ruby surface (the Psych-compat namespace arrives with
|
|
9
|
+
# the recorder-driven Visitors in phase B; this is the yeptris-native
|
|
10
|
+
# face users target first).
|
|
11
|
+
module YAML
|
|
12
|
+
autoload :Descriptor, "yeptris/yaml/descriptor"
|
|
13
|
+
|
|
14
|
+
module_function
|
|
15
|
+
|
|
16
|
+
# Loads the FIRST document of a YAML stream as native Ruby objects.
|
|
17
|
+
# schema: :compat_11 selects Psych/libyaml implicit typing
|
|
18
|
+
# (yes/no, 0o/octal, sexagesimal); :core_12 (default) is YAML 1.2.
|
|
19
|
+
#
|
|
20
|
+
# This surface keeps the Psych contract for EVERY input — including
|
|
21
|
+
# JSON-shaped ones (`{"a": [1,]}` is legal flow YAML; `"1e3"` is a
|
|
22
|
+
# Psych String). Strict RFC 8259 semantics live on Yeptris::JSON
|
|
23
|
+
# (TODO.restructure/31): defaults follow proof, not benchmarks.
|
|
24
|
+
def load(yaml, schema: :compat_11)
|
|
25
|
+
yaml = Yeptris.read_input(yaml)
|
|
26
|
+
yaml = yaml.to_s
|
|
27
|
+
docs = _drain_all(yaml, schema)
|
|
28
|
+
docs.empty? ? nil : docs.first
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Psych-semantics safe_load on the native surface (issue #69 —
|
|
32
|
+
# the entry frameworks actually want): plain data only; a leaf
|
|
33
|
+
# whose class is not permitted raises Yeptris::Psych::
|
|
34
|
+
# DisallowedClass; aliases: false raises Yeptris::Psych::
|
|
35
|
+
# AliasesError on alias use. Delegates to the compat namespace's
|
|
36
|
+
# tree walk (correctness first; the fused drain comes later).
|
|
37
|
+
def safe_load(yaml, permitted_classes: [], aliases: false, schema: :compat_11)
|
|
38
|
+
yaml = Yeptris.read_input(yaml)
|
|
39
|
+
Psych.safe_load(yaml.to_s, permitted_classes: permitted_classes, aliases: aliases)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Every document in the stream, in order.
|
|
43
|
+
def load_stream(yaml, schema: :compat_11)
|
|
44
|
+
yaml = Yeptris.read_input(yaml)
|
|
45
|
+
yaml = yaml.to_s
|
|
46
|
+
_drain_all(yaml, schema)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# The Marshal fast path when the loaded libyeptris has it (>= 0.1.11
|
|
50
|
+
# era builds), falling back to the columnar drain and finally the
|
|
51
|
+
# record drain — one code path, the fastest the library offers.
|
|
52
|
+
def _drain_all(yaml, schema)
|
|
53
|
+
if FFI::MARSHAL
|
|
54
|
+
result = ValueML.load_all_marshal(yaml, schema: schema, mode: :all)
|
|
55
|
+
return result unless result.nil?
|
|
56
|
+
end
|
|
57
|
+
if FFI::COLUMNS
|
|
58
|
+
ValueML.load_all_columns(yaml, schema: schema)
|
|
59
|
+
else
|
|
60
|
+
ValueML.load_all(yaml, schema: schema)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# The Psych-suite port's spelling (spec/psych/): compat typing.
|
|
65
|
+
def parse_yaml(yaml)
|
|
66
|
+
load(yaml)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def load_file(path, schema: :compat_11)
|
|
70
|
+
File.open(path, "rb") { |f| load(f, schema: schema) }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Parses without materializing: the first document's root Node.
|
|
74
|
+
def parse(yaml, schema: :core_12)
|
|
75
|
+
doc = Document.parse(yaml, schema: schema)
|
|
76
|
+
return nil if doc.nil? || doc.document_count.zero?
|
|
77
|
+
|
|
78
|
+
doc.root(0)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Serializes a Ruby object graph to YAML via the DOM builder
|
|
82
|
+
# (TODO.impl/11 phase 3). Strings are emitted plain only when the
|
|
83
|
+
# resolver round-trips them as strings — everything else takes a
|
|
84
|
+
# quoted style, so dump(load(x)) == x for the scalar types.
|
|
85
|
+
def dump(obj, canonical: false, header: false)
|
|
86
|
+
BulkBuilder.dump(obj, canonical: canonical, header: header)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# The dump-side mirror of the Materializer's bulk drain (TODO.impl
|
|
91
|
+
# 15 phase D): the tree walks into one flat entry array plus one
|
|
92
|
+
# string blob, and yeptris_document_build raises the DOM in a
|
|
93
|
+
# SINGLE FFI call — per-node FFI is gone. Same semantics as the
|
|
94
|
+
# per-node Builder it replaces (cycle refusal, :symbol scalars,
|
|
95
|
+
# Date/Time iso8601, plain-only-when-it-round-trips strings),
|
|
96
|
+
# with plain_string? pinned to the resolver by a differential
|
|
97
|
+
# spec.
|
|
98
|
+
module BulkBuilder
|
|
99
|
+
SCALAR = Yeptris::FFI::BUILD_SCALAR
|
|
100
|
+
SEQ = Yeptris::FFI::BUILD_SEQ
|
|
101
|
+
MAP = Yeptris::FFI::BUILD_MAP
|
|
102
|
+
STOP = Yeptris::FFI::BUILD_END
|
|
103
|
+
STYLE_PLAIN = 1
|
|
104
|
+
STYLE_SQ = 2
|
|
105
|
+
STYLE_DQ = 3
|
|
106
|
+
STYLE_LIT = 4
|
|
107
|
+
BUILD_TAG = Yeptris::FFI::BUILD_TAG
|
|
108
|
+
|
|
109
|
+
module_function
|
|
110
|
+
|
|
111
|
+
# Psych's exact timestamp spelling (format_time, #300): space
|
|
112
|
+
# separated, nanosecond width, Z for UTC — iso8601 diverged
|
|
113
|
+
def time_text(t)
|
|
114
|
+
t.utc? ? t.strftime("%Y-%m-%d %H:%M:%S.%9N Z") : t.strftime("%Y-%m-%d %H:%M:%S.%9N %:z")
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Psych's float spelling (#290): Infinity/NAN ride the YAML words,
|
|
118
|
+
# not Ruby's to_s (which prints "Infinity"/"NaN" — a STRING on
|
|
119
|
+
# reload)
|
|
120
|
+
def float_text(f)
|
|
121
|
+
return ".nan" if f.respond_to?(:nan?) && f.nan?
|
|
122
|
+
case (inf = f.infinite?)
|
|
123
|
+
when 1 then ".inf"
|
|
124
|
+
when -1 then "-.inf"
|
|
125
|
+
else f.to_s
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# container entries are constant bytes — no pack per op
|
|
130
|
+
MAP_ENTRY = [Yeptris::FFI::BUILD_MAP, 0, 0, 0].pack("CCx2VV").freeze
|
|
131
|
+
SEQ_ENTRY = [Yeptris::FFI::BUILD_SEQ, 0, 0, 0].pack("CCx2VV").freeze
|
|
132
|
+
END_ENTRY = [Yeptris::FFI::BUILD_END, 0, 0, 0].pack("CCx2VV").freeze
|
|
133
|
+
CONT_ENTRY = { Yeptris::FFI::BUILD_MAP => MAP_ENTRY,
|
|
134
|
+
Yeptris::FFI::BUILD_SEQ => SEQ_ENTRY,
|
|
135
|
+
Yeptris::FFI::BUILD_END => END_ENTRY }.freeze
|
|
136
|
+
|
|
137
|
+
def dump(obj, canonical: false, header: false)
|
|
138
|
+
parts = []
|
|
139
|
+
blob = String.new(encoding: Encoding::BINARY)
|
|
140
|
+
off = [0]
|
|
141
|
+
# one pack per SCALAR; containers reuse frozen constants
|
|
142
|
+
emit = lambda do |op, style, o, len|
|
|
143
|
+
parts << (o.zero? && len.zero? && style.zero? ? CONT_ENTRY[op] :
|
|
144
|
+
[op, style, o, len].pack("CCx2VV"))
|
|
145
|
+
end
|
|
146
|
+
place(obj, emit, blob, off, {})
|
|
147
|
+
doc = Document.create
|
|
148
|
+
buf = ::FFI::MemoryPointer.from_string(parts.join)
|
|
149
|
+
bblob = ::FFI::MemoryPointer.from_string(blob)
|
|
150
|
+
rc = doc.build_entries(buf, parts.length, bblob, blob.bytesize)
|
|
151
|
+
raise DumpError, "document_build failed: #{rc}" unless rc == FFI::OK
|
|
152
|
+
doc.serialize(canonical: canonical, explicit_doc_start: header)
|
|
153
|
+
ensure
|
|
154
|
+
doc&.free
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def place(obj, emit, blob, off, seen)
|
|
158
|
+
case obj
|
|
159
|
+
when Hash
|
|
160
|
+
cycle_guard(obj, seen) do
|
|
161
|
+
emit.call(MAP, 0, 0, 0)
|
|
162
|
+
obj.each do |k, v|
|
|
163
|
+
# Symbol keys emit as bare ":k" plain — the compat
|
|
164
|
+
# reader resolves them back to Symbols; string keys
|
|
165
|
+
# ride the visit_String rules like any other scalar
|
|
166
|
+
# Symbol keys emit as bare ":k" plain — the compat
|
|
167
|
+
# reader resolves them back to Symbols; Integer/Float/
|
|
168
|
+
# bool keys ride plain like their values (Psych emits
|
|
169
|
+
# 1: unquoted — #290 family 5); string keys ride the
|
|
170
|
+
# visit_String rules like any other scalar; NIL keys
|
|
171
|
+
# ride Psych's explicit-`!` empty-scalar form
|
|
172
|
+
# (#300 family 2)
|
|
173
|
+
if k.nil?
|
|
174
|
+
scalar("", STYLE_SQ, emit, blob, off)
|
|
175
|
+
emit.call(BUILD_TAG, 0, off[0], 1)
|
|
176
|
+
blob << "!"
|
|
177
|
+
off[0] += 1
|
|
178
|
+
elsif k.is_a?(Symbol)
|
|
179
|
+
scalar(":#{k}", STYLE_PLAIN, emit, blob, off)
|
|
180
|
+
elsif k.is_a?(String) && k == "<<"
|
|
181
|
+
# stdlib yaml_tree: a literal chevron key carries the
|
|
182
|
+
# explicit !!str tag (single-quoted) so it does not
|
|
183
|
+
# re-load as a merge
|
|
184
|
+
scalar("<<", STYLE_SQ, emit, blob, off)
|
|
185
|
+
emit.call(BUILD_TAG, 0, off[0], 5)
|
|
186
|
+
blob << "!!str"
|
|
187
|
+
off[0] += 5
|
|
188
|
+
elsif k.is_a?(String)
|
|
189
|
+
place(k, emit, blob, off, seen)
|
|
190
|
+
else
|
|
191
|
+
scalar(k.to_s, STYLE_PLAIN, emit, blob, off)
|
|
192
|
+
end
|
|
193
|
+
place(v, emit, blob, off, seen)
|
|
194
|
+
end
|
|
195
|
+
emit.call(STOP, 0, 0, 0)
|
|
196
|
+
end
|
|
197
|
+
when Array
|
|
198
|
+
cycle_guard(obj, seen) do
|
|
199
|
+
emit.call(SEQ, 0, 0, 0)
|
|
200
|
+
obj.each { |e| place(e, emit, blob, off, seen) }
|
|
201
|
+
emit.call(STOP, 0, 0, 0)
|
|
202
|
+
end
|
|
203
|
+
when String
|
|
204
|
+
if obj.encoding == ::Encoding::ASCII_8BIT && !obj.ascii_only?
|
|
205
|
+
# #168: psych visit_String's binary branch — strict base64
|
|
206
|
+
# + the short !binary tag + literal style (Builder
|
|
207
|
+
# build_string carries the same branch; the tagged-literal
|
|
208
|
+
# writer keeps the block form)
|
|
209
|
+
scalar([obj].pack("m0"), STYLE_LIT, emit, blob, off)
|
|
210
|
+
emit.call(BUILD_TAG, 0, off[0], 7)
|
|
211
|
+
blob << "!binary"
|
|
212
|
+
off[0] += 7
|
|
213
|
+
else
|
|
214
|
+
scalar(obj, STYLE_BY_NAME[string_style(obj)], emit, blob, off)
|
|
215
|
+
end
|
|
216
|
+
when Symbol then scalar(":#{obj}", STYLE_PLAIN, emit, blob, off)
|
|
217
|
+
when Integer then scalar(obj.to_s, STYLE_PLAIN, emit, blob, off)
|
|
218
|
+
when Float then scalar(float_text(obj), STYLE_PLAIN, emit, blob, off)
|
|
219
|
+
when true, false then scalar(obj.to_s, STYLE_PLAIN, emit, blob, off)
|
|
220
|
+
when nil then scalar("", STYLE_PLAIN, emit, blob, off)
|
|
221
|
+
when Time then scalar(time_text(obj), STYLE_PLAIN, emit, blob, off)
|
|
222
|
+
when Date then scalar(obj.iso8601, STYLE_PLAIN, emit, blob, off)
|
|
223
|
+
else
|
|
224
|
+
raise DumpError,
|
|
225
|
+
"cannot dump #{obj.class}: unsupported object " \
|
|
226
|
+
"(custom to_yaml support lands with the Psych Visitors)"
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def scalar(text, style, emit, blob, off)
|
|
231
|
+
# a BINARY blob absorbs any String bytewise (String#<< on
|
|
232
|
+
# ASCII-8BIT is compatible with every encoding) — the old
|
|
233
|
+
# text.b made a throwaway copy of every scalar before the
|
|
234
|
+
# blob's own copy
|
|
235
|
+
emit.call(SCALAR, style, off[0], text.bytesize)
|
|
236
|
+
blob << text
|
|
237
|
+
off[0] += text.bytesize
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def key_text(k)
|
|
241
|
+
k = ":#{k}" if k.is_a?(Symbol)
|
|
242
|
+
k.to_s
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# A plain scalar that the compat resolver re-reads as STR stays
|
|
246
|
+
# plain; anything resolvable (null/bool words, int/float/timestamp
|
|
247
|
+
# shapes, indicators, merge '<<') takes double quotes so the
|
|
248
|
+
# reparse yields String again. Pinned to the C resolver's own
|
|
249
|
+
# verdicts by the differential spec (spec/yaml_spec.rb).
|
|
250
|
+
RESHAPES = %w[~ null Null NULL y Y yes Yes YES n N no No NO true True
|
|
251
|
+
TRUE false False FALSE on On ON off Off OFF <<].freeze
|
|
252
|
+
# the fast lane consults the reshape table per string: an Array
|
|
253
|
+
# scan of 36 words was ~a third of the whole dump walk
|
|
254
|
+
RESHAPES_SET = RESHAPES.each_with_object({}) { |w, h| h[w] = true }.freeze
|
|
255
|
+
|
|
256
|
+
SAFE_WORD = /\A[A-Za-z][A-Za-z0-9_\-.\/ ]*\z/
|
|
257
|
+
|
|
258
|
+
def plain_string?(s)
|
|
259
|
+
# fast lane: letter-started, safe characters incl. spaces — no
|
|
260
|
+
# number/timestamp/indicator shape is possible; only the
|
|
261
|
+
# reshape words need the set lookup
|
|
262
|
+
if SAFE_WORD.match?(s) && !s.end_with?(" ") && !RESHAPES_SET[s]
|
|
263
|
+
return true
|
|
264
|
+
end
|
|
265
|
+
return false if s.empty? || s != s.strip
|
|
266
|
+
return false if s.match?(/[\n\t]/)
|
|
267
|
+
c = s[0]
|
|
268
|
+
return false if "#,[]{}&*!|>'\"%@`".include?(c)
|
|
269
|
+
return false if "-?:".include?(c) && (s.length == 1 || s[1] =~ /[ \t]/)
|
|
270
|
+
return false if s.include?(": ") || s.end_with?(":") || s.include?(" #")
|
|
271
|
+
return false if RESHAPES_SET[s]
|
|
272
|
+
# compat's float grammar REQUIRES the dot ("1e3" re-reads as a
|
|
273
|
+
# String and may dump plain); ints/sexagesimals still reshape.
|
|
274
|
+
# psych's FLOAT has no trailing [.:] group — "1.2.3" is a
|
|
275
|
+
# String and dumps plain (pinned by spec/yaml_spec.rb)
|
|
276
|
+
return false if s.match?(/\A[-+]?(0|[1-9][0-9_]*)(:[0-5]?[0-9])+\z/)
|
|
277
|
+
return false if s.match?(/\A[-+]?(0|[1-9][0-9_]*)\z/)
|
|
278
|
+
return false if s.match?(/\A[-+]?[0-9][0-9_]*\.[0-9_]*([eE][-+]?[0-9]+)?\z/)
|
|
279
|
+
return false if s.match?(/\A[-+]?[0-9][0-9_]*(:[0-5]?[0-9])+\.[0-9_]*\z/)
|
|
280
|
+
return false if s.match?(/\A[-+]?(0x[0-9a-fA-F_]+|0b[01_]+|0o?[0-7_]+)\z/)
|
|
281
|
+
return false if s.match?(/\A[-+]?\.(inf|Inf|INF)\z|\A\.(nan|NaN|NAN)\z/)
|
|
282
|
+
!s.match?(/\A\d{4}-\d\d?-\d\d?([Tt ]|$)/)
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
# Psych parity (psych yaml_tree#visit_String) — the quoting
|
|
286
|
+
# decision for a String, in psych's own order:
|
|
287
|
+
# y/Y/n/N (the 1.1 bool words psych double-quotes) → double
|
|
288
|
+
# a leading non-word character, no " anywhere → double
|
|
289
|
+
# a 1.1 bad-octal shape (0[0-7]*[89]) → single
|
|
290
|
+
# re-loads as something other than String (plain_string?
|
|
291
|
+
# rejects the 1.1 number/bool/null/timestamp shapes) → single
|
|
292
|
+
# needs escapes (control bytes, multiline) → double
|
|
293
|
+
# otherwise → plain
|
|
294
|
+
# psych's literal/folded/binary forms and its !!str tag for
|
|
295
|
+
# "<<" are follow-ups; those strings double-quote today.
|
|
296
|
+
STYLE_BY_NAME = { plain: STYLE_PLAIN, single: STYLE_SQ, double: STYLE_DQ,
|
|
297
|
+
literal: STYLE_LIT }.freeze
|
|
298
|
+
|
|
299
|
+
def string_style(s)
|
|
300
|
+
# psych's visit_String: any interior newline rides a literal
|
|
301
|
+
# block (the C writer's libyaml indicator rules render it) —
|
|
302
|
+
# first, exactly as psych orders it (#290 family 4)
|
|
303
|
+
return :literal if s.match?(/\n(?!\z)/)
|
|
304
|
+
return :double if s == "y" || s == "Y" || s == "n" || s == "N"
|
|
305
|
+
return :double if !s.empty? && !s.include?('"') && s.match?(/\A[^[:word:]]/)
|
|
306
|
+
return :single if s.match?(/\A0[0-7]*[89]/)
|
|
307
|
+
return :plain if plain_string?(s)
|
|
308
|
+
return :double if s.each_byte.any? { |b| b < 0x20 || b == 0x7f }
|
|
309
|
+
:single
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def cycle_guard(obj, seen)
|
|
313
|
+
id = obj.object_id
|
|
314
|
+
raise DumpError, "cycle detected: cannot dump recursive #{obj.class}" if seen[id]
|
|
315
|
+
|
|
316
|
+
seen[id] = true
|
|
317
|
+
out = yield
|
|
318
|
+
seen.delete(id)
|
|
319
|
+
out
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
# From-scratch builder over the public construction API.
|
|
324
|
+
module Builder
|
|
325
|
+
module_function
|
|
326
|
+
|
|
327
|
+
def build(doc, obj, seen = {})
|
|
328
|
+
case obj
|
|
329
|
+
when Hash then build_map(doc, obj, seen)
|
|
330
|
+
when Array then build_seq(doc, obj, seen)
|
|
331
|
+
when String then build_string(doc, obj)
|
|
332
|
+
when Symbol then new_scalar(doc, ":#{obj}")
|
|
333
|
+
when Integer then new_scalar(doc, obj.to_s)
|
|
334
|
+
when Float then new_scalar(doc, BulkBuilder.float_text(obj))
|
|
335
|
+
when true, false then new_scalar(doc, obj.to_s)
|
|
336
|
+
when nil then new_scalar(doc, "")
|
|
337
|
+
when Time then new_scalar(doc, BulkBuilder.time_text(obj))
|
|
338
|
+
when Date then new_scalar(doc, obj.iso8601)
|
|
339
|
+
else
|
|
340
|
+
raise DumpError,
|
|
341
|
+
"cannot dump #{obj.class}: unsupported object " \
|
|
342
|
+
"(custom to_yaml support lands with the Psych Visitors)"
|
|
343
|
+
end
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
def build_map(doc, h, seen)
|
|
347
|
+
cycle_guard(h, seen) do
|
|
348
|
+
m = doc.new_mapping
|
|
349
|
+
h.each do |k, v|
|
|
350
|
+
if k.nil?
|
|
351
|
+
key_node = doc.new_scalar("", :single_quoted)
|
|
352
|
+
key_node.set_tag("!")
|
|
353
|
+
m.map_add_node(key_node, build(doc, v, seen))
|
|
354
|
+
elsif k.is_a?(::String) && k == "<<"
|
|
355
|
+
# stdlib yaml_tree: a literal chevron key carries the
|
|
356
|
+
# explicit !!str tag so it does not re-load as a merge
|
|
357
|
+
key_node = doc.new_scalar("<<", :single_quoted)
|
|
358
|
+
key_node.set_tag("!!str")
|
|
359
|
+
m.map_add_node(key_node, build(doc, v, seen))
|
|
360
|
+
else
|
|
361
|
+
m.map_add(key_text(k), build(doc, v, seen))
|
|
362
|
+
end
|
|
363
|
+
end
|
|
364
|
+
m
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
def build_seq(doc, a, seen)
|
|
369
|
+
cycle_guard(a, seen) do
|
|
370
|
+
s = doc.new_sequence
|
|
371
|
+
a.each { |e| s.seq_add(build(doc, e, seen)) }
|
|
372
|
+
s
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
# One quoting table (BulkBuilder.string_style — psych's
|
|
377
|
+
# visit_String matrix) serves both builders; this side carries
|
|
378
|
+
# the style onto the per-node DOM.
|
|
379
|
+
def build_string(doc, s)
|
|
380
|
+
# psych 5.5 visit_String's binary branch, verbatim: an
|
|
381
|
+
# ASCII-8BIT string with non-ASCII bytes emits as strict base64
|
|
382
|
+
# (pack('m0'), never wrapped) under the short !binary tag with
|
|
383
|
+
# literal style — the writer keeps the block form for tagged
|
|
384
|
+
# literals (#168). BOTH dump paths converge here (the top-level
|
|
385
|
+
# scalar fast route and the YAMLTree visitor).
|
|
386
|
+
if s.encoding == ::Encoding::ASCII_8BIT && !s.ascii_only?
|
|
387
|
+
bin = new_scalar(doc, [s].pack("m0"), :force_lit)
|
|
388
|
+
bin.set_tag("!binary")
|
|
389
|
+
return bin
|
|
390
|
+
end
|
|
391
|
+
case BulkBuilder.string_style(s)
|
|
392
|
+
when :single then new_scalar(doc, s, :force_str_sq)
|
|
393
|
+
when :double then new_scalar(doc, s, :force_str)
|
|
394
|
+
when :literal then new_scalar(doc, s, :force_lit)
|
|
395
|
+
else new_scalar(doc, s)
|
|
396
|
+
end
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
def key_text(k)
|
|
400
|
+
k = ":#{k}" if k.is_a?(Symbol)
|
|
401
|
+
k.to_s
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
def new_scalar(doc, text, mode = nil)
|
|
405
|
+
doc.new_scalar(text.to_s,
|
|
406
|
+
mode == :force_str ? :double_quoted :
|
|
407
|
+
mode == :force_str_sq ? :single_quoted :
|
|
408
|
+
mode == :force_lit ? :literal : :plain)
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
def cycle_guard(obj, seen)
|
|
412
|
+
id = obj.object_id
|
|
413
|
+
raise DumpError, "cycle detected: cannot dump recursive #{obj.class}" if seen[id]
|
|
414
|
+
|
|
415
|
+
seen[id] = true
|
|
416
|
+
out = yield
|
|
417
|
+
seen.delete(id)
|
|
418
|
+
out
|
|
419
|
+
end
|
|
420
|
+
end
|
|
421
|
+
end
|
|
422
|
+
end
|
data/lib/yeptris.rb
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yeptris
|
|
4
|
+
# The gem's version lives in the parent namespace's file — the last
|
|
5
|
+
# internal require (yeptris/version) retired with it.
|
|
6
|
+
VERSION = "0.6.18.2".freeze
|
|
7
|
+
# The error hierarchy lives in THIS file (the parent namespace's
|
|
8
|
+
# own file): nested constants do not trigger a parent-constant
|
|
9
|
+
# autoload, and the law forbids internal requires — defining the
|
|
10
|
+
# hierarchy here makes it eager by construction.
|
|
11
|
+
|
|
12
|
+
# The base error for everything this library raises deliberately.
|
|
13
|
+
class Error < StandardError; end
|
|
14
|
+
|
|
15
|
+
# The input is not valid YAML (or valid for the requested mode).
|
|
16
|
+
# message carries the C parser's line/column detail.
|
|
17
|
+
class ParseError < Error; end
|
|
18
|
+
|
|
19
|
+
# A handle was used after its document was freed. Raised, never a
|
|
20
|
+
# segfault: the Document is the sole C-memory owner and every Node
|
|
21
|
+
# checks liveness through it.
|
|
22
|
+
class FreedError < Error; end
|
|
23
|
+
|
|
24
|
+
# Building a document from a Ruby object graph hit something the
|
|
25
|
+
# builder refuses (cycles, unsupported objects).
|
|
26
|
+
class DumpError < Error; end
|
|
27
|
+
|
|
28
|
+
# Input coercion — the ONE place the input boundary is typed
|
|
29
|
+
# (no respond_to? duck-probing): IO-like objects read, Strings
|
|
30
|
+
# pass through, anything else must be stringable and is.
|
|
31
|
+
def self.read_input(source)
|
|
32
|
+
case source
|
|
33
|
+
when String then source
|
|
34
|
+
when IO, StringIO then source.read
|
|
35
|
+
else source.to_s
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
autoload :Document, "yeptris/document"
|
|
40
|
+
autoload :Node, "yeptris/node"
|
|
41
|
+
autoload :YAML, "yeptris/yaml"
|
|
42
|
+
autoload :JSON, "yeptris/json"
|
|
43
|
+
autoload :Materializer, "yeptris/materializer"
|
|
44
|
+
autoload :ValueML, "yeptris/valueml"
|
|
45
|
+
autoload :Psych, "yeptris/psych"
|
|
46
|
+
autoload :Schema, "yeptris/schema"
|
|
47
|
+
autoload :CBOR, "yeptris/cbor"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# NOTE (the #318 poisoned-process class): the Psych error-name aliases
|
|
51
|
+
# live BELOW the ffi require — reading Psych here would trigger the
|
|
52
|
+
# psych autoload BEFORE the native library resolves, and a mid-load
|
|
53
|
+
# ffi failure under a drop-in rebind left ::Psych rebound against a
|
|
54
|
+
# half-initialized FFI module (every to_yaml in the process died).
|
|
55
|
+
# With this order an ffi failure is a clean, loud require failure.
|
|
56
|
+
|
|
57
|
+
# Eager native-library resolution (leptris-ruby lesson): fail at
|
|
58
|
+
# require time, not at first parse. The ffi require MUST come after
|
|
59
|
+
# the autoload registrations — ffi.rb opens module Yeptris, and
|
|
60
|
+
# requiring it first would shadow the manifest (leptris-ruby#53).
|
|
61
|
+
begin
|
|
62
|
+
require "yeptris/ffi"
|
|
63
|
+
rescue LoadError => e
|
|
64
|
+
raise LoadError, <<~MSG
|
|
65
|
+
Yeptris could not load the native libyeptris library.
|
|
66
|
+
Set YEPTRIS_LIB_PATH to a libyeptris.{so,dylib,dll}, or use the
|
|
67
|
+
platform gem that vendors it.
|
|
68
|
+
(Underlying error: #{e.message})
|
|
69
|
+
MSG
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# The safe_load error names, aliased at the top level so consumers
|
|
74
|
+
# writing rescues need no nesting knowledge (issue #73's naming
|
|
75
|
+
# note): Yeptris::DisallowedClass is Yeptris::Psych::DisallowedClass.
|
|
76
|
+
# LAZY (const_missing): a standalone require "yeptris/psych" loads
|
|
77
|
+
# yeptris.rb MID-psych — an eager alias here would re-enter the
|
|
78
|
+
# half-loaded psych.rb through the autoload and NameError. At first
|
|
79
|
+
# touch psych is complete in every load order.
|
|
80
|
+
module Yeptris
|
|
81
|
+
def self.const_missing(name)
|
|
82
|
+
case name
|
|
83
|
+
when :DisallowedClass, :AliasesError
|
|
84
|
+
const_set(name, Psych.const_get(name))
|
|
85
|
+
else
|
|
86
|
+
super
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Optional C-API materializer (TODO.restructure/22): fused visit →
|
|
92
|
+
# Ruby objects via the Ruby C API. Feature-detected — LoadError leaves
|
|
93
|
+
# the FFI ladder (Marshal → columns → records) as the sole path.
|
|
94
|
+
# Windows names the artifact per Ruby minor (the leptris-ruby #207/
|
|
95
|
+
# #227 lesson): a PE DLL must bind its build Ruby's runtime, so one
|
|
96
|
+
# DLL per supported minor ships and RUBY_VERSION selects. A missing
|
|
97
|
+
# cell (Ruby 3.3 arm64 has no build) falls back LOUDLY to the ladder.
|
|
98
|
+
begin
|
|
99
|
+
# per-minor FIRST everywhere: a single version-agnostic native.so
|
|
100
|
+
# loads under ANY Ruby (symbols resolve from the host process), and
|
|
101
|
+
# one built for a different minor is an ABI gamble — Ruby 3.0 ran
|
|
102
|
+
# a 3.3-built ext and the JSON parse failed. The plain name stays
|
|
103
|
+
# as the dev-checkout fallback (built for the running Ruby).
|
|
104
|
+
require "yeptris/native-#{RUBY_VERSION[/\A\d+\.\d+/]}"
|
|
105
|
+
rescue LoadError
|
|
106
|
+
begin
|
|
107
|
+
require "yeptris/native"
|
|
108
|
+
rescue LoadError => e
|
|
109
|
+
warn "yeptris: no precompiled native materializer for Ruby " \
|
|
110
|
+
"#{RUBY_VERSION[/\A\d+\.\d+/]} on this platform " \
|
|
111
|
+
"(#{e.message}); the FFI ladder carries the load"
|
|
112
|
+
end
|
|
113
|
+
end
|
data/libyeptris.so
ADDED
|
Binary file
|