flexor 0.1.0
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/.rspec +3 -0
- data/.rubocop.yml +394 -0
- data/.ruby-version +1 -0
- data/LICENSE.txt +21 -0
- data/README.md +218 -0
- data/Rakefile +8 -0
- data/benchmark/compare.rb +173 -0
- data/benchmark/results-with-caching.txt +175 -0
- data/docs/benchmark-results.md +64 -0
- data/docs/original_specification.yaml +426 -0
- data/docs/specification.yaml +453 -0
- data/lib/flexor/hash_delegation.rb +30 -0
- data/lib/flexor/serialization.rb +32 -0
- data/lib/flexor/version.rb +3 -0
- data/lib/flexor/vivification.rb +47 -0
- data/lib/flexor.rb +187 -0
- data/rakelib/benchmark.rake +4 -0
- data/rakelib/rdoc.rake +8 -0
- data/rakelib/version.rake +72 -0
- metadata +64 -0
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
Flexor.new:
|
|
2
|
+
with no arguments:
|
|
3
|
+
creates an empty store
|
|
4
|
+
is a root Flexor
|
|
5
|
+
with an empty hash:
|
|
6
|
+
creates an empty store
|
|
7
|
+
with a flat hash:
|
|
8
|
+
stores all key-value pairs
|
|
9
|
+
allows method access on each key
|
|
10
|
+
allows bracket access on each key
|
|
11
|
+
with a nested hash:
|
|
12
|
+
recursively converts nested hashes into Flexors
|
|
13
|
+
allows method access at every level
|
|
14
|
+
allows bracket access at every level
|
|
15
|
+
with a deeply nested hash (3+ levels):
|
|
16
|
+
allows method access at every level
|
|
17
|
+
allows bracket access at every level
|
|
18
|
+
with a hash containing arrays:
|
|
19
|
+
preserves arrays of scalars
|
|
20
|
+
converts hashes inside arrays into Flexors
|
|
21
|
+
preserves non-hash elements in mixed arrays
|
|
22
|
+
with a hash containing nil values:
|
|
23
|
+
stores the nil value directly
|
|
24
|
+
with a non-hash argument:
|
|
25
|
+
raises ArgumentError
|
|
26
|
+
when comparing root vs non-root (via .new):
|
|
27
|
+
defaults to root: true
|
|
28
|
+
can be set to root: false
|
|
29
|
+
|
|
30
|
+
Flexor.[]:
|
|
31
|
+
with a Hash:
|
|
32
|
+
creates a Flexor with the given data
|
|
33
|
+
with a JSON string:
|
|
34
|
+
parses JSON and creates a Flexor
|
|
35
|
+
with no arguments:
|
|
36
|
+
creates an empty Flexor
|
|
37
|
+
with a non-Hash, non-String argument:
|
|
38
|
+
raises ArgumentError
|
|
39
|
+
|
|
40
|
+
Flexor.from_json:
|
|
41
|
+
with valid flat JSON:
|
|
42
|
+
creates a Flexor with symbolized keys
|
|
43
|
+
allows method access on parsed values
|
|
44
|
+
with nested JSON:
|
|
45
|
+
recursively converts nested objects
|
|
46
|
+
allows method chaining on nested values
|
|
47
|
+
with JSON containing arrays:
|
|
48
|
+
preserves arrays
|
|
49
|
+
converts objects inside arrays into Flexors
|
|
50
|
+
with invalid JSON:
|
|
51
|
+
raises a parse error
|
|
52
|
+
|
|
53
|
+
reading a level 1 property via method:
|
|
54
|
+
when the property exists:
|
|
55
|
+
reads the correct property
|
|
56
|
+
when the property does not exist:
|
|
57
|
+
returns a value where nil? is true
|
|
58
|
+
returns a value that == nil
|
|
59
|
+
does not return the nil singleton
|
|
60
|
+
returns a Flexor
|
|
61
|
+
|
|
62
|
+
reading a level 1 property via hash accessor:
|
|
63
|
+
when the property exists:
|
|
64
|
+
reads the correct property
|
|
65
|
+
when the property does not exist:
|
|
66
|
+
returns a value where nil? is true
|
|
67
|
+
returns a value that == nil
|
|
68
|
+
does not return the nil singleton
|
|
69
|
+
returns a Flexor
|
|
70
|
+
|
|
71
|
+
reading a level 2 property via method:
|
|
72
|
+
when the level 1 property exists:
|
|
73
|
+
when the level 2 property exists:
|
|
74
|
+
reads the correct property
|
|
75
|
+
when the level 2 property does not exist:
|
|
76
|
+
returns a value where nil? is true
|
|
77
|
+
when the level 1 property does not exist:
|
|
78
|
+
returns a value where nil? is true
|
|
79
|
+
the returned value is itself a Flexor that supports further chaining
|
|
80
|
+
|
|
81
|
+
reading a level 2 property via hash accessor:
|
|
82
|
+
when the level 1 property exists:
|
|
83
|
+
when the level 2 property exists:
|
|
84
|
+
reads the correct property
|
|
85
|
+
when the level 2 property does not exist:
|
|
86
|
+
returns a value where nil? is true
|
|
87
|
+
when the level 1 property does not exist:
|
|
88
|
+
returns a value where nil? is true
|
|
89
|
+
the returned value is itself a Flexor that supports further chaining
|
|
90
|
+
|
|
91
|
+
reading at arbitrary depth (3+ levels):
|
|
92
|
+
when all levels are set:
|
|
93
|
+
reads the correct property
|
|
94
|
+
when no levels are set:
|
|
95
|
+
returns a value where nil? is true at every intermediate level
|
|
96
|
+
supports chaining to any depth without error
|
|
97
|
+
|
|
98
|
+
method vs bracket access equivalence:
|
|
99
|
+
writing via method and reading via bracket returns the written value
|
|
100
|
+
writing via bracket and reading via method returns the written value
|
|
101
|
+
when reading the same key via method and bracket:
|
|
102
|
+
for a set property
|
|
103
|
+
for an unset property
|
|
104
|
+
|
|
105
|
+
writing a level 1 property via method:
|
|
106
|
+
writes and reads the written property
|
|
107
|
+
|
|
108
|
+
writing a level 1 property via hash accessor:
|
|
109
|
+
writes and reads the written property
|
|
110
|
+
|
|
111
|
+
writing a level 2 property via method:
|
|
112
|
+
when the level 1 property has been set:
|
|
113
|
+
writes and reads the written property
|
|
114
|
+
when the level 1 property has not been set:
|
|
115
|
+
vivifies the level 1 property
|
|
116
|
+
writes and reads the written property
|
|
117
|
+
|
|
118
|
+
writing a level 2 property via hash accessor:
|
|
119
|
+
when the level 1 property has been set:
|
|
120
|
+
writes and reads the written property
|
|
121
|
+
when the level 1 property has not been set:
|
|
122
|
+
vivifies the level 1 property
|
|
123
|
+
writes and reads the written property
|
|
124
|
+
|
|
125
|
+
writing at arbitrary depth (3+ levels):
|
|
126
|
+
when no intermediate levels are set:
|
|
127
|
+
vivifies every intermediate level
|
|
128
|
+
writes and reads the written property
|
|
129
|
+
|
|
130
|
+
assigning a plain hash via setter:
|
|
131
|
+
when assigning a hash:
|
|
132
|
+
bracket access returns a Flexor, not a Hash
|
|
133
|
+
method chaining works on the assigned value
|
|
134
|
+
|
|
135
|
+
assigning an array via setter:
|
|
136
|
+
with an array of scalars:
|
|
137
|
+
stores and retrieves the array
|
|
138
|
+
with an array of hashes:
|
|
139
|
+
auto-converts inner hashes to Flexors
|
|
140
|
+
|
|
141
|
+
overwriting:
|
|
142
|
+
replacing a nested subtree with a scalar stores the scalar
|
|
143
|
+
replacing a nested subtree with a scalar removes the old subtree
|
|
144
|
+
replacing a scalar with a nested write vivifies the new path
|
|
145
|
+
replacing a scalar with nil makes the property read as nil
|
|
146
|
+
|
|
147
|
+
Flexor#set_raw:
|
|
148
|
+
stores a hash without vivification
|
|
149
|
+
stores an array of hashes without vivification
|
|
150
|
+
stores scalars normally
|
|
151
|
+
|
|
152
|
+
Flexor#merge:
|
|
153
|
+
returns a new Flexor with the merged contents
|
|
154
|
+
does not modify the original
|
|
155
|
+
overwrites scalar values
|
|
156
|
+
deep merges nested hashes
|
|
157
|
+
deep merges multiple levels
|
|
158
|
+
replaces a nested subtree with a scalar when incoming is scalar
|
|
159
|
+
replaces a scalar with a nested hash
|
|
160
|
+
accepts a Flexor as argument
|
|
161
|
+
vivifies hashes in the merged result
|
|
162
|
+
|
|
163
|
+
Flexor#merge!:
|
|
164
|
+
modifies the receiver in place
|
|
165
|
+
returns self
|
|
166
|
+
deep merges nested hashes in place
|
|
167
|
+
accepts a Flexor as argument
|
|
168
|
+
|
|
169
|
+
Flexor#delete:
|
|
170
|
+
removes the key and returns the value
|
|
171
|
+
returns nil for a missing key
|
|
172
|
+
removes nested Flexors
|
|
173
|
+
does not affect other keys
|
|
174
|
+
|
|
175
|
+
Flexor#clear:
|
|
176
|
+
removes all keys
|
|
177
|
+
returns self
|
|
178
|
+
makes the store nil-like
|
|
179
|
+
allows new data after clearing
|
|
180
|
+
|
|
181
|
+
Flexor#to_json:
|
|
182
|
+
serializes scalar values
|
|
183
|
+
serializes nested Flexors
|
|
184
|
+
serializes arrays
|
|
185
|
+
round-trips with from_json
|
|
186
|
+
serializes an empty Flexor as an empty object
|
|
187
|
+
raises when a value is not JSON-serializable (pending)
|
|
188
|
+
|
|
189
|
+
Flexor#nil?:
|
|
190
|
+
is truthy on an unset property (empty Flexor)
|
|
191
|
+
is falsey on a property with a value
|
|
192
|
+
is truthy on a property set explicitly to nil (via NilClass#nil?)
|
|
193
|
+
is truthy on a root Flexor with no data
|
|
194
|
+
is falsey on a root Flexor with data
|
|
195
|
+
|
|
196
|
+
Flexor#==:
|
|
197
|
+
when comparing an unset property (empty Flexor):
|
|
198
|
+
against nil is truthy
|
|
199
|
+
against a non-nil value is falsey
|
|
200
|
+
when comparing two Flexors:
|
|
201
|
+
is truthy when both have identical contents
|
|
202
|
+
is falsey when contents differ
|
|
203
|
+
is truthy when both are empty
|
|
204
|
+
when comparing a Flexor against a Hash:
|
|
205
|
+
is truthy when keys and values are identical
|
|
206
|
+
is falsey when keys and values are NOT identical
|
|
207
|
+
when comparing a property set explicitly to nil:
|
|
208
|
+
against nil is truthy (via NilClass#==)
|
|
209
|
+
against a non-nil value is falsey (via NilClass#==)
|
|
210
|
+
when comparing a scalar value:
|
|
211
|
+
against an identical scalar is truthy (via String#==)
|
|
212
|
+
against a different scalar is falsey (via String#==)
|
|
213
|
+
when checking symmetry:
|
|
214
|
+
documents whether nil == empty Flexor is symmetric
|
|
215
|
+
|
|
216
|
+
Flexor#===:
|
|
217
|
+
when used in a case/when on a Flexor value:
|
|
218
|
+
matches against a scalar when values are equal
|
|
219
|
+
matches against nil when property is unset
|
|
220
|
+
|
|
221
|
+
Flexor.===:
|
|
222
|
+
when used as a class in case/when:
|
|
223
|
+
matches a Flexor instance
|
|
224
|
+
does not match a plain Hash
|
|
225
|
+
does not match nil
|
|
226
|
+
|
|
227
|
+
Flexor#to_s:
|
|
228
|
+
when the property has been set:
|
|
229
|
+
returns the string representation for a scalar value
|
|
230
|
+
returns the string representation for a nested value
|
|
231
|
+
when the property has NOT been set:
|
|
232
|
+
returns an empty string to match nil.to_s behavior
|
|
233
|
+
returns an empty string, not actual nil
|
|
234
|
+
is indistinguishable from nil in string interpolation
|
|
235
|
+
is indistinguishable from nil when passed to puts
|
|
236
|
+
with multiple levels of depth:
|
|
237
|
+
returns empty string at every unset level
|
|
238
|
+
is indistinguishable from nil in puts at any depth
|
|
239
|
+
when comparing root vs non-root:
|
|
240
|
+
root with data returns the store's string representation
|
|
241
|
+
root without data returns empty string
|
|
242
|
+
non-root with data returns the store's string representation
|
|
243
|
+
non-root without data returns empty string
|
|
244
|
+
|
|
245
|
+
Flexor#inspect:
|
|
246
|
+
on a root Flexor with values returns the hash inspection
|
|
247
|
+
on a root Flexor without values returns the empty hash inspection
|
|
248
|
+
on an unset property (empty non-root Flexor) is indistinguishable from inspecting nil
|
|
249
|
+
on a non-root Flexor with values returns the hash inspection
|
|
250
|
+
|
|
251
|
+
Flexor#to_ary:
|
|
252
|
+
returns nil to prevent puts from treating Flexor as an array
|
|
253
|
+
prevents splat expansion from treating Flexor as an array
|
|
254
|
+
|
|
255
|
+
Flexor#to_h:
|
|
256
|
+
returns a plain hash with scalar values
|
|
257
|
+
recursively converts nested Flexors back to hashes
|
|
258
|
+
preserves arrays of scalars
|
|
259
|
+
recursively converts Flexors inside arrays
|
|
260
|
+
converts empty nested Flexors to nil
|
|
261
|
+
with round-trip conversion:
|
|
262
|
+
flat hash survives new -> to_h unchanged
|
|
263
|
+
nested hash survives new -> to_h unchanged
|
|
264
|
+
deeply nested hash survives new -> to_h unchanged
|
|
265
|
+
hash with arrays survives new -> to_h unchanged
|
|
266
|
+
with autovivified but never written paths:
|
|
267
|
+
does not include phantom keys
|
|
268
|
+
|
|
269
|
+
Flexor#deconstruct_keys:
|
|
270
|
+
with a Flexor containing values:
|
|
271
|
+
supports pattern matching via case/in
|
|
272
|
+
extracts matching keys
|
|
273
|
+
returns only requested keys
|
|
274
|
+
with nested Flexors:
|
|
275
|
+
supports nested pattern matching
|
|
276
|
+
with no keys requested:
|
|
277
|
+
returns the entire store
|
|
278
|
+
with an empty Flexor:
|
|
279
|
+
returns an empty hash
|
|
280
|
+
|
|
281
|
+
Flexor#deconstruct:
|
|
282
|
+
returns the values of the store for array-style pattern matching
|
|
283
|
+
supports variable binding in array patterns
|
|
284
|
+
returns an empty array for an empty Flexor
|
|
285
|
+
preserves nested Flexors for recursive pattern matching
|
|
286
|
+
|
|
287
|
+
hash-like query methods:
|
|
288
|
+
"#empty?":
|
|
289
|
+
is truthy when the store has no data
|
|
290
|
+
is falsey when the store has data
|
|
291
|
+
does not autovivify a key named :empty?
|
|
292
|
+
"#keys":
|
|
293
|
+
returns the keys of the store
|
|
294
|
+
does not autovivify a key named :keys
|
|
295
|
+
"#values":
|
|
296
|
+
returns the values of the store
|
|
297
|
+
does not autovivify a key named :values
|
|
298
|
+
"#size / #length":
|
|
299
|
+
returns the number of keys in the store
|
|
300
|
+
does not autovivify a key named :size or :length
|
|
301
|
+
"#key? / #has_key?":
|
|
302
|
+
when the key exists:
|
|
303
|
+
is truthy
|
|
304
|
+
when the key does not exist:
|
|
305
|
+
is falsey
|
|
306
|
+
does not autovivify the queried key
|
|
307
|
+
"#has_key?":
|
|
308
|
+
works identically to key? for existing keys
|
|
309
|
+
works identically to key? for missing keys
|
|
310
|
+
does not autovivify the queried key
|
|
311
|
+
non-standard key types:
|
|
312
|
+
with numeric keys:
|
|
313
|
+
stores and retrieves values with integer keys
|
|
314
|
+
with boolean keys:
|
|
315
|
+
stores and retrieves values with boolean keys
|
|
316
|
+
with empty string keys:
|
|
317
|
+
stores and retrieves values with empty string keys
|
|
318
|
+
|
|
319
|
+
symbol vs string keys:
|
|
320
|
+
method access uses symbol keys (store.foo writes to and reads from :foo)
|
|
321
|
+
bracket access with a symbol reads and writes using the symbol key
|
|
322
|
+
bracket access with a string reads and writes using the string key
|
|
323
|
+
with cross-access:
|
|
324
|
+
store.foo writes a value readable via store[:foo]
|
|
325
|
+
store[:baz] writes a value readable via store.baz
|
|
326
|
+
store.foo and store["foo"] do NOT access the same value
|
|
327
|
+
|
|
328
|
+
method name collisions:
|
|
329
|
+
with methods defined on Object (class, freeze, hash, object_id, send, display):
|
|
330
|
+
are not intercepted by method_missing
|
|
331
|
+
values stored under those keys are accessible via bracket
|
|
332
|
+
with methods defined on Flexor (to_h, to_s, nil?, ==):
|
|
333
|
+
are not intercepted by method_missing
|
|
334
|
+
values stored under those keys are accessible via bracket
|
|
335
|
+
|
|
336
|
+
method_missing edge cases:
|
|
337
|
+
calling a method with arguments (store.foo(1)) raises NoMethodError
|
|
338
|
+
calling a method with a block raises NoMethodError
|
|
339
|
+
setter with too many arguments (store.foo = 1, 2) raises NoMethodError
|
|
340
|
+
|
|
341
|
+
error message clarity:
|
|
342
|
+
with constructor ArgumentError:
|
|
343
|
+
includes the actual class in the error message
|
|
344
|
+
with NoMethodError from cached getter:
|
|
345
|
+
includes the method name after caching
|
|
346
|
+
with NoMethodError from method_missing:
|
|
347
|
+
includes the method name before caching
|
|
348
|
+
|
|
349
|
+
"#respond_to?":
|
|
350
|
+
is truthy for any arbitrary method name
|
|
351
|
+
is truthy for method names that also exist on Object
|
|
352
|
+
is truthy with include_private: true
|
|
353
|
+
|
|
354
|
+
"#respond_to_missing?":
|
|
355
|
+
returns true for any method name
|
|
356
|
+
|
|
357
|
+
array handling end-to-end:
|
|
358
|
+
when constructed with arrays:
|
|
359
|
+
hashes inside arrays are converted to Flexors
|
|
360
|
+
scalars inside arrays are preserved
|
|
361
|
+
nested arrays of hashes are converted recursively
|
|
362
|
+
when assigning directly:
|
|
363
|
+
assigning an array of hashes auto-converts inner hashes
|
|
364
|
+
when reading from arrays stored in Flexor:
|
|
365
|
+
array elements are accessible via standard array methods
|
|
366
|
+
|
|
367
|
+
autovivification side effects:
|
|
368
|
+
reading an unset property creates the key in the store (default_proc behavior)
|
|
369
|
+
the created key holds an empty Flexor
|
|
370
|
+
chaining reads on unset properties creates keys at every intermediate level
|
|
371
|
+
documents whether reads leave traces in to_h
|
|
372
|
+
|
|
373
|
+
thread safety:
|
|
374
|
+
concurrent reads on the same Flexor do not raise
|
|
375
|
+
concurrent writes to different keys documents expected behavior
|
|
376
|
+
concurrent autovivification documents expected behavior
|
|
377
|
+
|
|
378
|
+
dup and clone:
|
|
379
|
+
when duping a Flexor:
|
|
380
|
+
returns a new Flexor with the same contents
|
|
381
|
+
modifications to the dup do not affect the original
|
|
382
|
+
when cloning a Flexor:
|
|
383
|
+
returns a new Flexor with the same contents
|
|
384
|
+
modifications to the clone do not affect the original
|
|
385
|
+
with deep nesting:
|
|
386
|
+
dup is shallow (nested Flexors are shared)
|
|
387
|
+
|
|
388
|
+
freeze:
|
|
389
|
+
when freezing a Flexor:
|
|
390
|
+
prevents further writes
|
|
391
|
+
reads still work
|
|
392
|
+
autovivification raises on frozen store
|
|
393
|
+
merge! on a frozen Flexor raises FrozenError
|
|
394
|
+
delete on a frozen Flexor raises FrozenError
|
|
395
|
+
clear on a frozen Flexor raises FrozenError
|
|
396
|
+
set_raw on a frozen Flexor raises FrozenError
|
|
397
|
+
dup of a frozen Flexor returns an unfrozen copy
|
|
398
|
+
clone of a frozen Flexor returns a frozen copy
|
|
399
|
+
|
|
400
|
+
method caching:
|
|
401
|
+
defines a singleton method after first read
|
|
402
|
+
defines a singleton method after first write
|
|
403
|
+
cached getter returns the correct value
|
|
404
|
+
cached getter reflects updated values
|
|
405
|
+
cached setter writes correctly
|
|
406
|
+
cached getter on missing key returns nil-like Flexor
|
|
407
|
+
cached getter still raises NoMethodError with a block
|
|
408
|
+
cached getter still raises NoMethodError with arguments
|
|
409
|
+
does not cache methods that already exist on Flexor
|
|
410
|
+
caching does not leak between instances
|
|
411
|
+
works correctly with nested chaining
|
|
412
|
+
does not cache getter on first read of unset key (autovivification)
|
|
413
|
+
caches getter on second read of same key
|
|
414
|
+
caches getter immediately for constructor-populated keys
|
|
415
|
+
|
|
416
|
+
Flexor.from_json (edge cases):
|
|
417
|
+
with a JSON array root:
|
|
418
|
+
raises ArgumentError for an Array
|
|
419
|
+
with a JSON null root:
|
|
420
|
+
raises ArgumentError for nil
|
|
421
|
+
with a JSON scalar root:
|
|
422
|
+
raises ArgumentError for a String
|
|
423
|
+
|
|
424
|
+
Flexor#merge! (nil values):
|
|
425
|
+
with nil values:
|
|
426
|
+
nil overwrites an existing scalar
|
|
427
|
+
nil overwrites an existing nested Flexor
|
|
428
|
+
nil inside a nested hash is preserved during deep merge
|
|
429
|
+
|
|
430
|
+
Flexor#merge (nil values):
|
|
431
|
+
with nil values:
|
|
432
|
+
nil overwrites an existing scalar in the new Flexor
|
|
433
|
+
nil inside a nested hash is preserved during deep merge
|
|
434
|
+
|
|
435
|
+
Flexor#set_raw (edge cases):
|
|
436
|
+
with method access on a raw hash:
|
|
437
|
+
method access on a raw Hash value returns the Hash (not a Flexor)
|
|
438
|
+
with interaction with method cache:
|
|
439
|
+
returns the raw Hash on subsequent reads
|
|
440
|
+
|
|
441
|
+
serialization beyond JSON:
|
|
442
|
+
with Marshal:
|
|
443
|
+
round-trips via Marshal.dump and Marshal.load
|
|
444
|
+
preserves autovivification after Marshal round-trip
|
|
445
|
+
preserves nested Flexor structure after round-trip
|
|
446
|
+
preserves the root flag after round-trip
|
|
447
|
+
with YAML:
|
|
448
|
+
round-trips via YAML.dump and YAML.safe_load
|
|
449
|
+
preserves autovivification after YAML round-trip
|
|
450
|
+
preserves nested Flexor structure after round-trip
|
|
451
|
+
|
|
452
|
+
version:
|
|
453
|
+
has a version number
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
class Flexor
|
|
2
|
+
##
|
|
3
|
+
# Methods that delegate directly to the underlying Hash store.
|
|
4
|
+
module HashDelegation
|
|
5
|
+
def empty?
|
|
6
|
+
@store.empty?
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def keys
|
|
10
|
+
@store.keys
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def values
|
|
14
|
+
@store.values
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def size
|
|
18
|
+
@store.size
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def length
|
|
22
|
+
@store.length
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def key?(key)
|
|
26
|
+
@store.key?(key)
|
|
27
|
+
end
|
|
28
|
+
alias has_key? key?
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
class Flexor
|
|
2
|
+
##
|
|
3
|
+
# Methods for Marshal and YAML round-trip serialization.
|
|
4
|
+
module Serialization
|
|
5
|
+
def marshal_dump
|
|
6
|
+
{ store: to_h, root: @root }
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def marshal_load(data)
|
|
10
|
+
@root = data[:root]
|
|
11
|
+
@store = vivify(data[:store])
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def encode_with(coder)
|
|
15
|
+
coder["store"] = to_h
|
|
16
|
+
coder["root"] = @root
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def init_with(coder)
|
|
20
|
+
@root = coder["root"]
|
|
21
|
+
@store = vivify(symbolize_keys(coder["store"] || {}))
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def symbolize_keys(hash)
|
|
27
|
+
hash.transform_keys(&:to_sym).transform_values do |v|
|
|
28
|
+
v.is_a?(Hash) ? symbolize_keys(v) : v
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
class Flexor
|
|
2
|
+
##
|
|
3
|
+
# Methods for recursively converting raw Hashes and Arrays into Flexor objects.
|
|
4
|
+
module Vivification
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
def vivify(hash)
|
|
8
|
+
new_hash = Hash.new do |h, key|
|
|
9
|
+
h[key] = self.class.new({}, root: false)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
hash.each do |key, value|
|
|
13
|
+
new_hash[key] = vivify_value(value)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
new_hash
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def vivify_value(value)
|
|
20
|
+
case value
|
|
21
|
+
when Hash then self.class.new(value, root: false)
|
|
22
|
+
when Array then vivify_array(value)
|
|
23
|
+
else value
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def vivify_array(array)
|
|
28
|
+
array.map do |item|
|
|
29
|
+
case item
|
|
30
|
+
when Hash then self.class.new(item, root: false)
|
|
31
|
+
when Array then vivify_array(item)
|
|
32
|
+
else item
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def recurse_to_h(object)
|
|
38
|
+
case object
|
|
39
|
+
in Array then object.map { recurse_to_h(it) }
|
|
40
|
+
in ^(self.class)
|
|
41
|
+
converted = object.to_h
|
|
42
|
+
converted.empty? ? nil : converted
|
|
43
|
+
else object
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|