nrser 0.0.20 → 0.0.21

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: 01fe1d56c4df90623c6407128463f67ca2e65426
4
- data.tar.gz: 0b1dfcb6cdd036647526482901cffe7e652cf189
3
+ metadata.gz: e94f9b40217a5d9e632cf2baa64d5e5e0d39cf23
4
+ data.tar.gz: 5bd0fc14f35efd8c8c42873eddf35ec3261aa483
5
5
  SHA512:
6
- metadata.gz: af6cf0974b665d43b2b56a5fecbc1c44c2014f2aeb031eb3b831d599d55003a5d83ba6a6cdf604b34fc64700a6f713a96c8d9b69e51f550a3008449ee2d5de79
7
- data.tar.gz: e368146e2161490ed4a831063e6a79540e084c45c52a685801776b759c43cf37e5d679bad11bbe2b22fd8f2d78012276f73843a0afa0a14d54226bccd138b8ca
6
+ metadata.gz: 77102788f88d0de577d33e617b6b3fadf98c64c8fccec2415440d708f071518133147e9bea3bd6eedd02ee038fd3b23f578985edd80e2706a8611d30bb92b626
7
+ data.tar.gz: 98163e885104844aae596aa379f970780d271df8fdbeeae76f25a31460635c2de5bc1f8eec7008e67038ea84e7c35a5047676d3ee7d80504e7c6d82e1894c048
data/README.md CHANGED
@@ -26,10 +26,7 @@ Or install it yourself as:
26
26
 
27
27
  $ gem install nrser
28
28
 
29
- ## Contributing
30
29
 
31
- 1. Fork it ( https://github.com/nrser/nrser-ruby/fork )
32
- 2. Create your feature branch (`git checkout -b my-new-feature`)
33
- 3. Commit your changes (`git commit -am 'Add some feature'`)
34
- 4. Push to the branch (`git push origin my-new-feature`)
35
- 5. Create a new Pull Request
30
+ ## Design
31
+
32
+
@@ -1,4 +1,5 @@
1
1
  require 'set'
2
+ require 'ostruct'
2
3
 
3
4
  module NRSER
4
5
  # include this module in any custom classes to have them treated as
@@ -10,9 +11,13 @@ module NRSER
10
11
  Array,
11
12
  Hash,
12
13
  Set,
14
+ OpenStruct,
13
15
  ]
14
16
  end
15
17
 
18
+ # Eigenclass (Singleton Class)
19
+ # ========================================================================
20
+ #
16
21
  class << self
17
22
 
18
23
  # test if an object is considered a collection.
@@ -24,40 +29,73 @@ module NRSER
24
29
  Collection::STDLIB.any? {|cls| obj.is_a? cls} || obj.is_a?(Collection)
25
30
  end
26
31
 
27
- # yield on each element of a collection or on the object itself if it's
32
+
33
+ # Yield on each element of a collection or on the object itself if it's
28
34
  # not a collection. avoids having to normalize to an array to iterate over
29
35
  # something that may be an object OR a collection of objects.
30
36
  #
31
- # @param obj [Object] target object.
37
+ # **NOTE** Implemented for our idea of a collection instead of testing
38
+ # for response to `#each` (or similar) to avoid catching things
39
+ # like {IO} instances, which include {Enumerable} but are
40
+ # probably not what is desired when using {NRSER.each}
41
+ # (more likely that you mean "I expect one or more files" than
42
+ # "I expect one or more strings which may be represented by
43
+ # lines in an open {File}").
44
+ #
45
+ # @param [Object] object
46
+ # Target object.
32
47
  #
33
- # @yield each element of a collection or the target object itself.
48
+ # @yield
49
+ # Each element of a collection or the target object itself.
34
50
  #
35
- # @return [Object] obj param.
51
+ # @return [Object]
52
+ # `object` param.
36
53
  #
37
- def each obj, &block
38
- if collection? obj
39
- obj.each &block
54
+ def each object, &block
55
+ if collection? object
56
+ # We need to test for response because {OpenStruct} *will* respond to
57
+ # #each because *it will respond to anything* (which sucks), but it
58
+ # will return `false` for `respond_to? :each` and the like, and this
59
+ # behavior could be shared by other collection objects, so it seems
60
+ # like a decent idea.
61
+ if object.respond_to? :each_pair
62
+ object.each_pair &block
63
+ elsif object.respond_to? :each
64
+ object.each &block
65
+ else
66
+ raise TypeError.squished <<-END
67
+ Object #{ obj.inpsect } does not respond to #each or #each_pair
68
+ END
69
+ end
40
70
  else
41
- block.call obj
42
- obj
71
+ block.call object
43
72
  end
73
+ object
44
74
  end
45
75
 
46
- # if `obj` is a collection, calls `#map` with the block. otherwise,
76
+
77
+ # If `object` is a collection, calls `#map` with the block. Otherwise,
47
78
  # applies block to the object and returns the result.
48
79
  #
49
- # @param obj [Object] target object.
80
+ # See note in {NRSER.each} for discussion of why this tests for a
81
+ # collection instead of duck-typing `#map`.
82
+ #
83
+ # @param [Object] object
84
+ # Target object.
50
85
  #
51
- # @yield each element of a collection or the target object itself.
86
+ # @yield
87
+ # Each element of a collection or the target object itself.
52
88
  #
53
- # @return [Object] the result of mapping or applying the block.
89
+ # @return [Object]
90
+ # The result of mapping or applying the block.
54
91
  #
55
- def map obj, &block
56
- if collection? obj
57
- obj.map &block
92
+ def map object, &block
93
+ if collection? object
94
+ object.map &block
58
95
  else
59
- block.call obj
96
+ block.call object
60
97
  end
61
- end
62
- end
63
- end
98
+ end # #map
99
+
100
+ end # class << self
101
+ end # module NRSER
@@ -1,31 +1,161 @@
1
+ require 'pp'
2
+
3
+ require_relative './errors'
4
+
1
5
  module NRSER
2
- # Maps an enumerable object to a *new* hash with the same keys and values
3
- # obtained by calling `block` with the current key and value.
6
+
7
+ # Eigenclass (Singleton Class)
8
+ # ========================================================================
4
9
  #
5
- # If `enumerable` *does not* successfully respond to `#to_h` then it's
6
- # treated as a hash where it's elements are the keys and all the values
7
- # are `nil`.
8
- #
9
- # @return [Hash]
10
- #
11
- def self.map_values enumerable, &block
12
- # Short-cut for Hash itself - though it would of course work through the
13
- # next step, it's going to probably be *the* most common argument type,
14
- # and there's no reason to do the tests and set up the exception
15
- # handler if we already know what's up with it.
16
- return NRSER.map_hash_values(enumerable, &block) if enumerable.is_a? Hash
17
-
18
- if enumerable.respond_to? :to_h
19
- begin
20
- hash = enumerable.to_h
21
- rescue TypeError => e
10
+ class << self
11
+ # Maps an enumerable object to a *new* hash with the same keys and values
12
+ # obtained by calling `block` with the current key and value.
13
+ #
14
+ # If `enumerable` *does not* respond to `#to_pairs` then it's
15
+ # treated as a hash where the elements iterated by `#each` are it's keys
16
+ # and all it's values are `nil`.
17
+ #
18
+ # In this way, {NRSER.map_values} handles Hash, Array, Set, OpenStruct,
19
+ # and probably pretty much anything else reasonable you may throw at it.
20
+ #
21
+ # @param [#each_pair, #each] enumerable
22
+ #
23
+ # @yieldparam [Object] key
24
+ # The key that will be used for whatever value the block returns in the
25
+ # new hash.
26
+ #
27
+ # @yieldparam [nil, Object] value
28
+ # If `enumerable` responds to `#each_pair`, the second parameter it yielded
29
+ # along with `key`. Otherwise `nil`.
30
+ #
31
+ # @yieldreturn [Object]
32
+ # Value for the new hash.
33
+ #
34
+ # @return [Hash]
35
+ #
36
+ # @raise [TypeError]
37
+ # If `enumerable` does not respond to `#each_pair` or `#each`.
38
+ #
39
+ def map_values enumerable, &block
40
+ result = {}
41
+
42
+ if enumerable.respond_to? :each_pair
43
+ enumerable.each_pair { |key, value|
44
+ result[key] = block.call key, value
45
+ }
46
+ elsif enumerable.respond_to? :each
47
+ enumerable.each { |key|
48
+ result[key] = block.call key, nil
49
+ }
50
+ else
51
+ raise TypeError.new NRSER.squish <<-END
52
+ First argument must respond to #each_pair or #each
53
+ (found #{ enumerable.inspect })
54
+ END
55
+ end
56
+
57
+ result
58
+ end # #map_values
59
+
60
+
61
+
62
+ # @todo Document find_bounded method.
63
+ #
64
+ # @param [type] arg_name
65
+ # @todo Add name param description.
66
+ #
67
+ # @return [return_type]
68
+ # @todo Document return value.
69
+ #
70
+ def find_bounded enum, bounds, &block
71
+ NRSER::Types.
72
+ length(bounds).
73
+ check(enum.find_all &block) { |type:, value:|
74
+ NRSER.dedent <<-END
75
+
76
+ Length of found elements (#{ value.length }) FAILED to satisfy #{ type.to_s }
77
+
78
+ Found:
79
+ #{ NRSER.indent value.pretty_inspect }
80
+
81
+ Enumerable:
82
+ #{ NRSER.indent enum.pretty_inspect }
83
+
84
+ END
85
+ }
86
+ end # #find_bounded
87
+
88
+
89
+
90
+ # @todo Document find_only method.
91
+ #
92
+ # @param [type] arg_name
93
+ # @todo Add name param description.
94
+ #
95
+ # @return [return_type]
96
+ # @todo Document return value.
97
+ #
98
+ def find_only enum, &block
99
+ find_bounded(enum, 1, &block).first
100
+ end # #find_only
101
+
102
+
103
+
104
+ # @todo Document to_h_by method.
105
+ #
106
+ # @param [type] arg_name
107
+ # @todo Add name param description.
108
+ #
109
+ # @return [return_type]
110
+ # @todo Document return value.
111
+ #
112
+ def to_h_by enum, &block
113
+ {}.tap { |result|
114
+ enum.each { |element|
115
+ key = block.call element
116
+
117
+ if result.key? key
118
+ raise NRSER::ConflictError.dedented <<-END
119
+ Key #{ key.inspect } is already in results with value:
120
+
121
+ #{ result[key].pretty_inspect }
122
+ END
123
+ end
124
+
125
+ result[key] = element
126
+ }
127
+ }
128
+ end # #to_h_by
129
+
130
+
131
+ # Create an {Enumerator} that iterates over the "values" of an
132
+ # {Enumerable} `enum`. If `enum` responds to `#each_value` than we return
133
+ # that. Otherwise, we return `#each_entry`.
134
+ #
135
+ # @param [Enumerable] enum
136
+ #
137
+ # @return [Enumerator]
138
+ #
139
+ # @raise [TypeError]
140
+ # If `enum` doesn't respond to `#each_value` or `#each_entry`.
141
+ #
142
+ def enumerate_as_values enum
143
+ # NRSER.match enum,
144
+ # t.respond_to(:each_value), :each_value.to_proc,
145
+ # t.respond_to(:each_entry), :each_entry.to_proc
146
+ #
147
+ if enum.respond_to? :each_value
148
+ enum.each_value
149
+ elsif enum.respond_to? :each_entry
150
+ enum.each_entry
22
151
  else
23
- return NRSER.map_hash_values hash, &block
152
+ raise TypeError.squished <<-END
153
+ Expected `enum` arg to respond to :each_value or :each_entry,
154
+ found #{ enum.inspect }
155
+ END
24
156
  end
25
- end
157
+ end # #enumerate_as_values
158
+
26
159
 
27
- result = {}
28
- enumerable.each { |key| result[key] = block.call key, nil }
29
- result
30
- end
160
+ end # class << self (Eigenclass)
31
161
  end # module NRSER
@@ -0,0 +1,9 @@
1
+
2
+ module NRSER
3
+
4
+ class Error < StandardError; end
5
+
6
+ class ConflictError < Error; end
7
+
8
+ end
9
+
data/lib/nrser/hash.rb CHANGED
@@ -116,22 +116,6 @@ module NRSER
116
116
 
117
117
 
118
118
 
119
- # @todo Document map_hash_values method.
120
- #
121
- # @param [Hash] hash
122
- # @todo Add name param description.
123
- #
124
- # @return [return_type]
125
- # @todo Document return value.
126
- #
127
- def self.map_hash_values hash, &block
128
- result = {}
129
- hash.each { |key, value| result[key] = block.call key, value }
130
- result
131
- end # #map_hash_values
132
-
133
-
134
-
135
119
  # Lifted from ActiveSupport
136
120
  # =====================================================================
137
121
  #
@@ -228,7 +212,7 @@ module NRSER
228
212
  end
229
213
 
230
214
  # My-style name
231
- singleton_class.send :alias_method, :map_hash_keys, :transform_keys
215
+ singleton_class.send :alias_method, :map_keys, :transform_keys
232
216
 
233
217
 
234
218
  # Mutates `hash` by converting all keys that respond to `#to_sym` to symbols.
@@ -28,26 +28,66 @@ module ClassAttrs
28
28
  # Class methods to extend the receiver with when {NRSER::Meta::ClassAttrs}
29
29
  # is included.
30
30
  module ClassMethods
31
- def instance_variable_lookup name
32
- instance_variable_get(name) || if (
33
- superclass.respond_to? :instance_variable_lookup
34
- )
35
- superclass.instance_variable_lookup name
31
+ def instance_variable_lookup name,
32
+ default: NRSER::NO_ARG,
33
+ default_from: NRSER::NO_ARG
34
+
35
+ # If it's defined here on self, return that
36
+ if instance_variable_defined? name
37
+ return instance_variable_get name
38
+ end
39
+
40
+ # Ok, now to need to look for it.
41
+
42
+ # See if the superclass has the lookup method
43
+ if superclass.respond_to? :instance_variable_lookup
44
+ # It does. See what we get from that. We create a new object to use
45
+ # as a flag and assign it to `default` so we can tell if the search
46
+ # failed.
47
+ not_found = Object.new
48
+ result = superclass.instance_variable_lookup name, default: not_found
49
+
50
+ # If we found something, return it.
51
+ return result unless result == not_found
52
+
53
+ end # if superclass.respond_to? :instance_variable_lookup
54
+
55
+ # Ok, nothing was found.
56
+
57
+ # See if we can use a default...
58
+ if default != NRSER::NO_ARG || default_from != NRSER::NO_ARG
59
+ # We can use a default.
60
+ # `default` takes precedence.
61
+ if default != NRSER::NO_ARG
62
+ default
63
+ else
64
+ send default_from
65
+ end
66
+
36
67
  else
68
+ # Nope, we can't, raise.
37
69
  raise NoMethodError.new NRSER.squish <<-END
38
70
  #{ name.inspect } is not defined anywhere in the class hierarchy
39
71
  END
40
- end
41
- end
42
72
 
43
- def class_attr_accessor attr
73
+ end # if we have a default value / else
74
+
75
+ end # #instance_variable_lookup
76
+
77
+
78
+ def class_attr_accessor attr,
79
+ default: NRSER::NO_ARG,
80
+ default_from: NRSER::NO_ARG
81
+
44
82
  var_name = "@#{ attr }".to_sym
45
83
 
46
84
  singleton_class.class_eval do
47
85
  define_method(attr) do |*args|
48
86
  case args.length
49
87
  when 0
50
- instance_variable_lookup var_name
88
+ instance_variable_lookup var_name,
89
+ default: default,
90
+ default_from: default_from
51
91
  when 1
52
92
  instance_variable_set var_name, args[0]
53
93
  else
@@ -63,6 +103,16 @@ module ClassAttrs
63
103
  end
64
104
  end
65
105
  end
106
+
107
+ def class_attr_writer attr
108
+ var_name = "@#{ attr }".to_sym
109
+
110
+ singleton_class.class_eval do
111
+ define_method("#{ attr }=") do |value|
112
+ instance_variable_set var_name, value
113
+ end
114
+ end
115
+ end
66
116
  end # module ClassMethods
67
117
 
68
118
  # Extend the including class with {NRSER::Meta::ClassAttrs::ClassMethods}
@@ -0,0 +1,60 @@
1
+ module NRSER
2
+
3
+ # Eigenclass (Singleton Class)
4
+ # ========================================================================
5
+ #
6
+ class << self
7
+
8
+ # Deeply convert a {Hash} to an {OpenStruct}.
9
+ #
10
+ # @param [Hash] hash
11
+ #
12
+ # @return [OpenStruct]
13
+ #
14
+ # @raise [TypeError]
15
+ # If `hash` is not a {Hash}.
16
+ #
17
+ def to_open_struct hash, freeze: false
18
+ unless hash.is_a? Hash
19
+ raise TypeError,
20
+ "Argument must be hash (found #{ hash.inspect })"
21
+ end
22
+
23
+ _to_open_struct hash, freeze: freeze
24
+ end # #to_open_struct
25
+
26
+
27
+ private
28
+
29
+ def _to_open_struct value, freeze:
30
+ result = case value
31
+ when OpenStruct
32
+ # Just assume it's already taken care of if it's already an OpenStruct
33
+ value
34
+
35
+ when Hash
36
+ OpenStruct.new(
37
+ map_values(value) { |k, v| _to_open_struct v, freeze: freeze }
38
+ )
39
+
40
+ when Array
41
+ value.map { |v| _to_open_struct v, freeze: freeze }
42
+
43
+ when Set
44
+ Set.new value.map { |v| _to_open_struct v, freeze: freeze }
45
+
46
+ else
47
+ value
48
+ end
49
+
50
+ if freeze
51
+ result.freeze
52
+ end
53
+
54
+ result
55
+ end # ._to_open_struct
56
+ # end private
57
+
58
+ end # class < self (Eigenclass)
59
+
60
+ end # module NRSER
@@ -1,8 +1,7 @@
1
+ require_relative './enumerable'
2
+
1
3
  module NRSER
2
4
  refine ::Array do
3
- # See {NRSER.map_array_values}
4
- def map_values &block
5
- NRSER.map_values self, &block
6
- end
7
- end # Array
5
+ include NRSER::Refinements::Enumerable
6
+ end # refine ::Array
8
7
  end # NRSER
@@ -0,0 +1,41 @@
1
+ module NRSER; end
2
+ module NRSER::Refinements; end
3
+
4
+ # Instance methods that are mixed in to the refinements of many classes that
5
+ # include {Enumerable}, including {Array}, {Set}, {Hash} and {OpenStruct}.
6
+ #
7
+ # All of these just proxy to a {NRSER} module (static) method, so the
8
+ # functionality can be used on older Rubies that can't refine.
9
+ #
10
+ module NRSER::Refinements::Enumerable
11
+
12
+ # See {NRSER.map_values}
13
+ def map_values &block
14
+ NRSER.map_values self, &block
15
+ end
16
+
17
+
18
+ # See {NRSER.find_bounded}
19
+ def find_bounded bounds, &block
20
+ NRSER.find_bounded self, bounds, &block
21
+ end
22
+
23
+
24
+ # See {NRSER.find_only}
25
+ def find_only &block
26
+ NRSER.find_only self, &block
27
+ end
28
+
29
+
30
+ # See {NRSER.to_h_by}
31
+ def to_h_by &block
32
+ NRSER.to_h_by self, &block
33
+ end
34
+
35
+ # See {NRSER.enumerate_as_values}
36
+ def enumerate_as_values
37
+ NRSER.enumerate_as_values self
38
+ end
39
+
40
+ end # module NRSER::Refinements::Enumerable
41
+
@@ -0,0 +1,7 @@
1
+ require_relative './enumerable'
2
+
3
+ module NRSER
4
+ refine ::Enumerator do
5
+ include NRSER::Refinements::Enumerable
6
+ end # refine ::Enumerator
7
+ end # NRSER
@@ -4,4 +4,32 @@ module NRSER
4
4
  NRSER.format_exception self
5
5
  end
6
6
  end
7
+
8
+ refine Exception.singleton_class do
9
+
10
+ # Create a new instance from the squished message.
11
+ #
12
+ # See {NRSER.squish}.
13
+ #
14
+ # @param [String] message
15
+ #
16
+ # @return [Exception]
17
+ #
18
+ def squished message
19
+ new message.squish
20
+ end
21
+
22
+ # Create a new instance from the dedented message.
23
+ #
24
+ # See {NRSER.dedent}.
25
+ #
26
+ # @param [String] message
27
+ #
28
+ # @return [Exception]
29
+ #
30
+ def dedented message
31
+ new message.dedent
32
+ end
33
+
34
+ end # refine Exception.singleton_class
7
35
  end # NRSER
@@ -1,5 +1,10 @@
1
+ require_relative './enumerable'
2
+
1
3
  module NRSER
4
+
2
5
  refine ::Hash do
6
+ include NRSER::Refinements::Enumerable
7
+
3
8
  # See {NRSER.except_keys!}.
4
9
  def except! *keys
5
10
  NRSER.except_keys! self, *keys
@@ -28,11 +33,6 @@ module NRSER
28
33
  end # #leaves
29
34
 
30
35
 
31
- # See {NRSER.map_hash_values}.
32
- def map_values &block
33
- NRSER.map_hash_values self, &block
34
- end
35
-
36
36
  # See {NRSER.transform_keys!}
37
37
  def transform_keys! &block
38
38
  return enum_for(:transform_keys!) { size } unless block_given?
@@ -70,9 +70,11 @@ module NRSER
70
70
  NRSER.stringify_keys self
71
71
  end
72
72
 
73
+
73
74
  # See {NRSER.map_hash_keys}
74
75
  def map_keys &block
75
- NRSER.map_hash_keys self, &block
76
+ NRSER.map_keys self, &block
76
77
  end
77
- end # Hash
78
+
79
+ end # refine ::Hash
78
80
  end # NRSER
@@ -0,0 +1,30 @@
1
+ require 'ostruct'
2
+
3
+ module NRSER
4
+ refine OpenStruct do
5
+
6
+ # Map values using {NRSER.map_values} into a new {OpenStruct} instance.
7
+ #
8
+ # @return [OpenStruct]
9
+ #
10
+ def map_values &block
11
+ self.class.new NRSER.map_values(self, &block)
12
+ end # #map_values
13
+
14
+
15
+ # See {NRSER.to_h_by}
16
+ def to_h_by &block
17
+ NRSER.to_h_by self, &block
18
+ end
19
+
20
+ end
21
+
22
+ refine OpenStruct.singleton_class do
23
+
24
+ # See {NRSER.to_open_struct}.
25
+ def from_h hash, freeze: false
26
+ NRSER.to_open_struct hash, freeze: freeze
27
+ end # .from
28
+
29
+ end
30
+ end # module NRSER