transproc 0.2.4 → 0.3.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +30 -0
- data/README.md +33 -15
- data/lib/transproc.rb +23 -14
- data/lib/transproc/array.rb +21 -15
- data/lib/transproc/class.rb +7 -3
- data/lib/transproc/coercions.rb +15 -11
- data/lib/transproc/composer.rb +26 -2
- data/lib/transproc/conditional.rb +7 -3
- data/lib/transproc/error.rb +7 -1
- data/lib/transproc/function.rb +28 -5
- data/lib/transproc/functions.rb +5 -0
- data/lib/transproc/hash.rb +102 -26
- data/lib/transproc/recursion.rb +11 -7
- data/lib/transproc/registry.rb +53 -55
- data/lib/transproc/rspec.rb +59 -0
- data/lib/transproc/store.rb +94 -0
- data/lib/transproc/support/deprecations.rb +11 -0
- data/lib/transproc/version.rb +1 -1
- data/spec/unit/array_transformations_spec.rb +29 -25
- data/spec/unit/class_transformations_spec.rb +2 -2
- data/spec/unit/coercions_spec.rb +13 -13
- data/spec/unit/composer_spec.rb +13 -2
- data/spec/unit/conditional_spec.rb +3 -1
- data/spec/unit/function_not_found_error_spec.rb +20 -0
- data/spec/unit/function_spec.rb +49 -8
- data/spec/unit/hash_transformations_spec.rb +101 -63
- data/spec/unit/recursion_spec.rb +12 -6
- data/spec/unit/registry_spec.rb +65 -55
- data/spec/unit/store_spec.rb +148 -0
- data/spec/unit/transproc_spec.rb +8 -0
- metadata +9 -2
data/lib/transproc/composer.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'transproc/support/deprecations'
|
2
|
+
|
1
3
|
module Transproc
|
2
4
|
# Transproc helper that adds `t` method as a shortcut for `Transproc` method
|
3
5
|
#
|
@@ -8,6 +10,24 @@ module Transproc
|
|
8
10
|
#
|
9
11
|
# @api public
|
10
12
|
module Helper
|
13
|
+
# @api private
|
14
|
+
def self.included(*)
|
15
|
+
Transproc::Deprecations.announce(
|
16
|
+
'Transproc::Helper',
|
17
|
+
'Define your own function registry using Transproc::Registry extension'
|
18
|
+
)
|
19
|
+
super
|
20
|
+
end
|
21
|
+
|
22
|
+
# @api private
|
23
|
+
def self.included(*)
|
24
|
+
Transproc::Deprecations.announce(
|
25
|
+
'Transproc::Helper',
|
26
|
+
'Define your own function registry using Transproc::Registry extension'
|
27
|
+
)
|
28
|
+
super
|
29
|
+
end
|
30
|
+
|
11
31
|
# @see Transproc
|
12
32
|
#
|
13
33
|
# @api public
|
@@ -31,8 +51,6 @@ module Transproc
|
|
31
51
|
#
|
32
52
|
# @api public
|
33
53
|
module Composer
|
34
|
-
include Helper
|
35
|
-
|
36
54
|
# @api private
|
37
55
|
class Factory
|
38
56
|
attr_reader :fns, :default
|
@@ -53,6 +71,12 @@ module Transproc
|
|
53
71
|
def to_fn
|
54
72
|
fns.reduce(:+) || default
|
55
73
|
end
|
74
|
+
|
75
|
+
# @deprecated
|
76
|
+
# @api public
|
77
|
+
def t(*args, &block)
|
78
|
+
Transproc(*args, &block)
|
79
|
+
end
|
56
80
|
end
|
57
81
|
|
58
82
|
# Gather and compose functions and fall-back to a default one if provided
|
@@ -13,7 +13,7 @@ module Transproc
|
|
13
13
|
#
|
14
14
|
# @api public
|
15
15
|
module Conditional
|
16
|
-
extend
|
16
|
+
extend Registry
|
17
17
|
|
18
18
|
# Apply the transformation function to subject if the predicate returns true, or return un-modified
|
19
19
|
#
|
@@ -28,7 +28,7 @@ module Transproc
|
|
28
28
|
# @return [Mixed]
|
29
29
|
#
|
30
30
|
# @api public
|
31
|
-
def guard(value, predicate, fn)
|
31
|
+
def self.guard(value, predicate, fn)
|
32
32
|
predicate[value] ? fn[value] : value
|
33
33
|
end
|
34
34
|
|
@@ -48,8 +48,12 @@ module Transproc
|
|
48
48
|
# @return [Object]
|
49
49
|
#
|
50
50
|
# @api public
|
51
|
-
def is(value, type, fn)
|
51
|
+
def self.is(value, type, fn)
|
52
52
|
guard(value, -> v { v.is_a?(type) }, fn)
|
53
53
|
end
|
54
|
+
|
55
|
+
# @deprecated Register methods globally
|
56
|
+
(methods - Registry.methods - Registry.instance_methods)
|
57
|
+
.each { |name| Transproc.register name, t(name) }
|
54
58
|
end
|
55
59
|
end
|
data/lib/transproc/error.rb
CHANGED
@@ -1,8 +1,14 @@
|
|
1
1
|
module Transproc
|
2
2
|
Error = Class.new(StandardError)
|
3
|
-
FunctionNotFoundError = Class.new(Error)
|
4
3
|
FunctionAlreadyRegisteredError = Class.new(Error)
|
5
4
|
|
5
|
+
class FunctionNotFoundError < Error
|
6
|
+
def initialize(function, source = nil)
|
7
|
+
return super "No registered function #{source}[:#{function}]" if source
|
8
|
+
super "No globally registered function for #{function}"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
6
12
|
class MalformedInputError < Error
|
7
13
|
attr_reader :function, :value, :original_error
|
8
14
|
|
data/lib/transproc/function.rb
CHANGED
@@ -22,10 +22,18 @@ module Transproc
|
|
22
22
|
# @api private
|
23
23
|
attr_reader :args
|
24
24
|
|
25
|
+
# @!attribute [r] name
|
26
|
+
#
|
27
|
+
# @return [<type] The name of the function
|
28
|
+
#
|
29
|
+
# @api public
|
30
|
+
attr_reader :name
|
31
|
+
|
25
32
|
# @api private
|
26
|
-
def initialize(fn, options)
|
33
|
+
def initialize(fn, options = {})
|
27
34
|
@fn = fn
|
28
|
-
@args = options
|
35
|
+
@args = options.fetch(:args, [])
|
36
|
+
@name = options.fetch(:name, fn)
|
29
37
|
end
|
30
38
|
|
31
39
|
# Call the wrapped proc
|
@@ -38,7 +46,7 @@ module Transproc
|
|
38
46
|
def call(*value)
|
39
47
|
fn[*value, *args]
|
40
48
|
rescue => e
|
41
|
-
raise MalformedInputError.new(@
|
49
|
+
raise MalformedInputError.new(@name, value, e)
|
42
50
|
end
|
43
51
|
alias_method :[], :call
|
44
52
|
|
@@ -57,14 +65,29 @@ module Transproc
|
|
57
65
|
alias_method :+, :compose
|
58
66
|
alias_method :>>, :compose
|
59
67
|
|
68
|
+
# Return a new fn with curried args
|
69
|
+
#
|
70
|
+
# @return [Function]
|
71
|
+
#
|
72
|
+
# @api private
|
73
|
+
def with(*args)
|
74
|
+
self.class.new(fn, name: name, args: args)
|
75
|
+
end
|
76
|
+
|
77
|
+
# @api public
|
78
|
+
def ==(other)
|
79
|
+
return false unless other.instance_of?(self.class)
|
80
|
+
[fn, name, args] == [other.fn, other.name, other.args]
|
81
|
+
end
|
82
|
+
alias_method :eql?, :==
|
83
|
+
|
60
84
|
# Return a simple AST representation of this function
|
61
85
|
#
|
62
86
|
# @return [Array]
|
63
87
|
#
|
64
88
|
# @api public
|
65
89
|
def to_ast
|
66
|
-
|
67
|
-
[identifier, args]
|
90
|
+
[name, args]
|
68
91
|
end
|
69
92
|
end
|
70
93
|
end
|
data/lib/transproc/functions.rb
CHANGED
@@ -14,6 +14,11 @@ module Transproc
|
|
14
14
|
#
|
15
15
|
# @api public
|
16
16
|
module Functions
|
17
|
+
def self.extended(mod)
|
18
|
+
warn 'Transproc::Functions is deprecated please switch to Transproc::Registry'
|
19
|
+
super
|
20
|
+
end
|
21
|
+
|
17
22
|
def method_added(meth)
|
18
23
|
module_function meth
|
19
24
|
Transproc.register(meth, method(meth))
|
data/lib/transproc/hash.rb
CHANGED
@@ -15,7 +15,7 @@ module Transproc
|
|
15
15
|
#
|
16
16
|
# @api public
|
17
17
|
module HashTransformations
|
18
|
-
extend
|
18
|
+
extend Registry
|
19
19
|
|
20
20
|
# Map all keys in a hash with the provided transformation function
|
21
21
|
#
|
@@ -28,7 +28,7 @@ module Transproc
|
|
28
28
|
# @return [Hash]
|
29
29
|
#
|
30
30
|
# @api public
|
31
|
-
def map_keys(hash, fn)
|
31
|
+
def self.map_keys(hash, fn)
|
32
32
|
map_keys!(Hash[hash], fn)
|
33
33
|
end
|
34
34
|
|
@@ -37,7 +37,7 @@ module Transproc
|
|
37
37
|
# @see HashTransformations.map_keys
|
38
38
|
#
|
39
39
|
# @api public
|
40
|
-
def map_keys!(hash, fn)
|
40
|
+
def self.map_keys!(hash, fn)
|
41
41
|
hash.keys.each { |key| hash[fn[key]] = hash.delete(key) }
|
42
42
|
hash
|
43
43
|
end
|
@@ -53,7 +53,7 @@ module Transproc
|
|
53
53
|
# @return [Hash]
|
54
54
|
#
|
55
55
|
# @api public
|
56
|
-
def symbolize_keys(hash)
|
56
|
+
def self.symbolize_keys(hash)
|
57
57
|
symbolize_keys!(Hash[hash])
|
58
58
|
end
|
59
59
|
|
@@ -62,8 +62,38 @@ module Transproc
|
|
62
62
|
# @see HashTransformations.symbolize_keys!
|
63
63
|
#
|
64
64
|
# @api public
|
65
|
-
def symbolize_keys!(hash)
|
66
|
-
map_keys!(hash,
|
65
|
+
def self.symbolize_keys!(hash)
|
66
|
+
map_keys!(hash, Coercions[:to_symbol].fn)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Symbolize keys in a hash recursively
|
70
|
+
#
|
71
|
+
# @example
|
72
|
+
#
|
73
|
+
# input = { 'foo' => 'bar', 'baz' => [{ 'one' => 1 }] }
|
74
|
+
#
|
75
|
+
# t(:deep_symbolize_keys)[input]
|
76
|
+
# # => { :foo => "bar", :baz => [{ :one => 1 }] }
|
77
|
+
#
|
78
|
+
# @param [Hash]
|
79
|
+
#
|
80
|
+
# @return [Hash]
|
81
|
+
#
|
82
|
+
# @api public
|
83
|
+
def self.deep_symbolize_keys(hash)
|
84
|
+
hash.each_with_object({}) do |(key, value), output|
|
85
|
+
output[key.to_sym] =
|
86
|
+
case value
|
87
|
+
when Hash
|
88
|
+
deep_symbolize_keys(value)
|
89
|
+
when Array
|
90
|
+
value.map { |item|
|
91
|
+
item.is_a?(Hash) ? deep_symbolize_keys(item) : item
|
92
|
+
}
|
93
|
+
else
|
94
|
+
value
|
95
|
+
end
|
96
|
+
end
|
67
97
|
end
|
68
98
|
|
69
99
|
# Stringify all keys in a hash
|
@@ -77,7 +107,7 @@ module Transproc
|
|
77
107
|
# @return [Hash]
|
78
108
|
#
|
79
109
|
# @api public
|
80
|
-
def stringify_keys(hash)
|
110
|
+
def self.stringify_keys(hash)
|
81
111
|
stringify_keys!(Hash[hash])
|
82
112
|
end
|
83
113
|
|
@@ -86,8 +116,8 @@ module Transproc
|
|
86
116
|
# @see HashTransformations.stringify_keys
|
87
117
|
#
|
88
118
|
# @api public
|
89
|
-
def stringify_keys!(hash)
|
90
|
-
map_keys!(hash,
|
119
|
+
def self.stringify_keys!(hash)
|
120
|
+
map_keys!(hash, Coercions[:to_string].fn)
|
91
121
|
end
|
92
122
|
|
93
123
|
# Map all values in a hash using transformation function
|
@@ -101,7 +131,7 @@ module Transproc
|
|
101
131
|
# @return [Hash]
|
102
132
|
#
|
103
133
|
# @api public
|
104
|
-
def map_values(hash, fn)
|
134
|
+
def self.map_values(hash, fn)
|
105
135
|
map_values!(Hash[hash], fn)
|
106
136
|
end
|
107
137
|
|
@@ -114,7 +144,7 @@ module Transproc
|
|
114
144
|
# @return [Hash]
|
115
145
|
#
|
116
146
|
# @api public
|
117
|
-
def map_values!(hash, fn)
|
147
|
+
def self.map_values!(hash, fn)
|
118
148
|
hash.each { |key, value| hash[key] = fn[value] }
|
119
149
|
hash
|
120
150
|
end
|
@@ -131,7 +161,7 @@ module Transproc
|
|
131
161
|
# @return [Hash]
|
132
162
|
#
|
133
163
|
# @api public
|
134
|
-
def rename_keys(hash, mapping)
|
164
|
+
def self.rename_keys(hash, mapping)
|
135
165
|
rename_keys!(Hash[hash], mapping)
|
136
166
|
end
|
137
167
|
|
@@ -140,7 +170,7 @@ module Transproc
|
|
140
170
|
# @see HashTransformations.rename_keys
|
141
171
|
#
|
142
172
|
# @api public
|
143
|
-
def rename_keys!(hash, mapping)
|
173
|
+
def self.rename_keys!(hash, mapping)
|
144
174
|
mapping.each { |k, v| hash[v] = hash.delete(k) }
|
145
175
|
hash
|
146
176
|
end
|
@@ -157,7 +187,7 @@ module Transproc
|
|
157
187
|
# @return [Hash]
|
158
188
|
#
|
159
189
|
# @api public
|
160
|
-
def reject_keys(hash, keys)
|
190
|
+
def self.reject_keys(hash, keys)
|
161
191
|
reject_keys!(Hash[hash], keys)
|
162
192
|
end
|
163
193
|
|
@@ -166,7 +196,7 @@ module Transproc
|
|
166
196
|
# @see HashTransformations.reject_keys
|
167
197
|
#
|
168
198
|
# @api public
|
169
|
-
def reject_keys!(hash, keys)
|
199
|
+
def self.reject_keys!(hash, keys)
|
170
200
|
hash.reject { |k, _| keys.include?(k) }
|
171
201
|
end
|
172
202
|
|
@@ -182,7 +212,7 @@ module Transproc
|
|
182
212
|
# @return [Hash]
|
183
213
|
#
|
184
214
|
# @api public
|
185
|
-
def accept_keys(hash, keys)
|
215
|
+
def self.accept_keys(hash, keys)
|
186
216
|
accept_keys!(Hash[hash], keys)
|
187
217
|
end
|
188
218
|
|
@@ -191,7 +221,7 @@ module Transproc
|
|
191
221
|
# @see HashTransformations.accept
|
192
222
|
#
|
193
223
|
# @api public
|
194
|
-
def accept_keys!(hash, keys)
|
224
|
+
def self.accept_keys!(hash, keys)
|
195
225
|
reject_keys!(hash, hash.keys - keys)
|
196
226
|
end
|
197
227
|
|
@@ -206,7 +236,7 @@ module Transproc
|
|
206
236
|
# @return [Hash]
|
207
237
|
#
|
208
238
|
# @api public
|
209
|
-
def map_value(hash, key, fn)
|
239
|
+
def self.map_value(hash, key, fn)
|
210
240
|
hash.merge(key => fn[hash[key]])
|
211
241
|
end
|
212
242
|
|
@@ -215,7 +245,7 @@ module Transproc
|
|
215
245
|
# @see HashTransformations.map_value
|
216
246
|
#
|
217
247
|
# @api public
|
218
|
-
def map_value!(hash, key, fn)
|
248
|
+
def self.map_value!(hash, key, fn)
|
219
249
|
hash.update(key => fn[hash[key]])
|
220
250
|
end
|
221
251
|
|
@@ -230,7 +260,7 @@ module Transproc
|
|
230
260
|
# @return [Hash]
|
231
261
|
#
|
232
262
|
# @api public
|
233
|
-
def nest(hash, key, keys)
|
263
|
+
def self.nest(hash, key, keys)
|
234
264
|
nest!(Hash[hash], key, keys)
|
235
265
|
end
|
236
266
|
|
@@ -239,7 +269,7 @@ module Transproc
|
|
239
269
|
# @see HashTransformations.nest
|
240
270
|
#
|
241
271
|
# @api public
|
242
|
-
def nest!(hash, root, keys)
|
272
|
+
def self.nest!(hash, root, keys)
|
243
273
|
nest_keys = hash.keys & keys
|
244
274
|
|
245
275
|
if nest_keys.size > 0
|
@@ -263,7 +293,7 @@ module Transproc
|
|
263
293
|
# @return [Hash]
|
264
294
|
#
|
265
295
|
# @api public
|
266
|
-
def unwrap(hash, root, keys = nil)
|
296
|
+
def self.unwrap(hash, root, keys = nil)
|
267
297
|
copy = Hash[hash].merge(root => Hash[hash[root]])
|
268
298
|
unwrap!(copy, root, keys)
|
269
299
|
end
|
@@ -273,7 +303,7 @@ module Transproc
|
|
273
303
|
# @see HashTransformations.unwrap
|
274
304
|
#
|
275
305
|
# @api public
|
276
|
-
def unwrap!(hash, root, selected = nil)
|
306
|
+
def self.unwrap!(hash, root, selected = nil)
|
277
307
|
if nested_hash = hash[root]
|
278
308
|
keys = nested_hash.keys
|
279
309
|
keys &= selected if selected
|
@@ -303,7 +333,7 @@ module Transproc
|
|
303
333
|
# @return [Hash]
|
304
334
|
#
|
305
335
|
# @api public
|
306
|
-
def fold(hash, key, tuple_key)
|
336
|
+
def self.fold(hash, key, tuple_key)
|
307
337
|
fold!(Hash[hash], key, tuple_key)
|
308
338
|
end
|
309
339
|
|
@@ -312,7 +342,7 @@ module Transproc
|
|
312
342
|
# @see HashTransformations.fold
|
313
343
|
#
|
314
344
|
# @api public
|
315
|
-
def fold!(hash, key, tuple_key)
|
345
|
+
def self.fold!(hash, key, tuple_key)
|
316
346
|
hash.update(key => ArrayTransformations.extract_key(hash[key], tuple_key))
|
317
347
|
end
|
318
348
|
|
@@ -344,7 +374,7 @@ module Transproc
|
|
344
374
|
# @return [Array<Hash>]
|
345
375
|
#
|
346
376
|
# @api public
|
347
|
-
def split(hash, key, keys)
|
377
|
+
def self.split(hash, key, keys)
|
348
378
|
list = Array(hash[key])
|
349
379
|
return [hash.reject { |k, _| k == key }] if list.empty?
|
350
380
|
|
@@ -356,5 +386,51 @@ module Transproc
|
|
356
386
|
list = list.map { |item| item.merge(reject_keys(hash, [key])) }
|
357
387
|
ArrayTransformations.add_keys(list, ungrouped)
|
358
388
|
end
|
389
|
+
|
390
|
+
# Recursively evaluate hash values if they are procs/lambdas
|
391
|
+
#
|
392
|
+
# @example
|
393
|
+
# hash = {
|
394
|
+
# num: -> i { i + 1 },
|
395
|
+
# str: -> i { "num #{i}" }
|
396
|
+
# }
|
397
|
+
#
|
398
|
+
# t(:eval_values, 1)[hash]
|
399
|
+
# # => {:num => 2, :str => "num 1" }
|
400
|
+
#
|
401
|
+
# # with filters
|
402
|
+
# t(:eval_values, 1, [:str])[hash]
|
403
|
+
# # => {:num => #{still a proc}, :str => "num 1" }
|
404
|
+
#
|
405
|
+
# @param [Hash]
|
406
|
+
# @param [Array,Object] args Anything that should be passed to procs
|
407
|
+
# @param [Array] filters A list of attribute names that should be evaluated
|
408
|
+
#
|
409
|
+
# @api public
|
410
|
+
def self.eval_values(hash, args, filters = [])
|
411
|
+
hash.each_with_object({}) do |(key, value), output|
|
412
|
+
output[key] =
|
413
|
+
case value
|
414
|
+
when Proc
|
415
|
+
if filters.empty? || filters.include?(key)
|
416
|
+
value.call(*args)
|
417
|
+
else
|
418
|
+
value
|
419
|
+
end
|
420
|
+
when Hash
|
421
|
+
eval_values(value, args, filters)
|
422
|
+
when Array
|
423
|
+
value.map { |item|
|
424
|
+
item.is_a?(Hash) ? eval_values(item, args, filters) : item
|
425
|
+
}
|
426
|
+
else
|
427
|
+
value
|
428
|
+
end
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
432
|
+
# @deprecated Register methods globally
|
433
|
+
(methods - Registry.instance_methods - Registry.methods)
|
434
|
+
.each { |name| Transproc.register name, t(name) }
|
359
435
|
end
|
360
436
|
end
|