yeptris 0.2.4.1 → 0.4.0.1
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 +4 -4
- data/ext/build_windows_native.rb +41 -0
- data/lib/yeptris/document.rb +5 -0
- data/lib/yeptris/ffi.rb +24 -0
- data/lib/yeptris/json.rb +226 -1
- data/lib/yeptris.rb +16 -3
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8d15146500e202e6c6ce0b97073284d0ac31f78df2b0260c2bac7b1e733747df
|
|
4
|
+
data.tar.gz: 8521281dbdaffdd88551de237741f773a02381613a4b9640c34d1b46dd1e9369
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c2b9eaa728ead723567966836a16eee18a48f5b5bbf8c2d5f1f3dc4d7cdcee69ce237cd19c7eabf4fbb55abf1e72437697ec6f7fe96e3704b342fb7a8d3da8ff
|
|
7
|
+
data.tar.gz: a857fd5b3e2d86f2b8f142263cb609a00eb21458a5b035b681fa0c9f24097ed11f7162c69ad20b3eaf8faea47d49e03d999377f8bacde847416b79b202469e24
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Builds the native materializer DLL for the CURRENT Ruby and
|
|
4
|
+
# installs it under its minor-versioned name
|
|
5
|
+
# (lib/yeptris/native-<major.minor>.so).
|
|
6
|
+
#
|
|
7
|
+
# The leptris-ruby Windows lesson (#207/#227): a PE DLL cannot
|
|
8
|
+
# resolve Ruby imports lazily — it must bind the build Ruby's
|
|
9
|
+
# x64-ucrt-rubyNNN.dll. One DLL per supported minor therefore ships
|
|
10
|
+
# in the Windows platform gems, and the loader in lib/yeptris.rb
|
|
11
|
+
# picks by RUBY_VERSION at require. Ruby 3.3 has no arm64 build:
|
|
12
|
+
# that cell ships no artifact and falls back loudly to the FFI
|
|
13
|
+
# ladder.
|
|
14
|
+
#
|
|
15
|
+
# Standalone by design: the release workflow runs this under each
|
|
16
|
+
# Ruby minor (3.3/3.4/4.0) with no bundler context.
|
|
17
|
+
|
|
18
|
+
require "rbconfig"
|
|
19
|
+
require "fileutils"
|
|
20
|
+
|
|
21
|
+
root = File.expand_path("..", __dir__)
|
|
22
|
+
ext_dir = File.join(root, "ext", "yeptris_native")
|
|
23
|
+
minor = RUBY_VERSION[/\A\d+\.\d+/]
|
|
24
|
+
|
|
25
|
+
Dir.chdir(ext_dir) do
|
|
26
|
+
system(RbConfig.ruby, "extconf.rb") or abort "extconf failed under #{RUBY_VERSION}"
|
|
27
|
+
# mkmf's link binds the CURRENT Ruby's runtime DLL — exactly
|
|
28
|
+
# what the versioned naming is for.
|
|
29
|
+
success = system("make")
|
|
30
|
+
abort "make failed under #{RUBY_VERSION}" unless success
|
|
31
|
+
so = Dir.glob("native.{so,dll}").first
|
|
32
|
+
abort "native bundle not produced under #{RUBY_VERSION}" unless so
|
|
33
|
+
dest = File.join(root, "lib", "yeptris", "native-#{minor}.so")
|
|
34
|
+
# The artifact must reference only this minor's Ruby DLL.
|
|
35
|
+
imported = `strings #{so} 2>/dev/null`[/[a-z0-9-]*ruby\d{3,}\.dll/i]
|
|
36
|
+
if imported && !imported.include?("ruby#{minor.delete('.')}")
|
|
37
|
+
abort "#{so} imports #{imported} but was built under #{RUBY_VERSION} — refuse to mis-name it"
|
|
38
|
+
end
|
|
39
|
+
FileUtils.cp(so, dest)
|
|
40
|
+
puts "Installed native materializer for Ruby #{minor} -> #{dest} (#{imported || 'no ruby dll string found'})"
|
|
41
|
+
end
|
data/lib/yeptris/document.rb
CHANGED
|
@@ -153,6 +153,11 @@ class Yeptris::Document
|
|
|
153
153
|
Yeptris::FFI::Owned.string(ptr, len)
|
|
154
154
|
end
|
|
155
155
|
|
|
156
|
+
def serialize_json_compact
|
|
157
|
+
len = ::FFI::MemoryPointer.new(:uint64)
|
|
158
|
+
Yeptris::FFI::Owned.string(Yeptris::FFI.yeptris_serialize_json_ex(@c_ptr, len, 1), len)
|
|
159
|
+
end
|
|
160
|
+
|
|
156
161
|
def serialize_json
|
|
157
162
|
ensure_alive!
|
|
158
163
|
len = ::FFI::MemoryPointer.new(:uint64)
|
data/lib/yeptris/ffi.rb
CHANGED
|
@@ -59,6 +59,21 @@ module Yeptris
|
|
|
59
59
|
%i[pointer size_t pointer yeptris_status_out], :yeptris_document
|
|
60
60
|
attach_function :yeptris_parse_json, %i[pointer size_t yeptris_status_out], :yeptris_document
|
|
61
61
|
|
|
62
|
+
# The JSON tape (TODO.restructure/85; the JSON surface's load
|
|
63
|
+
# engine — issue #81): ONE call, three bulk columns, spans borrow
|
|
64
|
+
# the caller's string (no arena copy, no second validating parse).
|
|
65
|
+
# v2 records: numbers are spans at parse; yeptris_tape_convert
|
|
66
|
+
# materializes them in one bulk call.
|
|
67
|
+
class JsonTape < ::FFI::Struct
|
|
68
|
+
layout :count, :size_t, :kinds, :pointer, :offs, :pointer,
|
|
69
|
+
:lens, :pointer, :int_min, :int64, :_src, :pointer, :_srclen, :size_t,
|
|
70
|
+
:_block, :pointer
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
attach_function :yeptris_parse_json_tape, %i[pointer size_t pointer], :int
|
|
74
|
+
attach_function :yeptris_tape_free, [:pointer], :void
|
|
75
|
+
attach_function :yeptris_tape_convert, %i[pointer size_t size_t pointer pointer], :size_t
|
|
76
|
+
|
|
62
77
|
attach_function :yeptris_document_free, [:yeptris_document], :void
|
|
63
78
|
attach_function :yeptris_document_count, [:yeptris_document], :size_t
|
|
64
79
|
attach_function :yeptris_document_root, [:yeptris_document, :size_t], :yeptris_node
|
|
@@ -174,6 +189,15 @@ module Yeptris
|
|
|
174
189
|
%i[yeptris_document pointer pointer], :pointer
|
|
175
190
|
attach_function :yeptris_serialize_json, %i[yeptris_document pointer], :pointer
|
|
176
191
|
|
|
192
|
+
# compact JSON generation (JSON.generate's shape); absent on
|
|
193
|
+
# libraries before v0.2.5 — the JSON surface feature-detects
|
|
194
|
+
JSON_EX = begin
|
|
195
|
+
attach_function :yeptris_serialize_json_ex, %i[yeptris_document pointer int], :pointer
|
|
196
|
+
true
|
|
197
|
+
rescue ::FFI::NotFoundError
|
|
198
|
+
false
|
|
199
|
+
end
|
|
200
|
+
|
|
177
201
|
# Owned char* results (serialize*): one reader, freed exactly once.
|
|
178
202
|
# The buffers are plain malloc'd C memory (the header contract says
|
|
179
203
|
# "caller frees"), so the release is libc free.
|
data/lib/yeptris/json.rb
CHANGED
|
@@ -36,7 +36,157 @@ module Yeptris
|
|
|
36
36
|
raise ParseError, e.message
|
|
37
37
|
end
|
|
38
38
|
end
|
|
39
|
-
|
|
39
|
+
tape_engine(source)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# The tape engine (issue #81): ONE call validates and materializes
|
|
43
|
+
# the record stream; the walk reads four bulk columns and slices
|
|
44
|
+
# strings straight out of the caller's source — no gate document,
|
|
45
|
+
# no recorder transform, no arena copy (the drain path paid all
|
|
46
|
+
# three, which is where medium/large throughput went).
|
|
47
|
+
T_DOC = 0
|
|
48
|
+
T_NULL = 1
|
|
49
|
+
T_TRUE = 2
|
|
50
|
+
T_FALSE = 3
|
|
51
|
+
T_INT = 4
|
|
52
|
+
T_FLOAT = 5
|
|
53
|
+
T_STR = 6
|
|
54
|
+
T_SEQ_OPEN = 7
|
|
55
|
+
T_MAP_OPEN = 8
|
|
56
|
+
T_CLOSE = 9
|
|
57
|
+
|
|
58
|
+
# The tape keeps RAW string spans (escapes untouched — the C
|
|
59
|
+
# contract); the walk decodes them here. The hot path (no
|
|
60
|
+
# backslash) returns the slice untouched.
|
|
61
|
+
SIMPLE_ESCAPES = {
|
|
62
|
+
'"' => '"'.b, "\\" => "\\".b, "/" => "/".b, "b" => "\b".b, "f" => "\f".b,
|
|
63
|
+
"n" => "\n".b, "r" => "\r".b, "t" => "\t".b
|
|
64
|
+
}.freeze
|
|
65
|
+
|
|
66
|
+
def decode_span(src, off, len)
|
|
67
|
+
s = src.byteslice(off, len)
|
|
68
|
+
s.force_encoding(Encoding::UTF_8)
|
|
69
|
+
return s unless s.include?("\\")
|
|
70
|
+
|
|
71
|
+
b = s.b
|
|
72
|
+
out = +"".b
|
|
73
|
+
i = 0
|
|
74
|
+
n = b.bytesize
|
|
75
|
+
while i < n
|
|
76
|
+
c = b.getbyte(i)
|
|
77
|
+
if c != 0x5C || i + 1 >= n
|
|
78
|
+
out << c
|
|
79
|
+
i += 1
|
|
80
|
+
next
|
|
81
|
+
end
|
|
82
|
+
e = b.getbyte(i + 1)
|
|
83
|
+
if e != 0x75 # simple escape
|
|
84
|
+
ch = e.chr
|
|
85
|
+
out << (SIMPLE_ESCAPES[ch] || ch)
|
|
86
|
+
i += 2
|
|
87
|
+
next
|
|
88
|
+
end
|
|
89
|
+
cp = b.byteslice(i + 2, 4).to_i(16)
|
|
90
|
+
if cp >= 0xD800 && cp <= 0xDBFF && i + 12 <= n &&
|
|
91
|
+
b.getbyte(i + 6) == 0x5C && b.getbyte(i + 7) == 0x75
|
|
92
|
+
lo = b.byteslice(i + 8, 4).to_i(16)
|
|
93
|
+
if lo >= 0xDC00 && lo <= 0xDFFF
|
|
94
|
+
cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00)
|
|
95
|
+
i += 12
|
|
96
|
+
else
|
|
97
|
+
i += 6
|
|
98
|
+
end
|
|
99
|
+
else
|
|
100
|
+
i += 6
|
|
101
|
+
end
|
|
102
|
+
out << [cp].pack("U").b
|
|
103
|
+
end
|
|
104
|
+
out.force_encoding(Encoding::UTF_8)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def tape_engine(source)
|
|
108
|
+
tape = ::Yeptris::FFI::JsonTape.new
|
|
109
|
+
rc = ::Yeptris::FFI.yeptris_parse_json_tape(source, source.bytesize, tape)
|
|
110
|
+
raise ParseError, ::Yeptris::FFI.last_error_message if rc != ::Yeptris::FFI::OK
|
|
111
|
+
|
|
112
|
+
begin
|
|
113
|
+
walk_tape(source, tape)
|
|
114
|
+
ensure
|
|
115
|
+
::Yeptris::FFI.yeptris_tape_free(tape)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def walk_tape(src, tape)
|
|
120
|
+
n = tape[:count]
|
|
121
|
+
kinds = tape[:kinds].read_bytes(n).unpack("C*")
|
|
122
|
+
offs = tape[:offs].read_bytes(n * 4).unpack("V*")
|
|
123
|
+
lens = tape[:lens].read_bytes(n * 4).unpack("V*")
|
|
124
|
+
# v2: parse records number spans only — ONE bulk convert call
|
|
125
|
+
# materializes them (per-number FFI calls would sink the ladder)
|
|
126
|
+
ivp = ::FFI::MemoryPointer.new(:int64, n, true)
|
|
127
|
+
dvp = ::FFI::MemoryPointer.new(:double, n, true)
|
|
128
|
+
::Yeptris::FFI.yeptris_tape_convert(tape, 0, n, ivp, dvp)
|
|
129
|
+
vals_i = ivp.read_bytes(n * 8).unpack("q<*")
|
|
130
|
+
vals_f = dvp.read_bytes(n * 8).unpack("E*")
|
|
131
|
+
# any integer text beyond int64: every INT rebuilds from its span
|
|
132
|
+
# (exact Bignum; JSON.parse parity) — int_min is set by the
|
|
133
|
+
# convert call above
|
|
134
|
+
exact_ints = !tape[:int_min].zero?
|
|
135
|
+
utf8 = src.encoding == Encoding::UTF_8
|
|
136
|
+
|
|
137
|
+
docs = [nil] # record 0 (DOC) pre-consumed — the root's slot
|
|
138
|
+
stack = []
|
|
139
|
+
key_sets = STRICT_DUPLICATE_KEYS ? [{}] : nil
|
|
140
|
+
pending_key = nil
|
|
141
|
+
i = 1
|
|
142
|
+
while i < n
|
|
143
|
+
case kinds[i]
|
|
144
|
+
when T_SEQ_OPEN
|
|
145
|
+
place(docs, stack, pending_key) { [] }
|
|
146
|
+
pending_key = nil
|
|
147
|
+
when T_MAP_OPEN
|
|
148
|
+
key_sets&.push({})
|
|
149
|
+
place(docs, stack, pending_key) { {} }
|
|
150
|
+
pending_key = nil
|
|
151
|
+
when T_CLOSE
|
|
152
|
+
stack.pop
|
|
153
|
+
key_sets&.pop
|
|
154
|
+
when T_STR
|
|
155
|
+
text = lens[i] == 0 ? +"".force_encoding(Encoding::UTF_8) : decode_span(src, offs[i], lens[i])
|
|
156
|
+
if pending_key.nil? && !stack.empty? && stack.last.is_a?(Hash)
|
|
157
|
+
if key_sets && key_sets.last.key?(text)
|
|
158
|
+
raise ParseError, %(duplicate key "#{text}" in JSON object)
|
|
159
|
+
end
|
|
160
|
+
key_sets&.last&.store(text, true)
|
|
161
|
+
pending_key = text
|
|
162
|
+
else
|
|
163
|
+
place(docs, stack, pending_key) { text }
|
|
164
|
+
pending_key = nil
|
|
165
|
+
end
|
|
166
|
+
when T_INT
|
|
167
|
+
if exact_ints
|
|
168
|
+
place(docs, stack, pending_key) { Integer(src.byteslice(offs[i], lens[i]), 10) }
|
|
169
|
+
else
|
|
170
|
+
v = vals_i[i]
|
|
171
|
+
place(docs, stack, pending_key) { v }
|
|
172
|
+
end
|
|
173
|
+
pending_key = nil
|
|
174
|
+
when T_FLOAT
|
|
175
|
+
v = vals_f[i]
|
|
176
|
+
place(docs, stack, pending_key) { v }
|
|
177
|
+
pending_key = nil
|
|
178
|
+
when T_TRUE, T_FALSE # v2: the kind IS the value
|
|
179
|
+
place(docs, stack, pending_key) { kinds[i] == T_TRUE }
|
|
180
|
+
pending_key = nil
|
|
181
|
+
when T_NULL
|
|
182
|
+
place(docs, stack, pending_key) { nil }
|
|
183
|
+
pending_key = nil
|
|
184
|
+
else
|
|
185
|
+
raise Error, "internal: impossible tape record #{kinds[i]}"
|
|
186
|
+
end
|
|
187
|
+
i += 1
|
|
188
|
+
end
|
|
189
|
+
docs.empty? ? nil : docs.first
|
|
40
190
|
end
|
|
41
191
|
|
|
42
192
|
# The always-available engine: the strict-JSON validator gates
|
|
@@ -143,6 +293,81 @@ module Yeptris
|
|
|
143
293
|
docs.empty? ? nil : docs.first
|
|
144
294
|
end
|
|
145
295
|
|
|
296
|
+
# ---- generation (issue #81) ------------------------------------------
|
|
297
|
+
#
|
|
298
|
+
# `dump` targets JSON.generate semantics for the core types: the
|
|
299
|
+
# object walk reuses YAML.dump's packed-entry bulk builder (one
|
|
300
|
+
# FFI build call), then the strict-JSON writer serializes —
|
|
301
|
+
# Strings ride DOUBLE_QUOTED (a "42" stays quoted; numbers, bools
|
|
302
|
+
# and null stay plain), Symbols become their name.
|
|
303
|
+
class DumpError < Error; end
|
|
304
|
+
|
|
305
|
+
def dump(obj)
|
|
306
|
+
parts = String.new(encoding: Encoding::BINARY,
|
|
307
|
+
capacity: 12 * 16) # 12-byte entries, appended in place
|
|
308
|
+
blob = String.new(encoding: Encoding::BINARY)
|
|
309
|
+
off = [0]
|
|
310
|
+
place_obj(obj, parts, blob, off)
|
|
311
|
+
doc = ::Yeptris::Document.create
|
|
312
|
+
# FFI passes String args BY REFERENCE — no MemoryPointer copies
|
|
313
|
+
rc = doc.build_entries(parts, parts.bytesize / 12, blob, blob.bytesize)
|
|
314
|
+
raise DumpError, "document_build failed: #{rc}" unless rc == ::Yeptris::FFI::OK
|
|
315
|
+
out = ::Yeptris::FFI::JSON_EX ? doc.serialize_json_compact : doc.serialize_json
|
|
316
|
+
# JSON.generate emits no trailing newline (the document writer does)
|
|
317
|
+
out.end_with?("\n") ? out[0, out.bytesize - 1] : out
|
|
318
|
+
ensure
|
|
319
|
+
doc&.free
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
F = ::Yeptris::FFI
|
|
323
|
+
MAP_E = [F::BUILD_MAP, 0, 0, 0].pack("CCx2VV").freeze
|
|
324
|
+
SEQ_E = [F::BUILD_SEQ, 0, 0, 0].pack("CCx2VV").freeze
|
|
325
|
+
END_E = [F::BUILD_END, 0, 0, 0].pack("CCx2VV").freeze
|
|
326
|
+
DQ = F::STYLE_DOUBLE_QUOTED
|
|
327
|
+
PL = F::STYLE_PLAIN
|
|
328
|
+
SC_DQ = [F::BUILD_SCALAR, DQ].pack("CCx2").freeze
|
|
329
|
+
SC_PL = [F::BUILD_SCALAR, PL].pack("CCx2").freeze
|
|
330
|
+
|
|
331
|
+
def place_obj(obj, parts, blob, off)
|
|
332
|
+
case obj
|
|
333
|
+
when Hash
|
|
334
|
+
parts << MAP_E
|
|
335
|
+
obj.each do |k, v|
|
|
336
|
+
key = k.is_a?(String) ? k : k.is_a?(Symbol) ? k.name : k.to_s
|
|
337
|
+
scalar_json(key, SC_DQ, parts, blob, off)
|
|
338
|
+
place_obj(v, parts, blob, off)
|
|
339
|
+
end
|
|
340
|
+
parts << END_E
|
|
341
|
+
when Array
|
|
342
|
+
parts << SEQ_E
|
|
343
|
+
obj.each { |e| place_obj(e, parts, blob, off) }
|
|
344
|
+
parts << END_E
|
|
345
|
+
when String
|
|
346
|
+
scalar_json(obj, SC_DQ, parts, blob, off)
|
|
347
|
+
when Symbol
|
|
348
|
+
scalar_json(obj.name, SC_DQ, parts, blob, off)
|
|
349
|
+
when Integer, Float
|
|
350
|
+
scalar_json(obj.to_s, SC_PL, parts, blob, off)
|
|
351
|
+
when true
|
|
352
|
+
scalar_json("true", SC_PL, parts, blob, off)
|
|
353
|
+
when false
|
|
354
|
+
scalar_json("false", SC_PL, parts, blob, off)
|
|
355
|
+
when nil
|
|
356
|
+
scalar_json("null", SC_PL, parts, blob, off)
|
|
357
|
+
when Date, Time
|
|
358
|
+
scalar_json(obj.iso8601, SC_DQ, parts, blob, off)
|
|
359
|
+
else
|
|
360
|
+
raise DumpError, "cannot dump #{obj.class}: unsupported object " \
|
|
361
|
+
"(JSON.generate calls to_json on custom types)"
|
|
362
|
+
end
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
def scalar_json(text, prefix, parts, blob, off)
|
|
366
|
+
parts << (prefix + [off[0], text.bytesize].pack("VV"))
|
|
367
|
+
blob << text
|
|
368
|
+
off[0] += text.bytesize
|
|
369
|
+
end
|
|
370
|
+
|
|
146
371
|
def place(docs, stack, key)
|
|
147
372
|
v = yield
|
|
148
373
|
if stack.empty?
|
data/lib/yeptris.rb
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module Yeptris
|
|
4
4
|
# The gem's version lives in the parent namespace's file — the last
|
|
5
5
|
# internal require (yeptris/version) retired with it.
|
|
6
|
-
VERSION = "0.
|
|
6
|
+
VERSION = "0.4.0.1".freeze
|
|
7
7
|
# The error hierarchy lives in THIS file (the parent namespace's
|
|
8
8
|
# own file): nested constants do not trigger a parent-constant
|
|
9
9
|
# autoload, and the law forbids internal requires — defining the
|
|
@@ -70,8 +70,21 @@ end
|
|
|
70
70
|
# Optional C-API materializer (TODO.restructure/22): fused visit →
|
|
71
71
|
# Ruby objects via the Ruby C API. Feature-detected — LoadError leaves
|
|
72
72
|
# the FFI ladder (Marshal → columns → records) as the sole path.
|
|
73
|
+
# Windows names the artifact per Ruby minor (the leptris-ruby #207/
|
|
74
|
+
# #227 lesson): a PE DLL must bind its build Ruby's runtime, so one
|
|
75
|
+
# DLL per supported minor ships and RUBY_VERSION selects. A missing
|
|
76
|
+
# cell (Ruby 3.3 arm64 has no build) falls back LOUDLY to the ladder.
|
|
73
77
|
begin
|
|
74
|
-
|
|
75
|
-
|
|
78
|
+
if Gem.win_platform?
|
|
79
|
+
require "yeptris/native-#{RUBY_VERSION[/\A\d+\.\d+/]}"
|
|
80
|
+
else
|
|
81
|
+
require "yeptris/native"
|
|
82
|
+
end
|
|
83
|
+
rescue LoadError => e
|
|
84
|
+
if Gem.win_platform?
|
|
85
|
+
warn "yeptris: no precompiled native materializer for Ruby " \
|
|
86
|
+
"#{RUBY_VERSION[/\A\d+\.\d+/]} on this platform " \
|
|
87
|
+
"(#{e.message}); the FFI ladder carries the load"
|
|
88
|
+
end
|
|
76
89
|
# FFI ladder only
|
|
77
90
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: yeptris
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-09-
|
|
11
|
+
date: 2026-09-16 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: ffi
|
|
@@ -34,6 +34,7 @@ extensions: []
|
|
|
34
34
|
extra_rdoc_files: []
|
|
35
35
|
files:
|
|
36
36
|
- README.adoc
|
|
37
|
+
- ext/build_windows_native.rb
|
|
37
38
|
- ext/yeptris_native/extconf.rb
|
|
38
39
|
- ext/yeptris_native/json_ruby.c
|
|
39
40
|
- ext/yeptris_native/yeptris_native.c
|