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,426 @@
|
|
|
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
|
+
root vs non-root:
|
|
27
|
+
defaults to root: true:
|
|
28
|
+
is a root Flexor
|
|
29
|
+
when root: false:
|
|
30
|
+
is a non-root Flexor
|
|
31
|
+
|
|
32
|
+
Flexor.from_json:
|
|
33
|
+
with valid flat JSON:
|
|
34
|
+
creates a Flexor with symbolized keys
|
|
35
|
+
allows method access on parsed values
|
|
36
|
+
with nested JSON:
|
|
37
|
+
recursively converts nested objects
|
|
38
|
+
allows method chaining on nested values
|
|
39
|
+
with JSON containing arrays:
|
|
40
|
+
preserves arrays
|
|
41
|
+
converts objects inside arrays into Flexors
|
|
42
|
+
with invalid JSON:
|
|
43
|
+
raises a parse error
|
|
44
|
+
|
|
45
|
+
reading a level 1 property via method:
|
|
46
|
+
the property does exist:
|
|
47
|
+
reads the correct property
|
|
48
|
+
the property does NOT exist:
|
|
49
|
+
returns a value where nil? is true
|
|
50
|
+
returns a value that == nil
|
|
51
|
+
does not return the nil singleton
|
|
52
|
+
returns a Flexor
|
|
53
|
+
|
|
54
|
+
reading a level 1 property via hash accessor:
|
|
55
|
+
the property does exist:
|
|
56
|
+
reads the correct property
|
|
57
|
+
the property does NOT exist:
|
|
58
|
+
returns a value where nil? is true
|
|
59
|
+
returns a value that == nil
|
|
60
|
+
does not return the nil singleton
|
|
61
|
+
returns a Flexor
|
|
62
|
+
|
|
63
|
+
reading a level 2 property via method:
|
|
64
|
+
the level 1 property does exist:
|
|
65
|
+
the level 2 property does exist:
|
|
66
|
+
reads the correct property
|
|
67
|
+
the level 2 property does NOT exist:
|
|
68
|
+
returns a value where nil? is true
|
|
69
|
+
the level 1 property does NOT exist:
|
|
70
|
+
returns a value where nil? is true
|
|
71
|
+
the returned value is itself a Flexor that supports further chaining
|
|
72
|
+
|
|
73
|
+
reading a level 2 property via hash accessor:
|
|
74
|
+
the level 1 property does exist:
|
|
75
|
+
the level 2 property does exist:
|
|
76
|
+
reads the correct property
|
|
77
|
+
the level 2 property does NOT exist:
|
|
78
|
+
returns a value where nil? is true
|
|
79
|
+
the level 1 property does NOT exist:
|
|
80
|
+
returns a value where nil? is true
|
|
81
|
+
the returned value is itself a Flexor that supports further chaining
|
|
82
|
+
|
|
83
|
+
reading at arbitrary depth (3+ levels):
|
|
84
|
+
all levels set:
|
|
85
|
+
reads the correct property
|
|
86
|
+
no levels set:
|
|
87
|
+
returns a value where nil? is true at every intermediate level
|
|
88
|
+
supports chaining to any depth without error
|
|
89
|
+
|
|
90
|
+
method vs bracket access equivalence:
|
|
91
|
+
reading the same key via method and bracket returns the same value:
|
|
92
|
+
for a set property
|
|
93
|
+
for an unset property
|
|
94
|
+
writing via method and reading via bracket returns the written value
|
|
95
|
+
writing via bracket and reading via method returns the written value
|
|
96
|
+
|
|
97
|
+
writing a level 1 property via method:
|
|
98
|
+
writes and reads the written property
|
|
99
|
+
|
|
100
|
+
writing a level 1 property via hash accessor:
|
|
101
|
+
writes and reads the written property
|
|
102
|
+
|
|
103
|
+
writing a level 2 property via method:
|
|
104
|
+
the level 1 property has been set:
|
|
105
|
+
writes and reads the written property
|
|
106
|
+
the level 1 property has NOT been set:
|
|
107
|
+
vivifies the level 1 property
|
|
108
|
+
writes and reads the written property
|
|
109
|
+
|
|
110
|
+
writing a level 2 property via hash accessor:
|
|
111
|
+
the level 1 property has been set:
|
|
112
|
+
writes and reads the written property
|
|
113
|
+
the level 1 property has NOT been set:
|
|
114
|
+
vivifies the level 1 property
|
|
115
|
+
writes and reads the written property
|
|
116
|
+
|
|
117
|
+
writing at arbitrary depth (3+ levels):
|
|
118
|
+
no intermediate levels set:
|
|
119
|
+
vivifies every intermediate level
|
|
120
|
+
writes and reads the written property
|
|
121
|
+
|
|
122
|
+
assigning a plain hash via setter:
|
|
123
|
+
auto-converts the hash to a Flexor:
|
|
124
|
+
bracket access returns a Flexor, not a Hash
|
|
125
|
+
method chaining works on the assigned value
|
|
126
|
+
|
|
127
|
+
assigning an array via setter:
|
|
128
|
+
with an array of scalars:
|
|
129
|
+
stores and retrieves the array
|
|
130
|
+
with an array of hashes:
|
|
131
|
+
auto-converts inner hashes to Flexors
|
|
132
|
+
|
|
133
|
+
Flexor#set_raw:
|
|
134
|
+
stores a hash without vivification
|
|
135
|
+
stores an array of hashes without vivification
|
|
136
|
+
stores scalars normally
|
|
137
|
+
|
|
138
|
+
Flexor#merge:
|
|
139
|
+
returns a new Flexor with the merged contents
|
|
140
|
+
does not modify the original
|
|
141
|
+
overwrites scalar values
|
|
142
|
+
deep merges nested hashes
|
|
143
|
+
deep merges multiple levels
|
|
144
|
+
replaces a nested subtree with a scalar when incoming is scalar
|
|
145
|
+
replaces a scalar with a nested hash
|
|
146
|
+
accepts a Flexor as argument
|
|
147
|
+
vivifies hashes in the merged result
|
|
148
|
+
|
|
149
|
+
Flexor#merge!:
|
|
150
|
+
modifies the receiver in place
|
|
151
|
+
returns self
|
|
152
|
+
deep merges nested hashes in place
|
|
153
|
+
accepts a Flexor as argument
|
|
154
|
+
|
|
155
|
+
Flexor#delete:
|
|
156
|
+
removes the key and returns the value
|
|
157
|
+
returns nil for a missing key
|
|
158
|
+
removes nested Flexors
|
|
159
|
+
does not affect other keys
|
|
160
|
+
|
|
161
|
+
Flexor#to_json:
|
|
162
|
+
serializes scalar values
|
|
163
|
+
serializes nested Flexors
|
|
164
|
+
serializes arrays
|
|
165
|
+
round-trips with from_json
|
|
166
|
+
serializes an empty Flexor as an empty object
|
|
167
|
+
|
|
168
|
+
overwriting:
|
|
169
|
+
replacing a nested subtree with a scalar:
|
|
170
|
+
the scalar is stored
|
|
171
|
+
the old subtree is gone
|
|
172
|
+
replacing a scalar with a nested write:
|
|
173
|
+
vivifies the new path
|
|
174
|
+
replacing a scalar with nil:
|
|
175
|
+
the property now reads as nil
|
|
176
|
+
|
|
177
|
+
Flexor#nil?:
|
|
178
|
+
on an unset property (empty Flexor):
|
|
179
|
+
is expected to be truthy
|
|
180
|
+
on a property with a value:
|
|
181
|
+
is expected to be falsey
|
|
182
|
+
on a property set explicitly to nil:
|
|
183
|
+
# This accesses the real nil singleton, not a Flexor.
|
|
184
|
+
# Documenting end-to-end behavior, not Flexor#nil?.
|
|
185
|
+
is expected to be truthy (via NilClass#nil?, not Flexor#nil?)
|
|
186
|
+
on a root Flexor with no data:
|
|
187
|
+
is expected to be truthy
|
|
188
|
+
on a root Flexor with data:
|
|
189
|
+
is expected to be falsey
|
|
190
|
+
|
|
191
|
+
Flexor#==:
|
|
192
|
+
when comparing an unset property (empty Flexor):
|
|
193
|
+
against nil:
|
|
194
|
+
is expected to be truthy
|
|
195
|
+
against a non-nil value:
|
|
196
|
+
is expected to be falsey
|
|
197
|
+
when comparing two Flexors:
|
|
198
|
+
when both have identical contents:
|
|
199
|
+
is expected to be truthy
|
|
200
|
+
when contents differ:
|
|
201
|
+
is expected to be falsey
|
|
202
|
+
when both are empty:
|
|
203
|
+
is expected to be truthy
|
|
204
|
+
when comparing a Flexor against a Hash:
|
|
205
|
+
when the keys and values are identical:
|
|
206
|
+
is expected to be truthy
|
|
207
|
+
when the keys and values are NOT identical:
|
|
208
|
+
is expected to be falsey
|
|
209
|
+
when comparing a property set explicitly to nil:
|
|
210
|
+
# This accesses the real nil singleton, not a Flexor.
|
|
211
|
+
# Documenting end-to-end behavior, not Flexor#==.
|
|
212
|
+
against nil:
|
|
213
|
+
is expected to be truthy (via NilClass#==, not Flexor#==)
|
|
214
|
+
against a non-nil value:
|
|
215
|
+
is expected to be falsey (via NilClass#==, not Flexor#==)
|
|
216
|
+
when comparing a scalar value:
|
|
217
|
+
# store.name = "alice"; store.name == "alice"
|
|
218
|
+
# This accesses the real String, not a Flexor.
|
|
219
|
+
# Documenting end-to-end behavior, not Flexor#==.
|
|
220
|
+
against an identical scalar:
|
|
221
|
+
is expected to be truthy (via String#==, not Flexor#==)
|
|
222
|
+
against a different scalar:
|
|
223
|
+
is expected to be falsey (via String#==, not Flexor#==)
|
|
224
|
+
symmetry:
|
|
225
|
+
nil == empty Flexor:
|
|
226
|
+
# NilClass#== does not know about Flexor, so this may be false
|
|
227
|
+
# even though empty_flexor == nil is true
|
|
228
|
+
documents whether equality is symmetric
|
|
229
|
+
|
|
230
|
+
Flexor#===:
|
|
231
|
+
when used in a case/when on a Flexor value:
|
|
232
|
+
matching against a scalar:
|
|
233
|
+
matches when values are equal
|
|
234
|
+
matching against nil:
|
|
235
|
+
matches when property is unset
|
|
236
|
+
|
|
237
|
+
Flexor.===:
|
|
238
|
+
when used as a class in case/when:
|
|
239
|
+
matches a Flexor instance
|
|
240
|
+
does not match a plain Hash
|
|
241
|
+
does not match nil
|
|
242
|
+
|
|
243
|
+
Flexor#to_s:
|
|
244
|
+
when the property has been set:
|
|
245
|
+
to a scalar value:
|
|
246
|
+
returns the string representation of the store
|
|
247
|
+
to a nested value:
|
|
248
|
+
returns the string representation of the store
|
|
249
|
+
when the property has NOT been set:
|
|
250
|
+
returns an empty string to match nil.to_s behavior
|
|
251
|
+
returns an empty string, not actual nil
|
|
252
|
+
is indistinguishable from nil in string interpolation
|
|
253
|
+
is indistinguishable from nil when passed to puts
|
|
254
|
+
at multiple levels of depth:
|
|
255
|
+
returns empty string at every unset level
|
|
256
|
+
is indistinguishable from nil in puts at any depth
|
|
257
|
+
root vs non-root:
|
|
258
|
+
root with data:
|
|
259
|
+
returns the store's string representation
|
|
260
|
+
root without data:
|
|
261
|
+
returns empty string
|
|
262
|
+
non-root with data:
|
|
263
|
+
returns the store's string representation
|
|
264
|
+
non-root without data:
|
|
265
|
+
returns empty string
|
|
266
|
+
|
|
267
|
+
Flexor#inspect:
|
|
268
|
+
on a root Flexor with values:
|
|
269
|
+
returns the hash inspection
|
|
270
|
+
on a root Flexor without values:
|
|
271
|
+
returns the empty hash inspection
|
|
272
|
+
on an unset property (empty non-root Flexor):
|
|
273
|
+
is indistinguishable from inspecting nil
|
|
274
|
+
on a non-root Flexor with values:
|
|
275
|
+
returns the hash inspection
|
|
276
|
+
|
|
277
|
+
Flexor#to_ary:
|
|
278
|
+
always returns nil:
|
|
279
|
+
prevents puts from treating Flexor as an array
|
|
280
|
+
prevents splat expansion from treating Flexor as an array
|
|
281
|
+
|
|
282
|
+
Flexor#to_h:
|
|
283
|
+
with scalar values:
|
|
284
|
+
returns a plain hash with scalar values
|
|
285
|
+
with nested Flexors:
|
|
286
|
+
recursively converts Flexors back to hashes
|
|
287
|
+
with arrays:
|
|
288
|
+
preserves arrays of scalars
|
|
289
|
+
recursively converts Flexors inside arrays
|
|
290
|
+
with empty nested Flexors:
|
|
291
|
+
converts empty Flexors to nil
|
|
292
|
+
round-trip:
|
|
293
|
+
flat hash survives new -> to_h unchanged
|
|
294
|
+
nested hash survives new -> to_h unchanged
|
|
295
|
+
deeply nested hash survives new -> to_h unchanged
|
|
296
|
+
hash with arrays survives new -> to_h unchanged
|
|
297
|
+
with autovivified but never written paths:
|
|
298
|
+
does not include phantom keys
|
|
299
|
+
|
|
300
|
+
Flexor#deconstruct_keys:
|
|
301
|
+
with a Flexor containing values:
|
|
302
|
+
supports pattern matching via case/in
|
|
303
|
+
extracts matching keys
|
|
304
|
+
returns only requested keys
|
|
305
|
+
with nested Flexors:
|
|
306
|
+
supports nested pattern matching
|
|
307
|
+
with no keys requested:
|
|
308
|
+
returns the entire store
|
|
309
|
+
on an empty Flexor:
|
|
310
|
+
returns an empty hash
|
|
311
|
+
|
|
312
|
+
Flexor#deconstruct:
|
|
313
|
+
returns the values of the store for array-style pattern matching
|
|
314
|
+
supports variable binding in array patterns
|
|
315
|
+
returns an empty array for an empty Flexor
|
|
316
|
+
preserves nested Flexors for recursive pattern matching
|
|
317
|
+
|
|
318
|
+
hash-like query methods:
|
|
319
|
+
empty?:
|
|
320
|
+
when the store has no data:
|
|
321
|
+
is expected to be truthy
|
|
322
|
+
when the store has data:
|
|
323
|
+
is expected to be falsey
|
|
324
|
+
does not autovivify a key named :empty?
|
|
325
|
+
keys:
|
|
326
|
+
returns the keys of the store
|
|
327
|
+
does not autovivify a key named :keys
|
|
328
|
+
values:
|
|
329
|
+
returns the values of the store
|
|
330
|
+
does not autovivify a key named :values
|
|
331
|
+
size / length:
|
|
332
|
+
returns the number of keys in the store
|
|
333
|
+
does not autovivify a key named :size or :length
|
|
334
|
+
key? / has_key?:
|
|
335
|
+
when the key exists:
|
|
336
|
+
is expected to be truthy
|
|
337
|
+
when the key does not exist:
|
|
338
|
+
is expected to be falsey
|
|
339
|
+
does not autovivify the queried key
|
|
340
|
+
|
|
341
|
+
symbol vs string keys:
|
|
342
|
+
method access uses symbol keys:
|
|
343
|
+
store.foo writes to and reads from :foo
|
|
344
|
+
bracket access with a symbol:
|
|
345
|
+
reads and writes using the symbol key
|
|
346
|
+
bracket access with a string:
|
|
347
|
+
reads and writes using the string key
|
|
348
|
+
cross-access:
|
|
349
|
+
store.foo and store[:foo] access the same value
|
|
350
|
+
store.foo and store["foo"] do NOT access the same value
|
|
351
|
+
|
|
352
|
+
method name collisions:
|
|
353
|
+
methods defined on Object (class, freeze, hash, object_id, send, display):
|
|
354
|
+
are not intercepted by method_missing
|
|
355
|
+
values stored under those keys are accessible via bracket
|
|
356
|
+
methods defined on Flexor (to_h, to_s, nil?, ==):
|
|
357
|
+
are not intercepted by method_missing
|
|
358
|
+
values stored under those keys are accessible via bracket
|
|
359
|
+
|
|
360
|
+
method_missing edge cases:
|
|
361
|
+
calling a method with arguments (store.foo(1)):
|
|
362
|
+
raises NoMethodError via super
|
|
363
|
+
calling a method with a block:
|
|
364
|
+
raises NoMethodError via super
|
|
365
|
+
setter with too many arguments (store.foo = 1, 2):
|
|
366
|
+
raises NoMethodError via super
|
|
367
|
+
|
|
368
|
+
respond_to?:
|
|
369
|
+
for any arbitrary method name:
|
|
370
|
+
is expected to be truthy
|
|
371
|
+
for method names that also exist on Object:
|
|
372
|
+
is expected to be truthy
|
|
373
|
+
with include_private: true:
|
|
374
|
+
is expected to be truthy
|
|
375
|
+
|
|
376
|
+
respond_to_missing?:
|
|
377
|
+
returns true for any method name:
|
|
378
|
+
is expected to be truthy
|
|
379
|
+
|
|
380
|
+
array handling end-to-end:
|
|
381
|
+
via constructor:
|
|
382
|
+
hashes inside arrays are converted to Flexors
|
|
383
|
+
scalars inside arrays are preserved
|
|
384
|
+
nested arrays of hashes are converted recursively
|
|
385
|
+
via direct assignment:
|
|
386
|
+
assigning an array of hashes auto-converts inner hashes
|
|
387
|
+
reading from arrays stored in Flexor:
|
|
388
|
+
array elements are accessible via standard array methods
|
|
389
|
+
|
|
390
|
+
autovivification side effects:
|
|
391
|
+
reading an unset property:
|
|
392
|
+
creates the key in the store (default_proc behavior)
|
|
393
|
+
the created key holds an empty Flexor
|
|
394
|
+
chaining reads on unset properties:
|
|
395
|
+
creates keys at every intermediate level
|
|
396
|
+
implications for to_h after read-only access:
|
|
397
|
+
# This ties back to the phantom keys exclusion above
|
|
398
|
+
reads do not leave traces in to_h
|
|
399
|
+
|
|
400
|
+
thread safety:
|
|
401
|
+
concurrent reads on the same Flexor:
|
|
402
|
+
do not raise
|
|
403
|
+
concurrent writes to different keys:
|
|
404
|
+
documents the expected behavior
|
|
405
|
+
concurrent autovivification:
|
|
406
|
+
documents the expected behavior
|
|
407
|
+
|
|
408
|
+
dup and clone:
|
|
409
|
+
duping a Flexor:
|
|
410
|
+
returns a new Flexor with the same contents
|
|
411
|
+
modifications to the dup do not affect the original
|
|
412
|
+
cloning a Flexor:
|
|
413
|
+
returns a new Flexor with the same contents
|
|
414
|
+
modifications to the clone do not affect the original
|
|
415
|
+
deep nesting:
|
|
416
|
+
dup is shallow (nested Flexors are shared)
|
|
417
|
+
|
|
418
|
+
freeze:
|
|
419
|
+
freezing a Flexor:
|
|
420
|
+
prevents further writes
|
|
421
|
+
reads still work
|
|
422
|
+
autovivification raises on frozen store
|
|
423
|
+
|
|
424
|
+
enumeration:
|
|
425
|
+
each / map / select:
|
|
426
|
+
delegates to the underlying store
|