transproc 1.0.0 → 1.1.1
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 +5 -5
- data/.codeclimate.yml +15 -0
- data/.gitignore +3 -0
- data/.travis.yml +14 -10
- data/CHANGELOG.md +61 -17
- data/Gemfile +9 -18
- data/README.md +17 -19
- data/Rakefile +1 -0
- data/lib/transproc.rb +3 -4
- data/lib/transproc/all.rb +2 -0
- data/lib/transproc/array.rb +9 -51
- data/lib/transproc/array/combine.rb +61 -0
- data/lib/transproc/class.rb +2 -0
- data/lib/transproc/coercions.rb +2 -0
- data/lib/transproc/compiler.rb +45 -0
- data/lib/transproc/composer.rb +2 -0
- data/lib/transproc/composite.rb +2 -0
- data/lib/transproc/conditional.rb +2 -0
- data/lib/transproc/constants.rb +5 -0
- data/lib/transproc/error.rb +2 -0
- data/lib/transproc/function.rb +4 -1
- data/lib/transproc/functions.rb +2 -0
- data/lib/transproc/hash.rb +105 -45
- data/lib/transproc/proc.rb +2 -0
- data/lib/transproc/recursion.rb +2 -0
- data/lib/transproc/registry.rb +4 -2
- data/lib/transproc/store.rb +1 -0
- data/lib/transproc/support/deprecations.rb +2 -0
- data/lib/transproc/transformer.rb +7 -1
- data/lib/transproc/transformer/class_interface.rb +32 -65
- data/lib/transproc/transformer/deprecated/class_interface.rb +81 -0
- data/lib/transproc/transformer/dsl.rb +51 -0
- data/lib/transproc/version.rb +3 -1
- data/spec/spec_helper.rb +21 -10
- data/spec/unit/array/combine_spec.rb +224 -0
- data/spec/unit/array_transformations_spec.rb +1 -52
- data/spec/unit/class_transformations_spec.rb +10 -7
- data/spec/unit/coercions_spec.rb +1 -1
- data/spec/unit/composer_spec.rb +1 -1
- data/spec/unit/conditional_spec.rb +1 -1
- data/spec/unit/function_not_found_error_spec.rb +1 -1
- data/spec/unit/function_spec.rb +16 -1
- data/spec/unit/hash_transformations_spec.rb +12 -1
- data/spec/unit/proc_transformations_spec.rb +3 -1
- data/spec/unit/recursion_spec.rb +1 -1
- data/spec/unit/registry_spec.rb +9 -1
- data/spec/unit/store_spec.rb +2 -1
- data/spec/unit/transformer/class_interface_spec.rb +364 -0
- data/spec/unit/transformer/dsl_spec.rb +15 -0
- data/spec/unit/transformer/instance_methods_spec.rb +25 -0
- data/spec/unit/transformer_spec.rb +128 -40
- data/spec/unit/transproc_spec.rb +1 -1
- data/transproc.gemspec +1 -5
- metadata +19 -54
- data/.rubocop.yml +0 -66
- data/.rubocop_todo.yml +0 -11
- data/rakelib/mutant.rake +0 -16
- data/rakelib/rubocop.rake +0 -18
- data/spec/support/mutant.rb +0 -10
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Transproc
|
4
|
+
module ArrayTransformations
|
5
|
+
class Combine
|
6
|
+
EMPTY_ARRAY = [].freeze
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def combine(array, mappings)
|
10
|
+
root, nodes = array
|
11
|
+
return EMPTY_ARRAY if root.nil?
|
12
|
+
return root if nodes.nil?
|
13
|
+
groups = group_nodes(nodes, mappings)
|
14
|
+
|
15
|
+
root.map do |element|
|
16
|
+
element.dup.tap { |copy| add_groups_to_element(copy, groups, mappings) }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def add_groups_to_element(element, groups, mappings)
|
23
|
+
groups.each_with_index do |candidates, index|
|
24
|
+
mapping = mappings[index]
|
25
|
+
resource_key = mapping[0]
|
26
|
+
element[resource_key] = element_candidates(element, candidates, mapping[1].keys)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def element_candidates(element, candidates, keys)
|
31
|
+
candidates[element_candidates_key(element, keys)] || EMPTY_ARRAY
|
32
|
+
end
|
33
|
+
|
34
|
+
def group_nodes(nodes, mappings)
|
35
|
+
nodes.each_with_index.map do |candidates, index|
|
36
|
+
mapping = mappings[index]
|
37
|
+
group_candidates(candidates, mapping)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def group_candidates(candidates, mapping)
|
42
|
+
nested_mapping = mapping[2]
|
43
|
+
candidates = combine(candidates, nested_mapping) unless nested_mapping.nil?
|
44
|
+
group_candidates_by_keys(candidates, mapping[1].values)
|
45
|
+
end
|
46
|
+
|
47
|
+
def group_candidates_by_keys(candidates, keys)
|
48
|
+
return candidates.group_by { |a| a.values_at(*keys) } if keys.size > 1
|
49
|
+
|
50
|
+
key = keys.first
|
51
|
+
candidates.group_by { |a| a[key] }
|
52
|
+
end
|
53
|
+
|
54
|
+
def element_candidates_key(element, keys)
|
55
|
+
return element.values_at(*keys) if keys.size > 1
|
56
|
+
element[keys.first]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/lib/transproc/class.rb
CHANGED
data/lib/transproc/coercions.rb
CHANGED
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Transproc
|
4
|
+
# @api private
|
5
|
+
class Compiler
|
6
|
+
InvalidFunctionNameError = Class.new(StandardError)
|
7
|
+
|
8
|
+
attr_reader :registry, :transformer
|
9
|
+
|
10
|
+
def initialize(registry, transformer = nil)
|
11
|
+
@registry = registry
|
12
|
+
@transformer = transformer
|
13
|
+
end
|
14
|
+
|
15
|
+
def call(ast)
|
16
|
+
ast.map(&method(:visit)).reduce(:>>)
|
17
|
+
end
|
18
|
+
|
19
|
+
def visit(node)
|
20
|
+
id, *rest = node
|
21
|
+
public_send(:"visit_#{id}", *rest)
|
22
|
+
end
|
23
|
+
|
24
|
+
def visit_fn(node)
|
25
|
+
name, rest = node
|
26
|
+
args = rest.map { |arg| visit(arg) }
|
27
|
+
|
28
|
+
if registry.contain?(name)
|
29
|
+
registry[name, *args]
|
30
|
+
elsif transformer.respond_to?(name)
|
31
|
+
Function.new(transformer.method(name), name: name, args: args)
|
32
|
+
else
|
33
|
+
raise InvalidFunctionNameError, "function name +#{name}+ is not valid"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def visit_arg(arg)
|
38
|
+
arg
|
39
|
+
end
|
40
|
+
|
41
|
+
def visit_t(node)
|
42
|
+
call(node)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/transproc/composer.rb
CHANGED
data/lib/transproc/composite.rb
CHANGED
data/lib/transproc/error.rb
CHANGED
data/lib/transproc/function.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'transproc/composite'
|
2
4
|
|
3
5
|
module Transproc
|
@@ -85,7 +87,8 @@ module Transproc
|
|
85
87
|
#
|
86
88
|
# @api public
|
87
89
|
def to_ast
|
88
|
-
|
90
|
+
args_ast = args.map { |arg| arg.respond_to?(:to_ast) ? arg.to_ast : arg }
|
91
|
+
[name, args_ast]
|
89
92
|
end
|
90
93
|
|
91
94
|
# Converts a transproc to a simple proc
|
data/lib/transproc/functions.rb
CHANGED
data/lib/transproc/hash.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'transproc/coercions'
|
2
4
|
|
3
5
|
module Transproc
|
@@ -17,20 +19,28 @@ module Transproc
|
|
17
19
|
module HashTransformations
|
18
20
|
extend Registry
|
19
21
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
22
|
+
EMPTY_HASH = {}.freeze
|
23
|
+
|
24
|
+
if RUBY_VERSION >= '2.5'
|
25
|
+
# Map all keys in a hash with the provided transformation function
|
26
|
+
#
|
27
|
+
# @example
|
28
|
+
# Transproc(:map_keys, -> s { s.upcase })['name' => 'Jane']
|
29
|
+
# # => {"NAME" => "Jane"}
|
30
|
+
#
|
31
|
+
# @param [Hash]
|
32
|
+
#
|
33
|
+
# @return [Hash]
|
34
|
+
#
|
35
|
+
# @api public
|
36
|
+
def self.map_keys(source_hash, fn)
|
37
|
+
Hash[source_hash].transform_keys!(&fn)
|
38
|
+
end
|
39
|
+
else
|
40
|
+
def self.map_keys(source_hash, fn)
|
41
|
+
Hash[source_hash].tap do |hash|
|
42
|
+
hash.keys.each { |key| hash[fn[key]] = hash.delete(key) }
|
43
|
+
end
|
34
44
|
end
|
35
45
|
end
|
36
46
|
|
@@ -94,20 +104,55 @@ module Transproc
|
|
94
104
|
map_keys(hash, Coercions[:to_string].fn)
|
95
105
|
end
|
96
106
|
|
97
|
-
#
|
107
|
+
# Stringify keys in a hash recursively
|
98
108
|
#
|
99
109
|
# @example
|
100
|
-
#
|
101
|
-
#
|
110
|
+
# input = { :foo => "bar", :baz => [{ :one => 1 }] }
|
111
|
+
#
|
112
|
+
# t(:deep_stringify_keys)[input]
|
113
|
+
# # => { "foo" => "bar", "baz" => [{ "one" => 1 }] }
|
102
114
|
#
|
103
115
|
# @param [Hash]
|
104
116
|
#
|
105
117
|
# @return [Hash]
|
106
118
|
#
|
107
119
|
# @api public
|
108
|
-
def self.
|
109
|
-
|
110
|
-
|
120
|
+
def self.deep_stringify_keys(hash)
|
121
|
+
hash.each_with_object({}) do |(key, value), output|
|
122
|
+
output[key.to_s] =
|
123
|
+
case value
|
124
|
+
when Hash
|
125
|
+
deep_stringify_keys(value)
|
126
|
+
when Array
|
127
|
+
value.map { |item|
|
128
|
+
item.is_a?(Hash) ? deep_stringify_keys(item) : item
|
129
|
+
}
|
130
|
+
else
|
131
|
+
value
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
if RUBY_VERSION >= '2.4'
|
137
|
+
# Map all values in a hash using transformation function
|
138
|
+
#
|
139
|
+
# @example
|
140
|
+
# Transproc(:map_values, -> v { v.upcase })[:name => 'Jane']
|
141
|
+
# # => {"name" => "JANE"}
|
142
|
+
#
|
143
|
+
# @param [Hash]
|
144
|
+
#
|
145
|
+
# @return [Hash]
|
146
|
+
#
|
147
|
+
# @api public
|
148
|
+
def self.map_values(source_hash, fn)
|
149
|
+
Hash[source_hash].transform_values!(&fn)
|
150
|
+
end
|
151
|
+
else
|
152
|
+
def self.map_values(source_hash, fn)
|
153
|
+
Hash[source_hash].tap do |hash|
|
154
|
+
hash.each { |key, value| hash[key] = fn[value] }
|
155
|
+
end
|
111
156
|
end
|
112
157
|
end
|
113
158
|
|
@@ -167,20 +212,26 @@ module Transproc
|
|
167
212
|
Hash[hash].reject { |k, _| keys.include?(k) }
|
168
213
|
end
|
169
214
|
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
215
|
+
if RUBY_VERSION >= '2.5'
|
216
|
+
# Accepts specified keys from a hash
|
217
|
+
#
|
218
|
+
# @example
|
219
|
+
# Transproc(:accept_keys, [:name])[name: 'Jane', email: 'jane@doe.org']
|
220
|
+
# # => {:name=>"Jane"}
|
221
|
+
#
|
222
|
+
# @param [Hash] hash The input hash
|
223
|
+
# @param [Array] keys The keys to be accepted
|
224
|
+
#
|
225
|
+
# @return [Hash]
|
226
|
+
#
|
227
|
+
# @api public
|
228
|
+
def self.accept_keys(hash, keys)
|
229
|
+
Hash[hash].slice(*keys)
|
230
|
+
end
|
231
|
+
else
|
232
|
+
def self.accept_keys(hash, keys)
|
233
|
+
reject_keys(hash, hash.keys - keys)
|
234
|
+
end
|
184
235
|
end
|
185
236
|
|
186
237
|
# Map a key in a hash with the provided transformation function
|
@@ -209,18 +260,26 @@ module Transproc
|
|
209
260
|
# @return [Hash]
|
210
261
|
#
|
211
262
|
# @api public
|
212
|
-
def self.nest(
|
213
|
-
|
263
|
+
def self.nest(hash, root, keys)
|
264
|
+
child = {}
|
214
265
|
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
266
|
+
keys.each do |key|
|
267
|
+
child[key] = hash[key] if hash.key?(key)
|
268
|
+
end
|
269
|
+
|
270
|
+
output = Hash[hash]
|
271
|
+
|
272
|
+
child.each_key { |key| output.delete(key) }
|
273
|
+
|
274
|
+
old_root = hash[root]
|
275
|
+
|
276
|
+
if old_root.is_a?(Hash)
|
277
|
+
output[root] = old_root.merge(child)
|
221
278
|
else
|
222
|
-
|
279
|
+
output[root] = child
|
223
280
|
end
|
281
|
+
|
282
|
+
output
|
224
283
|
end
|
225
284
|
|
226
285
|
# Collapse a nested hash from a specified key
|
@@ -239,8 +298,9 @@ module Transproc
|
|
239
298
|
# @return [Hash]
|
240
299
|
#
|
241
300
|
# @api public
|
242
|
-
def self.unwrap(source_hash, root, selected = nil,
|
301
|
+
def self.unwrap(source_hash, root, selected = nil, options = EMPTY_HASH)
|
243
302
|
return source_hash unless source_hash[root]
|
303
|
+
options, selected = selected, nil if options.empty? && selected.is_a?(::Hash)
|
244
304
|
|
245
305
|
add_prefix = ->(key) do
|
246
306
|
combined = [root, key].join('_')
|
@@ -251,7 +311,7 @@ module Transproc
|
|
251
311
|
nested_hash = hash[root]
|
252
312
|
keys = nested_hash.keys
|
253
313
|
keys &= selected if selected
|
254
|
-
new_keys = prefix ? keys.map(&add_prefix) : keys
|
314
|
+
new_keys = options[:prefix] ? keys.map(&add_prefix) : keys
|
255
315
|
|
256
316
|
hash.update(Hash[new_keys.zip(keys.map { |key| nested_hash.delete(key) })])
|
257
317
|
hash.delete(root) if nested_hash.empty?
|
data/lib/transproc/proc.rb
CHANGED
data/lib/transproc/recursion.rb
CHANGED
data/lib/transproc/registry.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
module Transproc
|
4
5
|
# Container to define transproc functions in, and access them via `[]` method
|
@@ -46,8 +47,9 @@ module Transproc
|
|
46
47
|
#
|
47
48
|
def [](fn, *args)
|
48
49
|
fetched = fetch(fn)
|
49
|
-
|
50
|
-
Function.new(fetched, args: args, name: fn)
|
50
|
+
|
51
|
+
return Function.new(fetched, args: args, name: fn) unless already_wrapped?(fetched)
|
52
|
+
args.empty? ? fetched : fetched.with(*args)
|
51
53
|
end
|
52
54
|
alias_method :t, :[]
|
53
55
|
|
data/lib/transproc/store.rb
CHANGED
@@ -1,4 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'transproc/transformer/class_interface'
|
4
|
+
require 'transproc/transformer/deprecated/class_interface'
|
2
5
|
|
3
6
|
module Transproc
|
4
7
|
# Transfomer class for defining transprocs with a class DSL.
|
@@ -45,6 +48,9 @@ module Transproc
|
|
45
48
|
# @api public
|
46
49
|
class Transformer
|
47
50
|
extend ClassInterface
|
51
|
+
extend Deprecated::ClassInterface
|
52
|
+
|
53
|
+
attr_reader :transproc
|
48
54
|
|
49
55
|
# Execute the transformation pipeline with the given input.
|
50
56
|
#
|
@@ -63,7 +69,7 @@ module Transproc
|
|
63
69
|
#
|
64
70
|
# @api public
|
65
71
|
def call(input)
|
66
|
-
|
72
|
+
transproc.call(input)
|
67
73
|
end
|
68
74
|
end
|
69
75
|
end
|