nrser 0.0.24 → 0.0.25

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 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
+