nrser 0.0.20 → 0.0.21

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