lutaml-model 0.8.138 → 0.8.140
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/lib/lutaml/model/attribute.rb +8 -1
- data/lib/lutaml/model/serialize/initialization.rb +0 -3
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/xml/leptris/native_emission.rb +145 -37
- data/lib/lutaml/xml/mapping.rb +1 -0
- data/lib/lutaml/xml/model_transform.rb +8 -5
- data/spec/lutaml/model/map_all_attributes_spec.rb +31 -0
- data/spec/lutaml/xml/plan_fast_path_spec.rb +61 -0
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e4340bd1bf4b75d088acb5a60a68073d2980d419fe5c7f59412966956a58e377
|
|
4
|
+
data.tar.gz: 829a741bc56fa070059d8198887cc51051859f64a2aeab747814c5f0d278356b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 14cda9bfd53830559db4848694f3118289958108e17f9445a079c585c8aed5f637878d4b1a0b4f232f1dca8d12100f4d56d73bb8289d2e73cd625a42682c90df
|
|
7
|
+
data.tar.gz: 124577fb5f037a6dea46e3da4f409713fadcc2ca2398e34a578d47230be5651274d3eabac7342fc708b2939c59d56f4248bfe97da88cc39dddce509cdffc83ef
|
|
@@ -379,6 +379,13 @@ module Lutaml
|
|
|
379
379
|
# Performance: Frozen empty hash to reduce allocations
|
|
380
380
|
EMPTY_TRANSFORM_HASH = {}.freeze
|
|
381
381
|
|
|
382
|
+
# Default-argument options for the serialize lane. The per-call
|
|
383
|
+
# `options = {}` literal allocated one Hash per serialize call
|
|
384
|
+
# (two per mapped XML row); every consumer of the argument only
|
|
385
|
+
# reads it or derives a new hash (merge), so a shared frozen
|
|
386
|
+
# empty is safe.
|
|
387
|
+
EMPTY_OPTIONS = {}.freeze
|
|
388
|
+
|
|
382
389
|
def transform
|
|
383
390
|
# Options are fixed after definition (value_policy memoizes the
|
|
384
391
|
# same way) — the per-call @options[:transform] Hash#[] was hit
|
|
@@ -918,7 +925,7 @@ instance_object = nil)
|
|
|
918
925
|
true
|
|
919
926
|
end
|
|
920
927
|
|
|
921
|
-
def serialize(value, format, register, options =
|
|
928
|
+
def serialize(value, format, register, options = EMPTY_OPTIONS)
|
|
922
929
|
value ||= build_collection if collection? && initialize_empty?
|
|
923
930
|
return value if value.nil? || Utils.uninitialized?(value)
|
|
924
931
|
|
|
@@ -374,9 +374,6 @@ module Lutaml
|
|
|
374
374
|
# compiled seed cannot know about (SchemaRepository's
|
|
375
375
|
# @parsed_schemas/@namespace_registry/@type_index): construct
|
|
376
376
|
# through the real constructor for them, exactly as the walk
|
|
377
|
-
# did before the compiled seed existed. The seed stays the
|
|
378
|
-
# fast path for classes using the standard initializer.
|
|
379
|
-
return new(lutaml_register: register) if constructs_through_initialize?
|
|
380
377
|
# did before the compiled seed existed, register included. The
|
|
381
378
|
# seed stays the fast path for classes using the standard
|
|
382
379
|
# initializer.
|
data/lib/lutaml/model/version.rb
CHANGED
|
@@ -31,6 +31,17 @@ module Lutaml
|
|
|
31
31
|
ROOT_ROW = 0xFFFF_FFFF
|
|
32
32
|
EMITTABLE_KINDS = %i[scalar collection nested].freeze
|
|
33
33
|
|
|
34
|
+
# Fiber-local buffer of reusable op Hash slots. The engine's
|
|
35
|
+
# build_result copies every op into FFI memory before it
|
|
36
|
+
# returns, so the Ruby hashes are dead weight past the call;
|
|
37
|
+
# recycling them positionally removes one Hash allocation per
|
|
38
|
+
# emitted op (four per mapped row on the emission lane) at no
|
|
39
|
+
# semantic cost. Slots are cleared before every reuse, so
|
|
40
|
+
# stale keys from a prior kind cannot leak into an op.
|
|
41
|
+
OP_BUFFER_KEY = :lutaml_model_leptris_native_emission_op_buffer
|
|
42
|
+
OP_BUFFER_BASE_KEY =
|
|
43
|
+
:lutaml_model_leptris_native_emission_op_buffer_base
|
|
44
|
+
|
|
34
45
|
class << self
|
|
35
46
|
def compatible?(plan)
|
|
36
47
|
!plan[:ordered] && tree_emittable?(plan[:tree])
|
|
@@ -49,28 +60,59 @@ module Lutaml
|
|
|
49
60
|
end
|
|
50
61
|
end
|
|
51
62
|
|
|
63
|
+
# len is the logical length of the buffer: slots [base, len) hold
|
|
64
|
+
# the ops of the emission in progress. base is nonzero when a
|
|
65
|
+
# user hook re-enters to_xml mid-emission — the inner
|
|
66
|
+
# emission appends after the outer's written slots instead of
|
|
67
|
+
# overwriting them. The tail is trimmed after the engine
|
|
68
|
+
# consumed the result so a pathological document cannot grow
|
|
69
|
+
# the buffer without bound.
|
|
52
70
|
def emit(instance, plan, register)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
71
|
+
buf = (Thread.current[OP_BUFFER_KEY] ||= [])
|
|
72
|
+
base = (Thread.current[OP_BUFFER_BASE_KEY] ||= 0)
|
|
73
|
+
len = base
|
|
74
|
+
begin
|
|
75
|
+
op_slot(buf, len)
|
|
76
|
+
buf[len][:kind] = :element
|
|
77
|
+
buf[len][:row_index] = ROOT_ROW
|
|
78
|
+
len = emit_element(buf, len + 1, instance, plan, register, 0)
|
|
79
|
+
op_slot(buf, len)
|
|
80
|
+
buf[len][:kind] = :end
|
|
81
|
+
len += 1
|
|
82
|
+
compact = plan[:descriptor].serialize(
|
|
83
|
+
plan[:descriptor].build_result(buf[base, len - base]),
|
|
84
|
+
)
|
|
85
|
+
::Leptris::XML.parse(compact).to_xml(indent: 2, no_decl: true)
|
|
86
|
+
ensure
|
|
87
|
+
buf.slice!(len..) if buf.size > len
|
|
88
|
+
Thread.current[OP_BUFFER_BASE_KEY] = base
|
|
89
|
+
end
|
|
60
90
|
end
|
|
61
91
|
|
|
62
92
|
private
|
|
63
93
|
|
|
94
|
+
# Cleared slot len of the buffer, reusing the Hash object a
|
|
95
|
+
# previous emission left there when present. Also publishes
|
|
96
|
+
# this emission's exclusive end as the fiber-local base: the
|
|
97
|
+
# walk evaluates user code (model getters, default procs,
|
|
98
|
+
# registered serializers) between slots, and a re-entrant
|
|
99
|
+
# to_xml from that code must append after every written op,
|
|
100
|
+
# never overlap this emission's slots.
|
|
101
|
+
def op_slot(buf, len)
|
|
102
|
+
Thread.current[OP_BUFFER_BASE_KEY] = len + 1
|
|
103
|
+
(buf[len] ||= {}).clear
|
|
104
|
+
end
|
|
105
|
+
|
|
64
106
|
# plan_index: this plan's flattened index. The engine's
|
|
65
107
|
# flatten_plan pushes each nested row's child plan exactly
|
|
66
108
|
# once — collection members share that slot — so child flat
|
|
67
109
|
# indices come from a static per-plan layout (relative to
|
|
68
110
|
# this plan's index), never from a runtime counter.
|
|
69
|
-
def emit_element(
|
|
111
|
+
def emit_element(buf, len, instance, plan, register, plan_index)
|
|
70
112
|
my_index = plan_index
|
|
71
113
|
layout = flat_layout_for(plan)
|
|
72
114
|
|
|
73
|
-
emit_attributes(
|
|
115
|
+
len = emit_attributes(buf, len, instance, plan, my_index, register)
|
|
74
116
|
|
|
75
117
|
plan[:rows].each_with_index do |(rule, attr, kind, _spelling, delegate), row_index|
|
|
76
118
|
value = value_of(instance, attr, delegate)
|
|
@@ -83,31 +125,41 @@ module Lutaml
|
|
|
83
125
|
# as the DOM lane does.
|
|
84
126
|
if value.is_a?(::Array)
|
|
85
127
|
value.each do |item|
|
|
86
|
-
|
|
128
|
+
len = scalar_op(buf, len, my_index, row_index, attr, item,
|
|
129
|
+
register)
|
|
87
130
|
end
|
|
88
131
|
else
|
|
89
|
-
|
|
132
|
+
len = scalar_op(buf, len, my_index, row_index, attr, value,
|
|
133
|
+
register)
|
|
90
134
|
end
|
|
91
135
|
when :collection_native, :collection_cb
|
|
92
136
|
Array(value).each do |item|
|
|
93
|
-
|
|
137
|
+
len = scalar_op(buf, len, my_index, row_index, attr, item,
|
|
138
|
+
register)
|
|
94
139
|
end
|
|
95
140
|
when :nested
|
|
96
141
|
_tree, child_rel = layout[row_index]
|
|
97
142
|
next unless child_rel
|
|
98
143
|
|
|
144
|
+
child_plans = child_plans_for(plan, row_index)
|
|
99
145
|
Array(value).each do |item|
|
|
100
|
-
child_plan =
|
|
101
|
-
(
|
|
146
|
+
child_plan = child_plans[item.class] ||
|
|
147
|
+
(child_plans[item.class] =
|
|
102
148
|
PlanCompiler.compile(item.class, register))
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
149
|
+
op_slot(buf, len)
|
|
150
|
+
buf[len][:kind] = :element
|
|
151
|
+
buf[len][:plan_index] = my_index
|
|
152
|
+
buf[len][:row_index] = row_index
|
|
153
|
+
len += 1
|
|
154
|
+
len = emit_element(buf, len, item, child_plan,
|
|
155
|
+
register, my_index + child_rel)
|
|
156
|
+
op_slot(buf, len)
|
|
157
|
+
buf[len][:kind] = :end
|
|
158
|
+
len += 1
|
|
108
159
|
end
|
|
109
160
|
end
|
|
110
161
|
end
|
|
162
|
+
len
|
|
111
163
|
end
|
|
112
164
|
|
|
113
165
|
# {nested row_index => [child tree, relative flat index]} —
|
|
@@ -133,19 +185,50 @@ module Lutaml
|
|
|
133
185
|
end
|
|
134
186
|
end
|
|
135
187
|
|
|
136
|
-
|
|
188
|
+
# The name bookkeeping is static per plan — attr_rows and the
|
|
189
|
+
# tree's attribute rows never vary per instance — so the
|
|
190
|
+
# name->tree row_index map is memoized on the plan. Only
|
|
191
|
+
# instances carrying a recorded attribute order (parsed
|
|
192
|
+
# documents whose XML attribute sequence must be replayed)
|
|
193
|
+
# take the per-instance path.
|
|
194
|
+
def emit_attributes(buf, len, instance, plan, plan_index, register)
|
|
137
195
|
recorded = instance.attribute_order
|
|
138
|
-
attr_rows = plan[:attr_rows]
|
|
139
196
|
if recorded && !recorded.empty?
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
197
|
+
emit_attributes_recorded(buf, len, instance, plan, plan_index,
|
|
198
|
+
register, recorded)
|
|
199
|
+
return len
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
tree_attrs = plan[:tree][:attributes] || []
|
|
203
|
+
row_indices = attr_row_indices_for(plan, tree_attrs)
|
|
204
|
+
attr_rows = plan[:attr_rows]
|
|
205
|
+
attr_rows.each do |rule, attr, _sp, delegate|
|
|
206
|
+
value = value_of(instance, attr, delegate)
|
|
207
|
+
next if value.nil?
|
|
208
|
+
|
|
209
|
+
name = rule.name.to_s
|
|
210
|
+
row_index = row_indices[name]
|
|
211
|
+
next unless row_index
|
|
212
|
+
|
|
213
|
+
op_slot(buf, len)
|
|
214
|
+
buf[len][:kind] = :attr
|
|
215
|
+
buf[len][:plan_index] = plan_index
|
|
216
|
+
buf[len][:row_index] = row_index
|
|
217
|
+
buf[len][:name] = name
|
|
218
|
+
buf[len][:value] = attr.serialize(value, :xml, register).to_s
|
|
219
|
+
len += 1
|
|
147
220
|
end
|
|
221
|
+
len
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def emit_attributes_recorded(buf, len, instance, plan, plan_index,
|
|
225
|
+
register, recorded)
|
|
226
|
+
attr_rows = plan[:attr_rows]
|
|
227
|
+
by_name = {}
|
|
228
|
+
attr_rows.each { |rule, attr, _sp, delegate| by_name[rule.name.to_s] = [rule, attr, delegate] }
|
|
229
|
+
names = recorded | attr_rows.map { |rule, _a, _s, _d| rule.name.to_s }
|
|
148
230
|
tree_attrs = plan[:tree][:attributes] || []
|
|
231
|
+
row_indices = attr_row_indices_for(plan, tree_attrs)
|
|
149
232
|
names.each do |name|
|
|
150
233
|
entry = by_name[name]
|
|
151
234
|
next unless entry
|
|
@@ -154,18 +237,40 @@ module Lutaml
|
|
|
154
237
|
value = value_of(instance, attr, delegate)
|
|
155
238
|
next if value.nil?
|
|
156
239
|
|
|
157
|
-
row_index =
|
|
240
|
+
row_index = row_indices[name]
|
|
158
241
|
next unless row_index
|
|
159
242
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
243
|
+
op_slot(buf, len)
|
|
244
|
+
buf[len][:kind] = :attr
|
|
245
|
+
buf[len][:plan_index] = plan_index
|
|
246
|
+
buf[len][:row_index] = row_index
|
|
247
|
+
buf[len][:name] = name
|
|
248
|
+
buf[len][:value] = attr.serialize(value, :xml, register).to_s
|
|
249
|
+
len += 1
|
|
250
|
+
end
|
|
251
|
+
len
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# name -> tree_attrs index, first occurrence wins (same match
|
|
255
|
+
# semantics as the Array#index scan it replaced).
|
|
256
|
+
def attr_row_indices_for(plan, tree_attrs)
|
|
257
|
+
return plan[:attr_row_indices] if plan[:attr_row_indices]
|
|
258
|
+
|
|
259
|
+
indices = {}
|
|
260
|
+
tree_attrs.each_with_index do |tree_attr, i|
|
|
261
|
+
name = tree_attr[:name]
|
|
262
|
+
indices[name] = i unless indices.key?(name)
|
|
163
263
|
end
|
|
264
|
+
plan[:attr_row_indices] = indices
|
|
164
265
|
end
|
|
165
266
|
|
|
166
|
-
def scalar_op(plan_index, row_index, attr, value, register)
|
|
167
|
-
|
|
168
|
-
|
|
267
|
+
def scalar_op(buf, len, plan_index, row_index, attr, value, register)
|
|
268
|
+
op_slot(buf, len)
|
|
269
|
+
buf[len][:kind] = :scalar
|
|
270
|
+
buf[len][:plan_index] = plan_index
|
|
271
|
+
buf[len][:row_index] = row_index
|
|
272
|
+
buf[len][:value] = attr.serialize(value, :xml, register).to_s
|
|
273
|
+
len + 1
|
|
169
274
|
end
|
|
170
275
|
|
|
171
276
|
def value_of(instance, attr, delegate)
|
|
@@ -177,8 +282,11 @@ module Lutaml
|
|
|
177
282
|
end
|
|
178
283
|
end
|
|
179
284
|
|
|
180
|
-
|
|
181
|
-
|
|
285
|
+
# Compiled child plans per nested row, keyed by row index and
|
|
286
|
+
# then by member class. Keying through Integers and classes
|
|
287
|
+
# avoids allocating a [class, name] pair per emitted child.
|
|
288
|
+
def child_plans_for(plan, row_index)
|
|
289
|
+
(plan[:nested_plans_by_row] ||= {})[row_index] ||= {}
|
|
182
290
|
end
|
|
183
291
|
end
|
|
184
292
|
end
|
data/lib/lutaml/xml/mapping.rb
CHANGED
|
@@ -511,9 +511,11 @@ module Lutaml
|
|
|
511
511
|
end
|
|
512
512
|
|
|
513
513
|
# #1031: wildcard attribute families. After the explicit attribute
|
|
514
|
-
# rules have consumed their names, every remaining
|
|
515
|
-
#
|
|
516
|
-
#
|
|
514
|
+
# rules have consumed their names, every remaining attribute
|
|
515
|
+
# matching the rule's pattern lands in the target Hash — names
|
|
516
|
+
# claimed by explicit rules never leak into it. The key is the name
|
|
517
|
+
# as written, prefix included: keyed by its local name, xlink:href
|
|
518
|
+
# would serialize back as a plain href attribute.
|
|
517
519
|
def apply_all_attributes_deserialization(doc, instance, xml_mapping)
|
|
518
520
|
rule = xml_mapping.all_attributes_rule
|
|
519
521
|
return if rule.nil?
|
|
@@ -524,8 +526,9 @@ module Lutaml
|
|
|
524
526
|
claimed = attribute_names_claimed_by_rules(xml_mapping)
|
|
525
527
|
remainder = {}
|
|
526
528
|
element.attributes.each_value do |attr|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
+
next if claimed.include?(attr.unprefixed_name.to_s)
|
|
530
|
+
|
|
531
|
+
name = attr.name.to_s
|
|
529
532
|
next if rule.matching && !rule.matching.match?(name)
|
|
530
533
|
|
|
531
534
|
remainder[name] = attr.value
|
|
@@ -86,4 +86,35 @@ RSpec.describe "map_all_attributes" do
|
|
|
86
86
|
|
|
87
87
|
expect(model.other_attributes).to eq("zeta" => "1")
|
|
88
88
|
end
|
|
89
|
+
|
|
90
|
+
it "keeps a prefixed attribute's prefix through a round-trip" do
|
|
91
|
+
xml = '<mn xmlns:xlink="http://www.w3.org/1999/xlink" name="x" ' \
|
|
92
|
+
'data-title="T" xlink:href="#t"/>'
|
|
93
|
+
klass = Class.new(Lutaml::Model::Serializable) do
|
|
94
|
+
attribute :name, :string
|
|
95
|
+
attribute :other_attributes, :hash
|
|
96
|
+
|
|
97
|
+
xml do
|
|
98
|
+
element "mn"
|
|
99
|
+
map_attribute "name", to: :name
|
|
100
|
+
map_all_attributes to: :other_attributes
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
stub_const("MapAllPrefixedModel", klass)
|
|
104
|
+
|
|
105
|
+
model = klass.from_xml(xml)
|
|
106
|
+
|
|
107
|
+
expect(model.other_attributes).to eq("data-title" => "T", "xlink:href" => "#t")
|
|
108
|
+
expect(model.to_xml).to include('xlink:href="#t"')
|
|
109
|
+
expect(model.to_xml).not_to include(' href="#t"')
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
it "is inherited by a subclass along with the other mappings" do
|
|
113
|
+
stub_const("MapAllAttributesChild", Class.new(stubbed))
|
|
114
|
+
|
|
115
|
+
model = MapAllAttributesChild.from_xml('<mn name="x" data-title="T"/>')
|
|
116
|
+
|
|
117
|
+
expect(model.data_attributes).to eq("data-title" => "T")
|
|
118
|
+
expect(model.to_xml).to include('data-title="T"')
|
|
119
|
+
end
|
|
89
120
|
end
|
|
@@ -819,4 +819,65 @@ RSpec.describe "XML plan fast path" do
|
|
|
819
819
|
)
|
|
820
820
|
end
|
|
821
821
|
end
|
|
822
|
+
|
|
823
|
+
describe "native emission op buffer" do
|
|
824
|
+
around do |example|
|
|
825
|
+
Lutaml::Model::Config.with_adapter(xml: :leptris) { example.run }
|
|
826
|
+
end
|
|
827
|
+
|
|
828
|
+
it "keeps a re-entrant to_xml from a getter from corrupting the outer emission" do
|
|
829
|
+
inner = Class.new(Lutaml::Model::Serializable) do
|
|
830
|
+
attribute :v, :string
|
|
831
|
+
|
|
832
|
+
xml do
|
|
833
|
+
element "inner"
|
|
834
|
+
map_attribute "v", to: :v
|
|
835
|
+
end
|
|
836
|
+
end
|
|
837
|
+
outer = Class.new(Lutaml::Model::Serializable) do
|
|
838
|
+
attribute :v, :string
|
|
839
|
+
attribute :items, inner, collection: true
|
|
840
|
+
|
|
841
|
+
xml do
|
|
842
|
+
element "outer"
|
|
843
|
+
map_attribute "v", to: :v
|
|
844
|
+
map_element "inner", to: :items
|
|
845
|
+
end
|
|
846
|
+
|
|
847
|
+
define_method(:items) do
|
|
848
|
+
inner.new(v: "reentrant").to_xml
|
|
849
|
+
@items
|
|
850
|
+
end
|
|
851
|
+
end
|
|
852
|
+
|
|
853
|
+
model = outer.new(v: "x", items: [inner.new(v: "1"), inner.new(v: "2")])
|
|
854
|
+
expected = "<outer v=\"x\">\n <inner v=\"1\"/>\n <inner v=\"2\"/>\n</outer>"
|
|
855
|
+
expect(model.to_xml).to eq(expected)
|
|
856
|
+
expect(model.to_xml).to eq(expected)
|
|
857
|
+
end
|
|
858
|
+
|
|
859
|
+
it "keeps interleaved emissions of distinct models stable" do
|
|
860
|
+
alpha = Class.new(Lutaml::Model::Serializable) do
|
|
861
|
+
attribute :a, :string
|
|
862
|
+
|
|
863
|
+
xml do
|
|
864
|
+
element "alpha"
|
|
865
|
+
map_attribute "a", to: :a
|
|
866
|
+
end
|
|
867
|
+
end
|
|
868
|
+
beta = Class.new(Lutaml::Model::Serializable) do
|
|
869
|
+
attribute :b, :integer
|
|
870
|
+
|
|
871
|
+
xml do
|
|
872
|
+
element "beta"
|
|
873
|
+
map_attribute "b", to: :b
|
|
874
|
+
end
|
|
875
|
+
end
|
|
876
|
+
|
|
877
|
+
5.times do
|
|
878
|
+
expect(alpha.new(a: "x").to_xml).to eq("<alpha a=\"x\"/>")
|
|
879
|
+
expect(beta.new(b: 7).to_xml).to eq("<beta b=\"7\"/>")
|
|
880
|
+
end
|
|
881
|
+
end
|
|
882
|
+
end
|
|
822
883
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lutaml-model
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.8.
|
|
4
|
+
version: 0.8.140
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-10-
|
|
11
|
+
date: 2026-10-28 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: base64
|