nrser 0.0.24 → 0.0.25
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/nrser/enumerable.rb +12 -6
- data/lib/nrser/hash.rb +182 -0
- data/lib/nrser/meta/props/base.rb +22 -0
- data/lib/nrser/meta/props/prop.rb +84 -12
- data/lib/nrser/meta/props.rb +36 -15
- data/lib/nrser/refinements/hash.rb +13 -0
- data/lib/nrser/refinements/pathname.rb +11 -0
- data/lib/nrser/refinements.rb +2 -0
- data/lib/nrser/spex.rb +68 -0
- data/lib/nrser/types/attrs.rb +83 -30
- data/lib/nrser/types/combinators.rb +54 -6
- data/lib/nrser/types/paths.rb +62 -2
- data/lib/nrser/types/type.rb +122 -2
- data/lib/nrser/types.rb +2 -0
- data/lib/nrser/version.rb +1 -1
- data/lib/nrser.rb +5 -1
- data/spec/nrser/hash/bury_spec.rb +31 -0
- data/spec/nrser/hash/guess_name_type_spec.rb +47 -0
- data/spec/nrser/types/attrs_spec.rb +41 -0
- data/spec/nrser/types/paths_spec.rb +69 -0
- data/spec/nrser/types_spec.rb +0 -10
- data/spec/spec_helper.rb +46 -1
- metadata +12 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 150628e4ec282944b3bfa4d667e0c678cfe9b6e3
|
4
|
+
data.tar.gz: d31edbf013cde3c160d3105e0ac0770efae26e88
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d170a8aeba6ed6940aa53a0d92a8f674e6b0b0bcaed15b8808b43dcde4ae690665fbd7f4bad2bc7ae72b641e675dd3e67228d935f0e0ba18e30605385bec54da
|
7
|
+
data.tar.gz: c71d68a17bb981b5638f9c28521d093fbf69266aa94323e96969c38301ad4293ba65ca1857cc6efaacc938f46a4857faff8ddfa737b6a200cac11dc8914e49a9
|
data/lib/nrser/enumerable.rb
CHANGED
@@ -59,21 +59,29 @@ module NRSER
|
|
59
59
|
|
60
60
|
|
61
61
|
|
62
|
-
#
|
62
|
+
# Find all entries in an {Enumerable} for which `&block` returns a truthy
|
63
|
+
# value, then check the amount of results found against the
|
64
|
+
# {NRSER::Types.length} created from `bounds`, raising a {TypeError} if
|
65
|
+
# the results' length doesn't satisfy the bounds type.
|
63
66
|
#
|
64
|
-
# @param [
|
65
|
-
#
|
67
|
+
# @param [Enumerable] enum
|
68
|
+
# The entries to search and check.
|
69
|
+
#
|
70
|
+
# @param []
|
66
71
|
#
|
67
72
|
# @return [return_type]
|
68
73
|
# @todo Document return value.
|
69
74
|
#
|
75
|
+
# @raise
|
76
|
+
#
|
70
77
|
def find_bounded enum, bounds, &block
|
71
78
|
NRSER::Types.
|
72
79
|
length(bounds).
|
73
80
|
check(enum.find_all &block) { |type:, value:|
|
74
81
|
NRSER.dedent <<-END
|
75
82
|
|
76
|
-
Length of found elements (#{ value.length }) FAILED to
|
83
|
+
Length of found elements (#{ value.length }) FAILED to
|
84
|
+
satisfy #{ type.to_s }
|
77
85
|
|
78
86
|
Found:
|
79
87
|
#{ NRSER.indent value.pretty_inspect }
|
@@ -86,7 +94,6 @@ module NRSER
|
|
86
94
|
end # #find_bounded
|
87
95
|
|
88
96
|
|
89
|
-
|
90
97
|
# @todo Document find_only method.
|
91
98
|
#
|
92
99
|
# @param [type] arg_name
|
@@ -100,7 +107,6 @@ module NRSER
|
|
100
107
|
end # #find_only
|
101
108
|
|
102
109
|
|
103
|
-
|
104
110
|
# @todo Document to_h_by method.
|
105
111
|
#
|
106
112
|
# @param [type] arg_name
|
data/lib/nrser/hash.rb
CHANGED
@@ -291,4 +291,186 @@ module NRSER
|
|
291
291
|
}
|
292
292
|
end
|
293
293
|
|
294
|
+
|
295
|
+
# Eigenclass (Singleton Class)
|
296
|
+
# ========================================================================
|
297
|
+
#
|
298
|
+
class << self
|
299
|
+
|
300
|
+
|
301
|
+
|
302
|
+
# @todo Document select_map method.
|
303
|
+
#
|
304
|
+
# @param [type] arg_name
|
305
|
+
# @todo Add name param description.
|
306
|
+
#
|
307
|
+
# @return [return_type]
|
308
|
+
# @todo Document return value.
|
309
|
+
#
|
310
|
+
def select_map arg_name
|
311
|
+
# method body...
|
312
|
+
end # #select_map
|
313
|
+
|
314
|
+
|
315
|
+
# Guess which type of "name" key - strings or symbols - a hash (or other
|
316
|
+
# object that responds to `#keys` and `#empty`) uses.
|
317
|
+
#
|
318
|
+
# @param [#keys & #empty] keyed
|
319
|
+
# Hash or similar object that responds to `#keys` and `#empty` to guess
|
320
|
+
# about.
|
321
|
+
#
|
322
|
+
# @return [nil]
|
323
|
+
# If we can't determine the type of name keys used.
|
324
|
+
#
|
325
|
+
# @return [Class]
|
326
|
+
# If we can determine that {String} or {Symbol} keys are exclusively
|
327
|
+
# used returns that class.
|
328
|
+
#
|
329
|
+
def guess_name_type keyed
|
330
|
+
# We can't tell shit if the hash is empty
|
331
|
+
return nil if keyed.empty?
|
332
|
+
|
333
|
+
name_types = keyed.
|
334
|
+
keys.
|
335
|
+
map( &:class ).
|
336
|
+
select { |klass| klass == String || klass == Symbol }.
|
337
|
+
uniq
|
338
|
+
|
339
|
+
return name_types[0] if name_types.length == 1
|
340
|
+
|
341
|
+
# There are both string and symbol keys present, we can't guess
|
342
|
+
nil
|
343
|
+
end # #guess_key_type
|
344
|
+
|
345
|
+
|
346
|
+
# @todo Document bury method.
|
347
|
+
#
|
348
|
+
# @param [Hash] hash
|
349
|
+
# Hash to bury the value in.
|
350
|
+
#
|
351
|
+
# @param [Array | #to_s] key_path
|
352
|
+
# - When an {Array}, each entry is used exactly as-is for each key.
|
353
|
+
#
|
354
|
+
# - Otherwise, the `key_path` is converted to a string and split by
|
355
|
+
# `.` to produce the key array, and the actual keys used depend on
|
356
|
+
# the `parsed_key_type` option.
|
357
|
+
#
|
358
|
+
# @param [Object] value
|
359
|
+
# The value to set at the end of the path.
|
360
|
+
#
|
361
|
+
# @param [Class | :guess] parsed_key_type:
|
362
|
+
# How to handle parsed key path segments:
|
363
|
+
#
|
364
|
+
# - `String` - use the strings that naturally split from a parsed
|
365
|
+
# key path.
|
366
|
+
#
|
367
|
+
# Note that this is the *String class itself, **not** a value that
|
368
|
+
# is a String*.
|
369
|
+
#
|
370
|
+
# - `Symbol` - convert the strings that are split from the key path
|
371
|
+
# to symbols.
|
372
|
+
#
|
373
|
+
# Note that this is the *Symbol class itself, **not** a value that
|
374
|
+
# is a Symbol*.``
|
375
|
+
#
|
376
|
+
# - `:guess` (default) -
|
377
|
+
#
|
378
|
+
# @return [return_type]
|
379
|
+
# @todo Document return value.
|
380
|
+
#
|
381
|
+
def bury! hash,
|
382
|
+
key_path,
|
383
|
+
value,
|
384
|
+
parsed_key_type: :guess,
|
385
|
+
clobber: false
|
386
|
+
|
387
|
+
# Parse the key if it's not an array
|
388
|
+
unless key_path.is_a?( Array )
|
389
|
+
key_path = key_path.to_s.split '.'
|
390
|
+
|
391
|
+
# Convert the keys to symbols now if that's what we want to use
|
392
|
+
if parsed_key_type == Symbol
|
393
|
+
key_path.map! &:to_sym
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
397
|
+
_internal_bury! \
|
398
|
+
hash,
|
399
|
+
key_path,
|
400
|
+
value,
|
401
|
+
guess_key_type: ( parsed_key_type == :guess ),
|
402
|
+
clobber: clobber
|
403
|
+
end # #bury
|
404
|
+
|
405
|
+
|
406
|
+
|
407
|
+
private
|
408
|
+
# ========================================================================
|
409
|
+
|
410
|
+
|
411
|
+
# @todo Document _internal_bury! method.
|
412
|
+
#
|
413
|
+
# @param [type] arg_name
|
414
|
+
# @todo Add name param description.
|
415
|
+
#
|
416
|
+
# @return [return_type]
|
417
|
+
# @todo Document return value.
|
418
|
+
#
|
419
|
+
def _internal_bury! hash,
|
420
|
+
key_path,
|
421
|
+
value,
|
422
|
+
guess_key_type:,
|
423
|
+
clobber:
|
424
|
+
|
425
|
+
# Split the key path into the current key and the rest of the keys
|
426
|
+
key, *rest = key_path
|
427
|
+
|
428
|
+
# If we are guessing the key type and the hash uses some {Symbol}
|
429
|
+
# (and no {String}) keys then convert the key to a symbol.
|
430
|
+
if guess_key_type && guess_name_type( hash ) == Symbol
|
431
|
+
key = key.to_sym
|
432
|
+
end
|
433
|
+
|
434
|
+
# Terminating case: we're at the last segment
|
435
|
+
if rest.empty?
|
436
|
+
# Set the value
|
437
|
+
hash[key] = value
|
438
|
+
|
439
|
+
else
|
440
|
+
# Go deeper...
|
441
|
+
|
442
|
+
# See if there is a hash in place
|
443
|
+
unless hash[key].is_a?( Hash )
|
444
|
+
# There is not... so we need to do some figurin'
|
445
|
+
|
446
|
+
# If we're clobbering or the hash has no value, we're good:
|
447
|
+
# assign a new hash to set in
|
448
|
+
if clobber || ! hash.key?( key )
|
449
|
+
hash[key] = {}
|
450
|
+
|
451
|
+
else
|
452
|
+
# We've got an intractable state conflict; raise
|
453
|
+
raise NRSER::ConflictError.new squish <<-END
|
454
|
+
can not set key #{ key.inspect } due to conflicting value
|
455
|
+
#{ hash[key].inspect } in hash #{ hash.inspect } (:clobber
|
456
|
+
option not set)
|
457
|
+
END
|
458
|
+
|
459
|
+
end
|
460
|
+
end # unless hash[key].is_a?( Hash )
|
461
|
+
|
462
|
+
# Dive in...
|
463
|
+
bury! hash[key], rest, value
|
464
|
+
|
465
|
+
end # if rest.empty? / else
|
466
|
+
end # #_internal_bury!
|
467
|
+
|
468
|
+
|
469
|
+
# end private
|
470
|
+
|
471
|
+
|
472
|
+
|
473
|
+
end # class << self (Eigenclass)
|
474
|
+
|
475
|
+
|
294
476
|
end # module NRSER
|
@@ -1,3 +1,10 @@
|
|
1
|
+
# Refinements
|
2
|
+
# =======================================================================
|
3
|
+
|
4
|
+
require 'nrser/refinements'
|
5
|
+
using NRSER
|
6
|
+
|
7
|
+
|
1
8
|
module NRSER
|
2
9
|
module Meta
|
3
10
|
module Props
|
@@ -8,6 +15,21 @@ class Base
|
|
8
15
|
def initialize **values
|
9
16
|
initialize_props values
|
10
17
|
end
|
18
|
+
|
19
|
+
# @todo Prob wanna improve this at some point, but it's better than nothing.
|
20
|
+
#
|
21
|
+
# @return [String]
|
22
|
+
# a short string describing the instance.
|
23
|
+
#
|
24
|
+
def to_s
|
25
|
+
props_str = self.class.props( only_primary: true ).sort.map { |name, prop|
|
26
|
+
"#{ name }=#{ prop.get( self ).inspect }"
|
27
|
+
}.join ' '
|
28
|
+
|
29
|
+
<<-END.squish
|
30
|
+
#<#{ self.class.name } #{ props_str }>
|
31
|
+
END
|
32
|
+
end # #to_s
|
11
33
|
end # class Base
|
12
34
|
|
13
35
|
end # module Props
|
@@ -1,8 +1,9 @@
|
|
1
1
|
require 'nrser/no_arg'
|
2
2
|
|
3
3
|
require 'nrser/refinements'
|
4
|
-
|
4
|
+
using NRSER
|
5
5
|
|
6
|
+
require 'nrser/refinements/types'
|
6
7
|
using NRSER::Types
|
7
8
|
|
8
9
|
module NRSER
|
@@ -20,13 +21,15 @@ class Prop
|
|
20
21
|
name,
|
21
22
|
type: t.any,
|
22
23
|
default: NRSER::NO_ARG,
|
23
|
-
source: nil
|
24
|
+
source: nil,
|
25
|
+
to_data: nil
|
24
26
|
|
25
27
|
@defined_in = defined_in
|
26
28
|
@name = name
|
27
29
|
@type = t.make type
|
28
30
|
@source = source
|
29
31
|
@default = default
|
32
|
+
@to_data = to_data
|
30
33
|
|
31
34
|
if @source.nil?
|
32
35
|
@instance_variable_source = false
|
@@ -113,7 +116,17 @@ class Prop
|
|
113
116
|
if instance_variable_source?
|
114
117
|
instance.instance_variable_get source
|
115
118
|
else
|
116
|
-
|
119
|
+
case source
|
120
|
+
when String, Symbol
|
121
|
+
instance.send source
|
122
|
+
when Proc
|
123
|
+
instance.instance_exec &source
|
124
|
+
else
|
125
|
+
raise TypeError.squished <<-END
|
126
|
+
Expected `Prop#source` to be a String, Symbol or Proc;
|
127
|
+
found #{ source.inspect }
|
128
|
+
END
|
129
|
+
end
|
117
130
|
end
|
118
131
|
else
|
119
132
|
values(instance)[name]
|
@@ -166,26 +179,85 @@ class Prop
|
|
166
179
|
end # #set_from_hash
|
167
180
|
|
168
181
|
|
169
|
-
|
170
|
-
#
|
182
|
+
# Get the "data" value - a basic scalar or structure of hashes, arrays and
|
183
|
+
# scalars, suitable for JSON encoding, etc. - for the property from an
|
184
|
+
# instance.
|
171
185
|
#
|
172
|
-
#
|
173
|
-
#
|
186
|
+
# The process depends on the `to_data:` keyword provided at property
|
187
|
+
# declaration:
|
174
188
|
#
|
175
|
-
#
|
176
|
-
#
|
189
|
+
# 1. {nil} *default*
|
190
|
+
# - If the property value responds to `#to_data`, the result of
|
191
|
+
# invoking that method will be returned.
|
192
|
+
#
|
193
|
+
# **WARNING**
|
194
|
+
#
|
195
|
+
# This can cause infinite recursion if an instance has
|
196
|
+
# a property value that is also an instance of the same class (as
|
197
|
+
# as other more complicated scenarios that boil down to the same
|
198
|
+
# problem), but, really, what else would it do in this situation?
|
199
|
+
#
|
200
|
+
# This problem can be avoided by by providing a `to_data:` keyword
|
201
|
+
# when declaring the property that dictates how to handle it's value.
|
202
|
+
# In fact, that was the motivation for adding `to_data:`.
|
203
|
+
#
|
204
|
+
# - Otherwise, the value itself is returned, under the assumption that
|
205
|
+
# it is already suitable as data.
|
206
|
+
#
|
207
|
+
# 2. {Symbol} | {String}
|
208
|
+
# - The `to_data:` string or symbol is sent to the property value
|
209
|
+
# (the method with this name is called via {Object#send}).
|
210
|
+
#
|
211
|
+
# 3. {Proc}
|
212
|
+
# - The `to_data:` proc is called with the property value as the sole
|
213
|
+
# argument and the result is returned as the data.
|
214
|
+
#
|
215
|
+
# @param [NRSER::Meta::Props] instance
|
216
|
+
# Instance to get the property value form.
|
217
|
+
#
|
218
|
+
# @return [Object]
|
219
|
+
# Data representation of the property value (hopefully - the value itself
|
220
|
+
# is returned if we don't have any better options, see above).
|
221
|
+
#
|
222
|
+
# @raise [TypeError]
|
223
|
+
# If {@to_data} (provided via the `to_data:` keyword at property
|
224
|
+
# declaration) is anything other than {nil}, {String}, {Symbol} or {Proc}.
|
177
225
|
#
|
178
226
|
def to_data instance
|
179
227
|
value = get instance
|
180
228
|
|
181
|
-
|
182
|
-
|
229
|
+
case @to_data
|
230
|
+
when nil
|
231
|
+
if value.respond_to? :to_data
|
232
|
+
value.to_data
|
233
|
+
elsif type.respond_to? :to_data
|
234
|
+
type.to_data value
|
235
|
+
else
|
236
|
+
value
|
237
|
+
end
|
238
|
+
when Symbol, String
|
239
|
+
value.send @to_data
|
240
|
+
when Proc
|
241
|
+
@to_data.call value
|
183
242
|
else
|
184
|
-
|
243
|
+
raise TypeError.squished <<-END
|
244
|
+
Expected `@to_data` to be Symbol, String or Proc;
|
245
|
+
found #{ @to_data.inspect }
|
246
|
+
END
|
185
247
|
end
|
186
248
|
end # #to_data
|
187
249
|
|
188
250
|
|
251
|
+
# @return [String]
|
252
|
+
# a short string describing the instance.
|
253
|
+
#
|
254
|
+
def to_s
|
255
|
+
<<-END.squish
|
256
|
+
#<#{ self.class.name }
|
257
|
+
#{ @defined_in.name }##{ @name }:#{ @type }>
|
258
|
+
END
|
259
|
+
end # #to_s
|
260
|
+
|
189
261
|
|
190
262
|
private
|
191
263
|
|
data/lib/nrser/meta/props.rb
CHANGED
@@ -87,13 +87,16 @@ module Props
|
|
87
87
|
#
|
88
88
|
module ClassMethods
|
89
89
|
|
90
|
-
#
|
90
|
+
# Get a map of property names to property instances.
|
91
91
|
#
|
92
|
-
# @param [
|
93
|
-
#
|
92
|
+
# @param [Boolean] only_own:
|
93
|
+
# Don't include super-class properties.
|
94
94
|
#
|
95
|
-
# @
|
96
|
-
#
|
95
|
+
# @param [Boolean] only_primary:
|
96
|
+
# Don't include properties that have a {NRSER::Meta::Props::Prop#source}.
|
97
|
+
#
|
98
|
+
# @return [Hash{ Symbol => NRSER::Meta::Props::Prop }]
|
99
|
+
# Hash mapping property name to property instance.
|
97
100
|
#
|
98
101
|
def props only_own: false, only_primary: false
|
99
102
|
result = if !only_own && superclass.respond_to?(:props)
|
@@ -118,13 +121,17 @@ module Props
|
|
118
121
|
end # #own_props
|
119
122
|
|
120
123
|
|
121
|
-
#
|
124
|
+
# Define a property.
|
125
|
+
#
|
126
|
+
# @param [Symbol] name
|
127
|
+
# The name of the property.
|
122
128
|
#
|
123
|
-
# @param [
|
124
|
-
#
|
129
|
+
# @param [Hash{ Symbol => Object }] **opts
|
130
|
+
# Constructor options for {NRSER::Meta::Props::Prop}.
|
125
131
|
#
|
126
|
-
# @return [
|
127
|
-
#
|
132
|
+
# @return [NRSER::Meta::Props::Prop]
|
133
|
+
# The newly created prop, thought you probably don't need it (it's
|
134
|
+
# already all bound up on the class at this point), but why not?
|
128
135
|
#
|
129
136
|
def prop name, **opts
|
130
137
|
ref = NRSER::Meta::Props.get_props_ref self
|
@@ -153,6 +160,8 @@ module Props
|
|
153
160
|
# end
|
154
161
|
end
|
155
162
|
end
|
163
|
+
|
164
|
+
prop
|
156
165
|
end # #prop
|
157
166
|
|
158
167
|
|
@@ -212,6 +221,7 @@ module Props
|
|
212
221
|
map_values { |name, prop| prop.get self }
|
213
222
|
end # #to_h
|
214
223
|
|
224
|
+
|
215
225
|
# Create a "data" representation suitable for transport, storage, etc.
|
216
226
|
#
|
217
227
|
# The result is meant to consist of only basic data types and structures -
|
@@ -241,19 +251,30 @@ module Props
|
|
241
251
|
end # #to_data
|
242
252
|
|
243
253
|
|
244
|
-
|
254
|
+
|
255
|
+
# Language Inter-Op
|
256
|
+
# ---------------------------------------------------------------------
|
257
|
+
|
258
|
+
# Get a JSON {String} encoding the instance's data.
|
245
259
|
#
|
246
|
-
# @param [
|
247
|
-
#
|
260
|
+
# @param [Array] *args
|
261
|
+
# I really don't know. `#to_json` takes at last one argument, but I've
|
262
|
+
# had trouble finding a spec for it :/
|
248
263
|
#
|
249
|
-
# @return [
|
250
|
-
# @todo Document return value.
|
264
|
+
# @return [String]
|
251
265
|
#
|
252
266
|
def to_json *args
|
253
267
|
to_data.to_json *args
|
254
268
|
end # #to_json
|
255
269
|
|
256
270
|
|
271
|
+
# Get a YAML {String} encoding the instance's data.
|
272
|
+
#
|
273
|
+
# @param [Array] *args
|
274
|
+
# I really don't know... whatever {YAML.dump} sends to it i guess.
|
275
|
+
#
|
276
|
+
# @return [String]
|
277
|
+
#
|
257
278
|
def to_yaml *args
|
258
279
|
to_data.to_yaml *args
|
259
280
|
end
|
@@ -76,5 +76,18 @@ module NRSER
|
|
76
76
|
NRSER.map_keys self, &block
|
77
77
|
end
|
78
78
|
|
79
|
+
|
80
|
+
# See {NRSER.bury!}
|
81
|
+
def bury! key_path,
|
82
|
+
value,
|
83
|
+
parsed_key_type: :guess,
|
84
|
+
clobber: false
|
85
|
+
NRSER.bury! self,
|
86
|
+
key_path,
|
87
|
+
value,
|
88
|
+
parsed_key_type: parsed_key_type,
|
89
|
+
clobber: clobber
|
90
|
+
end
|
91
|
+
|
79
92
|
end # refine ::Hash
|
80
93
|
end # NRSER
|
@@ -33,5 +33,16 @@ module NRSER
|
|
33
33
|
super pattern, replacement
|
34
34
|
end
|
35
35
|
end
|
36
|
+
|
37
|
+
|
38
|
+
# Just returns `self`. Implemented to match the {String#to_pn} API so it
|
39
|
+
# can be called on an argument that may be either one.
|
40
|
+
#
|
41
|
+
# @return [Pathname]
|
42
|
+
#
|
43
|
+
def to_pn
|
44
|
+
Pathname.new self
|
45
|
+
end
|
46
|
+
|
36
47
|
end # Pathname
|
37
48
|
end # NRSER
|
data/lib/nrser/refinements.rb
CHANGED
data/lib/nrser/spex.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
##############################################################################
|
2
|
+
# RSpec helpers, shared examples, and other goodies.
|
3
|
+
#
|
4
|
+
# This file is *not* required by default when `nrser` is since it **defines
|
5
|
+
# global methods** and is not needed unless you're in [Rspec][].
|
6
|
+
#
|
7
|
+
# [Rspec]: http://rspec.info/
|
8
|
+
#
|
9
|
+
##############################################################################
|
10
|
+
|
11
|
+
|
12
|
+
# Helpers
|
13
|
+
# =====================================================================
|
14
|
+
|
15
|
+
# Merge "expectation" hashes by appending all clauses for each state.
|
16
|
+
#
|
17
|
+
# @example
|
18
|
+
#
|
19
|
+
#
|
20
|
+
# @param [Array<Hash>] *expectations
|
21
|
+
# Splat of "expectation" hashes - see the examples.
|
22
|
+
#
|
23
|
+
def merge_expectations *expectations
|
24
|
+
Hash.new { |result, state|
|
25
|
+
result[state] = []
|
26
|
+
}.tap { |result|
|
27
|
+
expectations.each { |ex|
|
28
|
+
ex.each { |state, clauses|
|
29
|
+
result[state] += clauses.to_a
|
30
|
+
}
|
31
|
+
}
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
# Refine the subject of the parent scope by `#send`ing it a message and setting
|
37
|
+
# the result as the new subject.
|
38
|
+
#
|
39
|
+
# @param [Symbol | String] method_name
|
40
|
+
# Name of the method to send to.
|
41
|
+
#
|
42
|
+
# @param [Array] args*
|
43
|
+
# Arguments to send to the method.
|
44
|
+
#
|
45
|
+
# @return [void]
|
46
|
+
# Seems to be what RSpec's `subject` method returns.
|
47
|
+
#
|
48
|
+
def refine_subject method_name, *args
|
49
|
+
subject {
|
50
|
+
super().send method_name, *args
|
51
|
+
}
|
52
|
+
end # #refine_subject
|
53
|
+
|
54
|
+
|
55
|
+
# Shared Examples
|
56
|
+
# =====================================================================
|
57
|
+
|
58
|
+
shared_examples "expect subject" do |*expectations|
|
59
|
+
merge_expectations( *expectations ).each { |state, specs|
|
60
|
+
specs.each { |verb, noun|
|
61
|
+
it {
|
62
|
+
# like: is_expected.to(include(noun))
|
63
|
+
is_expected.send state, self.send(verb, noun)
|
64
|
+
}
|
65
|
+
}
|
66
|
+
}
|
67
|
+
end # is expected
|
68
|
+
|