transproc 0.4.2 → 1.0.0

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.
@@ -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