transproc 0.4.2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -68,9 +68,5 @@ module Transproc
68
68
  def self.is(value, type, fn)
69
69
  guard(value, -> v { v.is_a?(type) }, fn)
70
70
  end
71
-
72
- # @deprecated Register methods globally
73
- (methods - Registry.methods - Registry.instance_methods)
74
- .each { |name| Transproc.register name, t(name) }
75
71
  end
76
72
  end
@@ -28,18 +28,10 @@ module Transproc
28
28
  # @return [Hash]
29
29
  #
30
30
  # @api public
31
- def self.map_keys(hash, fn)
32
- map_keys!(Hash[hash], fn)
33
- end
34
-
35
- # Same as `:map_keys` but mutates the hash
36
- #
37
- # @see HashTransformations.map_keys
38
- #
39
- # @api public
40
- def self.map_keys!(hash, fn)
41
- hash.keys.each { |key| hash[fn[key]] = hash.delete(key) }
42
- hash
31
+ def self.map_keys(source_hash, fn)
32
+ Hash[source_hash].tap do |hash|
33
+ hash.keys.each { |key| hash[fn[key]] = hash.delete(key) }
34
+ end
43
35
  end
44
36
 
45
37
  # Symbolize all keys in a hash
@@ -54,16 +46,7 @@ module Transproc
54
46
  #
55
47
  # @api public
56
48
  def self.symbolize_keys(hash)
57
- symbolize_keys!(Hash[hash])
58
- end
59
-
60
- # Same as `:symbolize_keys` but mutates the hash
61
- #
62
- # @see HashTransformations.symbolize_keys!
63
- #
64
- # @api public
65
- def self.symbolize_keys!(hash)
66
- map_keys!(hash, Coercions[:to_symbol].fn)
49
+ map_keys(hash, Coercions[:to_symbol].fn)
67
50
  end
68
51
 
69
52
  # Symbolize keys in a hash recursively
@@ -108,16 +91,7 @@ module Transproc
108
91
  #
109
92
  # @api public
110
93
  def self.stringify_keys(hash)
111
- stringify_keys!(Hash[hash])
112
- end
113
-
114
- # Same as `:stringify_keys` but mutates the hash
115
- #
116
- # @see HashTransformations.stringify_keys
117
- #
118
- # @api public
119
- def self.stringify_keys!(hash)
120
- map_keys!(hash, Coercions[:to_string].fn)
94
+ map_keys(hash, Coercions[:to_string].fn)
121
95
  end
122
96
 
123
97
  # Map all values in a hash using transformation function
@@ -131,22 +105,10 @@ module Transproc
131
105
  # @return [Hash]
132
106
  #
133
107
  # @api public
134
- def self.map_values(hash, fn)
135
- map_values!(Hash[hash], fn)
136
- end
137
-
138
- # Same as `:map_values` but mutates the hash
139
- #
140
- # @see HashTransformations.map_values
141
- #
142
- # @param [Hash]
143
- #
144
- # @return [Hash]
145
- #
146
- # @api public
147
- def self.map_values!(hash, fn)
148
- hash.each { |key, value| hash[key] = fn[value] }
149
- hash
108
+ def self.map_values(source_hash, fn)
109
+ Hash[source_hash].tap do |hash|
110
+ hash.each { |key, value| hash[key] = fn[value] }
111
+ end
150
112
  end
151
113
 
152
114
  # Rename all keys in a hash using provided mapping hash
@@ -155,24 +117,16 @@ module Transproc
155
117
  # Transproc(:rename_keys, user_name: :name)[user_name: 'Jane']
156
118
  # # => {:name => "Jane"}
157
119
  #
158
- # @param [Hash] hash The input hash
120
+ # @param [Hash] source_hash The input hash
159
121
  # @param [Hash] mapping The key-rename mapping
160
122
  #
161
123
  # @return [Hash]
162
124
  #
163
125
  # @api public
164
- def self.rename_keys(hash, mapping)
165
- rename_keys!(Hash[hash], mapping)
166
- end
167
-
168
- # Same as `:rename_keys` but mutates the hash
169
- #
170
- # @see HashTransformations.rename_keys
171
- #
172
- # @api public
173
- def self.rename_keys!(hash, mapping)
174
- mapping.each { |k, v| hash[v] = hash.delete(k) if hash.has_key?(k) }
175
- hash
126
+ def self.rename_keys(source_hash, mapping)
127
+ Hash[source_hash].tap do |hash|
128
+ mapping.each { |k, v| hash[v] = hash.delete(k) if hash.key?(k) }
129
+ end
176
130
  end
177
131
 
178
132
  # Copy all keys in a hash using provided mapping hash
@@ -181,28 +135,20 @@ module Transproc
181
135
  # Transproc(:copy_keys, user_name: :name)[user_name: 'Jane']
182
136
  # # => {:user_name => "Jane", :name => "Jane"}
183
137
  #
184
- # @param [Hash] hash The input hash
138
+ # @param [Hash] source_hash The input hash
185
139
  # @param [Hash] mapping The key-copy mapping
186
140
  #
187
141
  # @return [Hash]
188
142
  #
189
143
  # @api public
190
- def self.copy_keys(hash, mapping)
191
- copy_keys!(Hash[hash], mapping)
192
- end
193
-
194
- # Same as `:copy_keys` but mutates the hash
195
- #
196
- # @see HashTransformations.copy_keys
197
- #
198
- # @api public
199
- def self.copy_keys!(hash, mapping)
200
- mapping.each do |original_key, new_keys|
201
- [*new_keys].each do |new_key|
202
- hash[new_key] = hash[original_key]
144
+ def self.copy_keys(source_hash, mapping)
145
+ Hash[source_hash].tap do |hash|
146
+ mapping.each do |original_key, new_keys|
147
+ [*new_keys].each do |new_key|
148
+ hash[new_key] = hash[original_key]
149
+ end
203
150
  end
204
151
  end
205
- hash
206
152
  end
207
153
 
208
154
  # Rejects specified keys from a hash
@@ -218,16 +164,7 @@ module Transproc
218
164
  #
219
165
  # @api public
220
166
  def self.reject_keys(hash, keys)
221
- reject_keys!(Hash[hash], keys)
222
- end
223
-
224
- # Same as `:reject_keys` but mutates the hash
225
- #
226
- # @see HashTransformations.reject_keys
227
- #
228
- # @api public
229
- def self.reject_keys!(hash, keys)
230
- hash.reject { |k, _| keys.include?(k) }
167
+ Hash[hash].reject { |k, _| keys.include?(k) }
231
168
  end
232
169
 
233
170
  # Accepts specified keys from a hash
@@ -243,16 +180,7 @@ module Transproc
243
180
  #
244
181
  # @api public
245
182
  def self.accept_keys(hash, keys)
246
- accept_keys!(Hash[hash], keys)
247
- end
248
-
249
- # Same as `:accept_keys` but mutates the hash
250
- #
251
- # @see HashTransformations.accept
252
- #
253
- # @api public
254
- def self.accept_keys!(hash, keys)
255
- reject_keys!(hash, hash.keys - keys)
183
+ reject_keys(hash, hash.keys - keys)
256
184
  end
257
185
 
258
186
  # Map a key in a hash with the provided transformation function
@@ -270,15 +198,6 @@ module Transproc
270
198
  hash.merge(key => fn[hash[key]])
271
199
  end
272
200
 
273
- # Same as `:map_value` but mutates the hash
274
- #
275
- # @see HashTransformations.map_value
276
- #
277
- # @api public
278
- def self.map_value!(hash, key, fn)
279
- hash.update(key => fn[hash[key]])
280
- end
281
-
282
201
  # Nest values from specified keys under a new key
283
202
  #
284
203
  # @example
@@ -290,25 +209,17 @@ module Transproc
290
209
  # @return [Hash]
291
210
  #
292
211
  # @api public
293
- def self.nest(hash, key, keys)
294
- nest!(Hash[hash], key, keys)
295
- end
296
-
297
- # Same as `:nest` but mutates the hash
298
- #
299
- # @see HashTransformations.nest
300
- #
301
- # @api public
302
- def self.nest!(hash, root, keys)
303
- nest_keys = hash.keys & keys
212
+ def self.nest(source_hash, root, keys)
213
+ nest_keys = source_hash.keys & keys
304
214
 
305
- if nest_keys.size > 0
215
+ if !nest_keys.empty?
216
+ hash = Hash[source_hash]
306
217
  child = Hash[nest_keys.zip(nest_keys.map { |key| hash.delete(key) })]
307
218
  old_nest = hash[root]
308
219
  new_nest = old_nest.is_a?(Hash) ? old_nest.merge(child) : child
309
- hash.update(root => new_nest)
220
+ hash.merge(root => new_nest)
310
221
  else
311
- hash.update(root => {})
222
+ source_hash.merge(root => {})
312
223
  end
313
224
  end
314
225
 
@@ -318,9 +229,9 @@ module Transproc
318
229
  # Transproc(:unwrap, :address, [:street, :zipcode])[address: { street: 'Street', zipcode: '123' }]
319
230
  # # => {street: "Street", zipcode: "123"}
320
231
  #
321
- # @param [Hash] hash
232
+ # @param [Hash] source_hash
322
233
  # @param [Mixed] root The root key to unwrap values from
323
- # @param [Array] keys The keys that should be unwrapped (optional)
234
+ # @param [Array] selected The keys that should be unwrapped (optional)
324
235
  # @param [Hash] options hash of options (optional)
325
236
  # @option options [Boolean] :prefix if true, unwrapped keys will be prefixed
326
237
  # with the root key followed by an underscore (_)
@@ -328,37 +239,23 @@ module Transproc
328
239
  # @return [Hash]
329
240
  #
330
241
  # @api public
331
- def self.unwrap(hash, root, keys = nil, prefix: false)
332
- copy = Hash[hash].merge(root => Hash[hash[root]])
333
- unwrap!(copy, root, keys, prefix: prefix)
334
- end
242
+ def self.unwrap(source_hash, root, selected = nil, prefix: false)
243
+ return source_hash unless source_hash[root]
335
244
 
336
- # Same as `:unwrap` but mutates the hash
337
- #
338
- # @see HashTransformations.unwrap
339
- #
340
- # @api public
341
- def self.unwrap!(hash, root, selected = nil, prefix: false)
342
- if nested_hash = hash[root]
245
+ add_prefix = ->(key) do
246
+ combined = [root, key].join('_')
247
+ root.is_a?(::Symbol) ? combined.to_sym : combined
248
+ end
249
+
250
+ Hash[source_hash].merge(root => Hash[source_hash[root]]).tap do |hash|
251
+ nested_hash = hash[root]
343
252
  keys = nested_hash.keys
344
253
  keys &= selected if selected
345
- new_keys = if prefix
346
- keys.map do |key|
347
- if root.is_a?(::Symbol)
348
- [root, key].join('_').to_sym
349
- else
350
- [root, key].join('_')
351
- end
352
- end
353
- else
354
- keys
355
- end
254
+ new_keys = prefix ? keys.map(&add_prefix) : keys
356
255
 
357
256
  hash.update(Hash[new_keys.zip(keys.map { |key| nested_hash.delete(key) })])
358
257
  hash.delete(root) if nested_hash.empty?
359
258
  end
360
-
361
- hash
362
259
  end
363
260
 
364
261
  # Folds array of tuples to array of values from a specified key
@@ -381,16 +278,7 @@ module Transproc
381
278
  #
382
279
  # @api public
383
280
  def self.fold(hash, key, tuple_key)
384
- fold!(Hash[hash], key, tuple_key)
385
- end
386
-
387
- # Same as `:fold` but mutates the hash
388
- #
389
- # @see HashTransformations.fold
390
- #
391
- # @api public
392
- def self.fold!(hash, key, tuple_key)
393
- hash.update(key => ArrayTransformations.extract_key(hash[key], tuple_key))
281
+ hash.merge(key => ArrayTransformations.extract_key(hash[key], tuple_key))
394
282
  end
395
283
 
396
284
  # Splits hash to array by all values from a specified key
@@ -502,9 +390,5 @@ module Transproc
502
390
  end
503
391
  end
504
392
  end
505
-
506
- # @deprecated Register methods globally
507
- (methods - Registry.instance_methods - Registry.methods)
508
- .each { |name| Transproc.register name, t(name) }
509
393
  end
510
394
  end
@@ -38,9 +38,5 @@ module Transproc
38
38
  def self.bind(value, binding, fn)
39
39
  binding.instance_exec(value, &fn)
40
40
  end
41
-
42
- # @deprecated Register methods globally
43
- (methods - Registry.instance_methods - Registry.methods)
44
- .each { |name| Transproc.register name, t(name) }
45
41
  end
46
42
  end
@@ -113,9 +113,5 @@ module Transproc
113
113
 
114
114
  result
115
115
  end
116
-
117
- # @deprecated Register methods globally
118
- (methods - Registry.methods - Registry.instance_methods)
119
- .each { |name| Transproc.register name, t(name) }
120
116
  end
121
117
  end
@@ -45,10 +45,37 @@ module Transproc
45
45
  # @alias :t
46
46
  #
47
47
  def [](fn, *args)
48
- Function.new(fetch(fn), args: args, name: fn)
48
+ fetched = fetch(fn)
49
+ return fetched if already_wrapped?(fetched)
50
+ Function.new(fetched, args: args, name: fn)
49
51
  end
50
52
  alias_method :t, :[]
51
53
 
54
+ # Returns wether the registry contains such transformation by its key
55
+ #
56
+ # @param [Symbol] key
57
+ #
58
+ # @return [Boolean]
59
+ #
60
+ def contain?(key)
61
+ respond_to?(key) || store.contain?(key)
62
+ end
63
+
64
+ # Register a new function
65
+ #
66
+ # @example
67
+ # store.register(:to_json, -> v { v.to_json })
68
+
69
+ # store.register(:to_json) { |v| v.to_json }
70
+ #
71
+ def register(name, fn = nil, &block)
72
+ if contain?(name)
73
+ raise FunctionAlreadyRegisteredError, "Function #{name} is already defined"
74
+ end
75
+ @store = store.register(name, fn, &block)
76
+ self
77
+ end
78
+
52
79
  # Imports either a method (converted to a proc) from another module, or
53
80
  # all methods from that module.
54
81
  #
@@ -106,5 +133,12 @@ module Transproc
106
133
  rescue
107
134
  raise FunctionNotFoundError.new(fn, self)
108
135
  end
136
+
137
+ private
138
+
139
+ # @api private
140
+ def already_wrapped?(func)
141
+ func.is_a?(Transproc::Function) || func.is_a?(Transproc::Composite)
142
+ end
109
143
  end
110
144
  end
@@ -33,7 +33,28 @@ module Transproc
33
33
  # @return [Proc]
34
34
  #
35
35
  def fetch(key)
36
- methods.fetch(key.to_sym)
36
+ methods.fetch(key)
37
+ end
38
+
39
+ # Returns wether the collection contains such procedure by its key
40
+ #
41
+ # @param [Symbol] key
42
+ #
43
+ # @return [Boolean]
44
+ #
45
+ def contain?(key)
46
+ methods.key?(key)
47
+ end
48
+
49
+ # Register a new function
50
+ #
51
+ # @example
52
+ # store.register(:to_json, -> v { v.to_json })
53
+
54
+ # store.register(:to_json) { |v| v.to_json }
55
+ #
56
+ def register(name, fn = nil, &block)
57
+ self.class.new(methods.merge(name => fn || block))
37
58
  end
38
59
 
39
60
  # Imports proc(s) to the collection from another module
@@ -24,7 +24,7 @@ module Transproc
24
24
 
25
25
  # @api private
26
26
  def inherited(subclass)
27
- subclass.container(container)
27
+ subclass.container(@container) if defined?(@container)
28
28
  end
29
29
 
30
30
  # Get or set the container to resolve transprocs from.
@@ -47,24 +47,70 @@ module Transproc
47
47
  # @api private
48
48
  def container(container = ::Transproc::Undefined)
49
49
  if container == ::Transproc::Undefined
50
+ ensure_container_presence!
50
51
  @container
51
52
  else
52
53
  @container = container
53
54
  end
54
55
  end
55
56
 
57
+ # Define an anonymous transproc derived from given Transformer
58
+ # Evaluates block with transformations and returns initialized transproc.
59
+ # Does not mutate original Transformer
60
+ #
61
+ # @example
62
+ #
63
+ # class MyTransformer < Transproc::Transformer[MyContainer]
64
+ # end
65
+ #
66
+ # transproc = MyTransformer.define do
67
+ # map_values t(:to_string)
68
+ # end
69
+ # transproc.call(a: 1, b: 2)
70
+ # # => {a: '1', b: '2'}
71
+ #
72
+ # @yield Block allowing to define transformations. The same as class level DSL
73
+ #
74
+ # @return [Function] Composed transproc
75
+ #
76
+ # @api public
77
+ def define(&block)
78
+ return transproc unless block_given?
79
+
80
+ Class.new(self).tap { |klass| klass.instance_eval(&block) }.transproc
81
+ end
82
+ alias build define
83
+
84
+ # Get a transformation from the container,
85
+ # without adding it to the transformation pipeline
86
+ #
87
+ # @example
88
+ #
89
+ # class Stringify < Transproc::Transformer
90
+ # map_values t(:to_string)
91
+ # end
92
+ #
93
+ # Stringify.new.call(a: 1, b: 2)
94
+ # # => {a: '1', b: '2'}
95
+ #
96
+ # @param [Proc, Symbol] fn
97
+ # A proc, a name of the module's own function, or a name of imported
98
+ # procedure from another module
99
+ # @param [Object, Array] args
100
+ # Args to be carried by the transproc
101
+ #
102
+ # @return [Transproc::Function]
103
+ #
104
+ # @api public
105
+ def t(fn, *args)
106
+ container[fn, *args]
107
+ end
108
+
56
109
  # @api private
57
110
  def method_missing(method, *args, &block)
58
- if container.functions.has_key?(method)
59
- if block_given?
60
- transformations << container[
61
- method,
62
- *args,
63
- create(container, &block).transproc
64
- ]
65
- else
66
- transformations << container[method, *args]
67
- end
111
+ if container.contain?(method)
112
+ args.push(define(&block)) if block_given?
113
+ transformations << t(method, *args)
68
114
  else
69
115
  super
70
116
  end
@@ -72,7 +118,7 @@ module Transproc
72
118
 
73
119
  # @api private
74
120
  def respond_to_missing?(method, _include_private = false)
75
- container.functions.has_key?(method) || super
121
+ container.contain?(method) || super
76
122
  end
77
123
 
78
124
  # @api private
@@ -81,6 +127,7 @@ module Transproc
81
127
  end
82
128
 
83
129
  private
130
+
84
131
  # An array containing the transformation pipeline
85
132
  #
86
133
  # @api private
@@ -88,14 +135,11 @@ module Transproc
88
135
  @transformations ||= []
89
136
  end
90
137
 
91
- # Create and return a new instance of Transproc::Transformer
92
- # evaluating the block argument as the class body
93
- #
94
138
  # @api private
95
- def create(container, &block)
96
- klass = self[container]
97
- klass.instance_eval(&block)
98
- klass
139
+ def ensure_container_presence!
140
+ return if defined?(@container)
141
+ raise ArgumentError, 'Transformer function registry is empty. '\
142
+ 'Provide your registry via Transproc::Transformer[YourRegistry]'
99
143
  end
100
144
  end
101
145
  end