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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ad84454dbdd85a4a86dbb940a0901cafe0effaef
4
- data.tar.gz: 9992915527ce7f7749aac2d9bdf319a05507fd23
3
+ metadata.gz: 150628e4ec282944b3bfa4d667e0c678cfe9b6e3
4
+ data.tar.gz: d31edbf013cde3c160d3105e0ac0770efae26e88
5
5
  SHA512:
6
- metadata.gz: 995db23a4d353e6a54fec4480e3d17175b99efb75a0a08cc0583a3b166fa1153a26ce58b66d2b592306a17d495ca3544a805a06a03fb06516337f072ee22eb91
7
- data.tar.gz: 47488b4ca3a7655f5264578a6e9283b04e2e1148093c72ec032cbc03720f098d6e7531cd44ccc1e20f9ec3e4ea063a905fb4f4699b6793042a816e0a7f5d6ca7
6
+ metadata.gz: d170a8aeba6ed6940aa53a0d92a8f674e6b0b0bcaed15b8808b43dcde4ae690665fbd7f4bad2bc7ae72b641e675dd3e67228d935f0e0ba18e30605385bec54da
7
+ data.tar.gz: c71d68a17bb981b5638f9c28521d093fbf69266aa94323e96969c38301ad4293ba65ca1857cc6efaacc938f46a4857faff8ddfa737b6a200cac11dc8914e49a9
@@ -59,21 +59,29 @@ module NRSER
59
59
 
60
60
 
61
61
 
62
- # @todo Document find_bounded method.
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 [type] arg_name
65
- # @todo Add name param description.
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 satisfy #{ type.to_s }
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
- require 'nrser/refinements/types'
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
- instance.send source
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
- # @todo Document to_data method.
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
- # @param [type] arg_name
173
- # @todo Add name param description.
186
+ # The process depends on the `to_data:` keyword provided at property
187
+ # declaration:
174
188
  #
175
- # @return [return_type]
176
- # @todo Document return value.
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
- if value.respond_to? :to_data
182
- value.to_data
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
- value
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
 
@@ -87,13 +87,16 @@ module Props
87
87
  #
88
88
  module ClassMethods
89
89
 
90
- # @todo Document props method.
90
+ # Get a map of property names to property instances.
91
91
  #
92
- # @param [type] arg_name
93
- # @todo Add name param description.
92
+ # @param [Boolean] only_own:
93
+ # Don't include super-class properties.
94
94
  #
95
- # @return [return_type]
96
- # @todo Document return value.
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
- # @todo Document prop method.
124
+ # Define a property.
125
+ #
126
+ # @param [Symbol] name
127
+ # The name of the property.
122
128
  #
123
- # @param [type] arg_name
124
- # @todo Add name param description.
129
+ # @param [Hash{ Symbol => Object }] **opts
130
+ # Constructor options for {NRSER::Meta::Props::Prop}.
125
131
  #
126
- # @return [return_type]
127
- # @todo Document return value.
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
- # @todo Document to_json method.
254
+
255
+ # Language Inter-Op
256
+ # ---------------------------------------------------------------------
257
+
258
+ # Get a JSON {String} encoding the instance's data.
245
259
  #
246
- # @param [type] arg_name
247
- # @todo Add name param description.
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 [return_type]
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
@@ -1,3 +1,5 @@
1
+ require 'nrser'
2
+
1
3
  require_relative './refinements/object'
2
4
  require_relative './refinements/string'
3
5
  require_relative './refinements/array'
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
+