transproc 0.2.3 → 0.2.4
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 +14 -0
- data/Gemfile +5 -2
- data/lib/transproc.rb +3 -2
- data/lib/transproc/array.rb +9 -5
- data/lib/transproc/coercions.rb +20 -0
- data/lib/transproc/error.rb +4 -3
- data/lib/transproc/function.rb +5 -5
- data/lib/transproc/hash.rb +7 -5
- data/lib/transproc/recursion.rb +42 -2
- data/lib/transproc/registry.rb +126 -0
- data/lib/transproc/version.rb +1 -1
- data/spec/spec_helper.rb +3 -0
- data/spec/support/mutant.rb +10 -0
- data/spec/unit/array_transformations_spec.rb +83 -3
- data/spec/unit/coercions_spec.rb +34 -0
- data/spec/unit/hash_transformations_spec.rb +22 -0
- data/spec/unit/recursion_spec.rb +52 -0
- data/spec/unit/registry_spec.rb +95 -0
- data/spec/unit/transproc_spec.rb +9 -5
- data/transproc.gemspec +1 -1
- metadata +9 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 18747dd5f157b683c7f3fc9200c7a7eb084cb270
|
4
|
+
data.tar.gz: 6961af7f27c18df6f9da0e3b0cbf839a003c839f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 51f5533988a1658e74988a7176936aac2d97925624720ac74dc5ca3ffc51651f61732947e645a3b65ed430992d442ae5aff4d8d35a81acc2bcaae45a7bdb9d77
|
7
|
+
data.tar.gz: 0b665c3972a08d45fed440025c3c986dfb00726615c16ed4e52c29ac9a86ca7e6e91d9cd8275f32829b638b21edea9a4ba8813b8e673f0839e9576d54d4cefa0
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
+
## v0.2.4 2015-06-20
|
2
|
+
|
3
|
+
### Added
|
4
|
+
|
5
|
+
* `Transproc::Registry` extension for registering reusable functions within modules (nepalez)
|
6
|
+
* `:recursion` recurse over an enumerable (AMHOL)
|
7
|
+
|
8
|
+
### Changed
|
9
|
+
|
10
|
+
* `:group`, `:nest`, and `:wrap` support adding new data to existing groups/wraps (nepalez)
|
11
|
+
* `Transproc::MalformedInputError` includes original backtrace now (solnic)
|
12
|
+
|
13
|
+
[Compare v0.2.3...v0.2.4](https://github.com/solnic/transproc/compare/v0.2.3...v0.2.4)
|
14
|
+
|
1
15
|
## v0.2.3 2015-06-02
|
2
16
|
|
3
17
|
### Added
|
data/Gemfile
CHANGED
@@ -5,9 +5,12 @@ gemspec
|
|
5
5
|
group :test do
|
6
6
|
gem 'equalizer'
|
7
7
|
gem 'anima'
|
8
|
-
gem 'mutant'
|
9
|
-
gem 'mutant-rspec'
|
10
8
|
gem 'codeclimate-test-reporter', require: nil
|
9
|
+
|
10
|
+
platform :mri do
|
11
|
+
gem 'mutant', github: 'mbj/mutant', branch: 'master'
|
12
|
+
gem 'mutant-rspec'
|
13
|
+
end
|
11
14
|
end
|
12
15
|
|
13
16
|
group :tools do
|
data/lib/transproc.rb
CHANGED
@@ -3,6 +3,7 @@ require 'transproc/function'
|
|
3
3
|
require 'transproc/functions'
|
4
4
|
require 'transproc/composer'
|
5
5
|
require 'transproc/error'
|
6
|
+
require 'transproc/registry'
|
6
7
|
|
7
8
|
module Transproc
|
8
9
|
module_function
|
@@ -21,7 +22,7 @@ module Transproc
|
|
21
22
|
def register(*args, &block)
|
22
23
|
name, fn = *args
|
23
24
|
if functions.include?(name)
|
24
|
-
raise FunctionAlreadyRegisteredError, "
|
25
|
+
raise FunctionAlreadyRegisteredError, "Function #{name} is already defined"
|
25
26
|
end
|
26
27
|
functions[name] = fn || block
|
27
28
|
end
|
@@ -33,7 +34,7 @@ module Transproc
|
|
33
34
|
# @api private
|
34
35
|
def [](name)
|
35
36
|
functions.fetch(name) {
|
36
|
-
raise FunctionNotFoundError, "
|
37
|
+
raise FunctionNotFoundError, "No registered function for #{name}"
|
37
38
|
}
|
38
39
|
end
|
39
40
|
|
data/lib/transproc/array.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'transproc/coercions'
|
2
|
+
|
1
3
|
module Transproc
|
2
4
|
# Transformation functions for Array objects
|
3
5
|
#
|
@@ -87,13 +89,15 @@ module Transproc
|
|
87
89
|
def group(array, key, keys)
|
88
90
|
grouped = Hash.new { |h, k| h[k] = [] }
|
89
91
|
array.each do |hash|
|
90
|
-
hash = hash
|
91
|
-
|
92
|
-
|
93
|
-
|
92
|
+
hash = Hash[hash]
|
93
|
+
|
94
|
+
old_group = Transproc::Coercions.to_tuples(hash.delete(key))
|
95
|
+
new_group = keys.inject({}) { |a, e| a.merge(e => hash.delete(e)) }
|
96
|
+
|
97
|
+
grouped[hash] << old_group.map { |item| item.merge(new_group) }
|
94
98
|
end
|
95
99
|
grouped.map do |root, children|
|
96
|
-
root.merge(key => children)
|
100
|
+
root.merge(key => children.flatten)
|
97
101
|
end
|
98
102
|
end
|
99
103
|
|
data/lib/transproc/coercions.rb
CHANGED
@@ -153,5 +153,25 @@ module Transproc
|
|
153
153
|
def to_datetime(value)
|
154
154
|
DateTime.parse(value)
|
155
155
|
end
|
156
|
+
|
157
|
+
# Coerce value into an array containing tuples only
|
158
|
+
#
|
159
|
+
# If the source is not an array, or doesn't contain a tuple, returns
|
160
|
+
# an array with one empty tuple
|
161
|
+
#
|
162
|
+
# @example
|
163
|
+
# Transproc(:to_tuples)[:foo] # => [{}]
|
164
|
+
# Transproc(:to_tuples)[[]] # => [{}]
|
165
|
+
# Transproc(:to_tuples)[[{ foo: :FOO, :bar }]] # => [{ foo: :FOO }]
|
166
|
+
#
|
167
|
+
# @param [Object] value
|
168
|
+
#
|
169
|
+
# @return [Array<Hash>]
|
170
|
+
#
|
171
|
+
def to_tuples(value)
|
172
|
+
array = value.is_a?(Array) ? Array[*value] : [{}]
|
173
|
+
array.select! { |item| item.is_a?(Hash) }
|
174
|
+
array.any? ? array : [{}]
|
175
|
+
end
|
156
176
|
end
|
157
177
|
end
|
data/lib/transproc/error.rb
CHANGED
@@ -4,13 +4,14 @@ module Transproc
|
|
4
4
|
FunctionAlreadyRegisteredError = Class.new(Error)
|
5
5
|
|
6
6
|
class MalformedInputError < Error
|
7
|
+
attr_reader :function, :value, :original_error
|
8
|
+
|
7
9
|
def initialize(function, value, error)
|
8
10
|
@function = function
|
9
11
|
@value = value
|
10
12
|
@original_error = error
|
11
|
-
super
|
13
|
+
super "Failed to call_function #{function} with #{value.inspect} - #{error}"
|
14
|
+
set_backtrace(error.backtrace)
|
12
15
|
end
|
13
|
-
|
14
|
-
attr_reader :function, :value, :original_error
|
15
16
|
end
|
16
17
|
end
|
data/lib/transproc/function.rb
CHANGED
@@ -23,9 +23,9 @@ module Transproc
|
|
23
23
|
attr_reader :args
|
24
24
|
|
25
25
|
# @api private
|
26
|
-
def initialize(fn, options
|
26
|
+
def initialize(fn, options)
|
27
27
|
@fn = fn
|
28
|
-
@args = options
|
28
|
+
@args = options[:args]
|
29
29
|
end
|
30
30
|
|
31
31
|
# Call the wrapped proc
|
@@ -37,8 +37,8 @@ module Transproc
|
|
37
37
|
# @api public
|
38
38
|
def call(*value)
|
39
39
|
fn[*value, *args]
|
40
|
-
rescue =>
|
41
|
-
raise MalformedInputError.new(@fn, value,
|
40
|
+
rescue => e
|
41
|
+
raise MalformedInputError.new(@fn, value, e)
|
42
42
|
end
|
43
43
|
alias_method :[], :call
|
44
44
|
|
@@ -63,7 +63,7 @@ module Transproc
|
|
63
63
|
#
|
64
64
|
# @api public
|
65
65
|
def to_ast
|
66
|
-
identifier = fn.
|
66
|
+
identifier = fn.instance_of?(Proc) ? fn : fn.name
|
67
67
|
[identifier, args]
|
68
68
|
end
|
69
69
|
end
|
data/lib/transproc/hash.rb
CHANGED
@@ -174,7 +174,7 @@ module Transproc
|
|
174
174
|
#
|
175
175
|
# @example
|
176
176
|
# Transproc(:accept_keys, [:name])[name: 'Jane', email: 'jane@doe.org']
|
177
|
-
# # => {:
|
177
|
+
# # => {:name=>"Jane"}
|
178
178
|
#
|
179
179
|
# @param [Hash] hash The input hash
|
180
180
|
# @param [Array] keys The keys to be accepted
|
@@ -244,7 +244,9 @@ module Transproc
|
|
244
244
|
|
245
245
|
if nest_keys.size > 0
|
246
246
|
child = Hash[nest_keys.zip(nest_keys.map { |key| hash.delete(key) })]
|
247
|
-
hash
|
247
|
+
old_nest = hash[root]
|
248
|
+
new_nest = old_nest.is_a?(Hash) ? old_nest.merge(child) : child
|
249
|
+
hash.update(root => new_nest)
|
248
250
|
else
|
249
251
|
hash.update(root => {})
|
250
252
|
end
|
@@ -302,7 +304,7 @@ module Transproc
|
|
302
304
|
#
|
303
305
|
# @api public
|
304
306
|
def fold(hash, key, tuple_key)
|
305
|
-
fold!(hash
|
307
|
+
fold!(Hash[hash], key, tuple_key)
|
306
308
|
end
|
307
309
|
|
308
310
|
# Same as `:fold` but mutates the hash
|
@@ -311,7 +313,7 @@ module Transproc
|
|
311
313
|
#
|
312
314
|
# @api public
|
313
315
|
def fold!(hash, key, tuple_key)
|
314
|
-
hash.
|
316
|
+
hash.update(key => ArrayTransformations.extract_key(hash[key], tuple_key))
|
315
317
|
end
|
316
318
|
|
317
319
|
# Splits hash to array by all values from a specified key
|
@@ -328,7 +330,7 @@ module Transproc
|
|
328
330
|
# { title: 'be cool' }
|
329
331
|
# ]
|
330
332
|
# }
|
331
|
-
# Transproc(:split, :tasks, [:priority])
|
333
|
+
# Transproc(:split, :tasks, [:priority])[input]
|
332
334
|
# => [
|
333
335
|
# { name: 'Joe', priority: 1, tasks: [{ title: 'sleep well' }] },
|
334
336
|
# { name: 'Joe', priority: 2, tasks: [{ title: 'be nice' }, { title: nil }] },
|
data/lib/transproc/recursion.rb
CHANGED
@@ -17,10 +17,50 @@ module Transproc
|
|
17
17
|
module Recursion
|
18
18
|
extend Functions
|
19
19
|
|
20
|
+
IF_ENUMERABLE = -> fn { Transproc(:is, Enumerable, fn) }
|
21
|
+
|
20
22
|
IF_ARRAY = -> fn { Transproc(:is, Array, fn) }
|
21
23
|
|
22
24
|
IF_HASH = -> fn { Transproc(:is, Hash, fn) }
|
23
25
|
|
26
|
+
# Recursively apply the provided transformation function to an enumerable
|
27
|
+
#
|
28
|
+
# @example
|
29
|
+
# Transproc(:recursion, Transproc(:is, ::Hash, Transproc(:symbolize_keys)))[
|
30
|
+
# {
|
31
|
+
# 'id' => 1,
|
32
|
+
# 'name' => 'Jane',
|
33
|
+
# 'tasks' => [
|
34
|
+
# { 'id' => 1, 'description' => 'Write some code' },
|
35
|
+
# { 'id' => 2, 'description' => 'Write some more code' }
|
36
|
+
# ]
|
37
|
+
# }
|
38
|
+
# ]
|
39
|
+
# => {:id=>1, :name=>"Jane", :tasks=>[{:id=>1, :description=>"Write some code"}, {:id=>2, :description=>"Write some more code"}]}
|
40
|
+
#
|
41
|
+
# @param [Enumerable]
|
42
|
+
#
|
43
|
+
# @return [Enumerable]
|
44
|
+
#
|
45
|
+
# @api public
|
46
|
+
def recursion(value, fn)
|
47
|
+
result = fn[value]
|
48
|
+
guarded = IF_ENUMERABLE[-> v { recursion(v, fn) }]
|
49
|
+
|
50
|
+
case result
|
51
|
+
when ::Hash
|
52
|
+
result.keys.each do |key|
|
53
|
+
result[key] = guarded[result.delete(key)]
|
54
|
+
end
|
55
|
+
when ::Array
|
56
|
+
result.map! do |item|
|
57
|
+
guarded[item]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
result
|
62
|
+
end
|
63
|
+
|
24
64
|
# Recursively apply the provided transformation function to an array
|
25
65
|
#
|
26
66
|
# @example
|
@@ -36,7 +76,7 @@ module Transproc
|
|
36
76
|
# @api public
|
37
77
|
def array_recursion(value, fn)
|
38
78
|
result = fn[value]
|
39
|
-
guarded = IF_ARRAY[-> v {
|
79
|
+
guarded = IF_ARRAY[-> v { array_recursion(v, fn) }]
|
40
80
|
|
41
81
|
result.map! do |item|
|
42
82
|
guarded[item]
|
@@ -58,7 +98,7 @@ module Transproc
|
|
58
98
|
# @api public
|
59
99
|
def hash_recursion(value, fn)
|
60
100
|
result = fn[value]
|
61
|
-
guarded = IF_HASH[-> v {
|
101
|
+
guarded = IF_HASH[-> v { hash_recursion(v, fn) }]
|
62
102
|
|
63
103
|
result.keys.each do |key|
|
64
104
|
result[key] = guarded[result.delete(key)]
|
@@ -0,0 +1,126 @@
|
|
1
|
+
module Transproc
|
2
|
+
# Container to define transproc functions in, and access them via `[]` method
|
3
|
+
# from the outside of the module
|
4
|
+
#
|
5
|
+
# @example
|
6
|
+
# module FooMethods
|
7
|
+
# extend Transproc::Registry
|
8
|
+
#
|
9
|
+
# def foo(name, prefix)
|
10
|
+
# [prefix, '_', name].join
|
11
|
+
# end
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# fn = FooMethods[:foo, 'baz']
|
15
|
+
# fn['qux'] # => 'qux_baz'
|
16
|
+
#
|
17
|
+
# module BarMethods
|
18
|
+
# # extend Transproc::Registry
|
19
|
+
# include FooMethods
|
20
|
+
#
|
21
|
+
# def bar(*args)
|
22
|
+
# foo(*args).upcase
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# fn = BarMethods[:foo, 'baz']
|
27
|
+
# fn['qux'] # => 'qux_baz'
|
28
|
+
#
|
29
|
+
# fn = BarMethods[:bar, 'baz']
|
30
|
+
# fn['qux'] # => 'QUX_BAZ'
|
31
|
+
#
|
32
|
+
# @api public
|
33
|
+
module Registry
|
34
|
+
# Builds the transproc function either from a Proc, or from the module method
|
35
|
+
#
|
36
|
+
# @param [Proc, Symbol] fn
|
37
|
+
# Either a proc, or a name of the module's function to be wrapped to transproc
|
38
|
+
# @param [Object, Array] args
|
39
|
+
# Args to be carried by the transproc
|
40
|
+
#
|
41
|
+
# @return [Transproc::Function]
|
42
|
+
#
|
43
|
+
# @alias :t
|
44
|
+
#
|
45
|
+
# @api public
|
46
|
+
def [](fn, *args)
|
47
|
+
fun = fn.is_a?(Proc) ? fn : method(fn).to_proc
|
48
|
+
Transproc::Function.new(fun, args: args)
|
49
|
+
end
|
50
|
+
alias_method :t, :[]
|
51
|
+
|
52
|
+
# Forwards the named method (transproc) to another module
|
53
|
+
#
|
54
|
+
# Allows using transprocs from other modules without including those
|
55
|
+
# modules as a whole
|
56
|
+
#
|
57
|
+
# @example
|
58
|
+
# module Foo
|
59
|
+
# extend Transproc::Registry
|
60
|
+
#
|
61
|
+
# def foo(value)
|
62
|
+
# value.upcase
|
63
|
+
# end
|
64
|
+
#
|
65
|
+
# def bar(value)
|
66
|
+
# value.downcase
|
67
|
+
# end
|
68
|
+
# end
|
69
|
+
#
|
70
|
+
# module Bar
|
71
|
+
# extend Transproc::Registry
|
72
|
+
#
|
73
|
+
# uses :foo, from: Foo, as: :baz
|
74
|
+
# uses :bar, from: Foo
|
75
|
+
# end
|
76
|
+
#
|
77
|
+
# Bar[:baz]['Qux'] # => 'QUX'
|
78
|
+
# Bar[:bar]['Qux'] # => 'qux'
|
79
|
+
#
|
80
|
+
# @param [String, Symbol] name
|
81
|
+
# @option [Class] :from The module to take the method from
|
82
|
+
# @option [String, Symbol] :as
|
83
|
+
# The name of imported transproc inside the current module
|
84
|
+
#
|
85
|
+
# @return [undefined]
|
86
|
+
#
|
87
|
+
# @api public
|
88
|
+
def uses(name, options = {})
|
89
|
+
source = options.fetch(:from)
|
90
|
+
new_name = options.fetch(:as, name)
|
91
|
+
define_method(new_name) { |*args| source.__send__(name, *args) }
|
92
|
+
end
|
93
|
+
|
94
|
+
# @api private
|
95
|
+
def self.extended(target)
|
96
|
+
target.extend(ClassMethods)
|
97
|
+
end
|
98
|
+
|
99
|
+
# @api private
|
100
|
+
module ClassMethods
|
101
|
+
# Makes `[]` and all functions defined in the included modules
|
102
|
+
# accessible in their receiver
|
103
|
+
#
|
104
|
+
# @api private
|
105
|
+
def included(other)
|
106
|
+
other.extend(Transproc::Registry, self)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Makes newly module-defined functions accessible via `[]` method
|
110
|
+
# by adding it to the module's eigenclass
|
111
|
+
#
|
112
|
+
# @api private
|
113
|
+
def method_added(name)
|
114
|
+
module_function(name)
|
115
|
+
end
|
116
|
+
|
117
|
+
# Makes undefined methods inaccessible via `[]` method by
|
118
|
+
# undefining it from the module's eigenclass
|
119
|
+
#
|
120
|
+
# @api private
|
121
|
+
def method_undefined(name)
|
122
|
+
singleton_class.__send__(:undef_method, name)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
data/lib/transproc/version.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -175,17 +175,69 @@ describe Transproc::ArrayTransformations do
|
|
175
175
|
|
176
176
|
expect(wrap[input]).to eql(output)
|
177
177
|
end
|
178
|
+
|
179
|
+
it 'adds data to the existing tuples' do
|
180
|
+
wrap = t(:wrap, :task, [:title])
|
181
|
+
|
182
|
+
input = [{ name: 'Jane', task: { priority: 1 }, title: 'One' }]
|
183
|
+
output = [{ name: 'Jane', task: { priority: 1, title: 'One' } }]
|
184
|
+
|
185
|
+
expect(wrap[input]).to eql(output)
|
186
|
+
end
|
178
187
|
end
|
179
188
|
|
180
189
|
describe '.group' do
|
181
|
-
|
182
|
-
group = t(:group, :tasks, [:title])
|
190
|
+
subject(:group) { t(:group, :tasks, [:title]) }
|
183
191
|
|
184
|
-
|
192
|
+
it 'returns a new array with grouped hashes' do
|
193
|
+
input = [{ name: 'Jane', title: 'One' }, { name: 'Jane', title: 'Two' }]
|
185
194
|
output = [{ name: 'Jane', tasks: [{ title: 'One' }, { title: 'Two' }] }]
|
186
195
|
|
187
196
|
expect(group[input]).to eql(output)
|
188
197
|
end
|
198
|
+
|
199
|
+
it 'updates the existing group' do
|
200
|
+
input = [
|
201
|
+
{
|
202
|
+
name: 'Jane',
|
203
|
+
title: 'One',
|
204
|
+
tasks: [{ type: 'one' }, { type: 'two' }]
|
205
|
+
},
|
206
|
+
{
|
207
|
+
name: 'Jane',
|
208
|
+
title: 'Two',
|
209
|
+
tasks: [{ type: 'one' }, { type: 'two' }]
|
210
|
+
}
|
211
|
+
]
|
212
|
+
output = [
|
213
|
+
{
|
214
|
+
name: 'Jane',
|
215
|
+
tasks: [
|
216
|
+
{ title: 'One', type: 'one' },
|
217
|
+
{ title: 'One', type: 'two' },
|
218
|
+
{ title: 'Two', type: 'one' },
|
219
|
+
{ title: 'Two', type: 'two' }
|
220
|
+
]
|
221
|
+
}
|
222
|
+
]
|
223
|
+
|
224
|
+
expect(group[input]).to eql(output)
|
225
|
+
end
|
226
|
+
|
227
|
+
it 'ingnores old values except for array of tuples' do
|
228
|
+
input = [
|
229
|
+
{ name: 'Jane', title: 'One', tasks: [{ priority: 1 }, :wrong] },
|
230
|
+
{ name: 'Jane', title: 'Two', tasks: :wrong }
|
231
|
+
]
|
232
|
+
output = [
|
233
|
+
{
|
234
|
+
name: 'Jane',
|
235
|
+
tasks: [{ title: 'One', priority: 1 }, { title: 'Two' }]
|
236
|
+
}
|
237
|
+
]
|
238
|
+
|
239
|
+
expect(group[input]).to eql(output)
|
240
|
+
end
|
189
241
|
end
|
190
242
|
|
191
243
|
describe '.ungroup' do
|
@@ -211,6 +263,34 @@ describe Transproc::ArrayTransformations do
|
|
211
263
|
|
212
264
|
expect(ungroup[input]).to eql(output)
|
213
265
|
end
|
266
|
+
|
267
|
+
it 'ungroups array partially' do
|
268
|
+
input = [
|
269
|
+
{
|
270
|
+
name: 'Jane',
|
271
|
+
tasks: [
|
272
|
+
{ title: 'One', type: 'one' },
|
273
|
+
{ title: 'One', type: 'two' },
|
274
|
+
{ title: 'Two', type: 'one' },
|
275
|
+
{ title: 'Two', type: 'two' }
|
276
|
+
]
|
277
|
+
}
|
278
|
+
]
|
279
|
+
output = [
|
280
|
+
{
|
281
|
+
name: 'Jane',
|
282
|
+
title: 'One',
|
283
|
+
tasks: [{ type: 'one' }, { type: 'two' }]
|
284
|
+
},
|
285
|
+
{
|
286
|
+
name: 'Jane',
|
287
|
+
title: 'Two',
|
288
|
+
tasks: [{ type: 'one' }, { type: 'two' }]
|
289
|
+
}
|
290
|
+
]
|
291
|
+
|
292
|
+
expect(ungroup[input]).to eql(output)
|
293
|
+
end
|
214
294
|
end
|
215
295
|
|
216
296
|
describe '.combine' do
|
data/spec/unit/coercions_spec.rb
CHANGED
@@ -79,4 +79,38 @@ describe Transproc::Coercions do
|
|
79
79
|
end
|
80
80
|
end
|
81
81
|
end
|
82
|
+
|
83
|
+
describe '.to_tuples' do
|
84
|
+
subject(:to_tuples) { t(:to_tuples) }
|
85
|
+
|
86
|
+
context 'non-array' do
|
87
|
+
let(:input) { :foo }
|
88
|
+
|
89
|
+
it 'returns an array with one blank tuple' do
|
90
|
+
output = [{}]
|
91
|
+
|
92
|
+
expect(to_tuples[input]).to eql(output)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
context 'empty array' do
|
97
|
+
let(:input) { [] }
|
98
|
+
|
99
|
+
it 'returns an array with one blank tuple' do
|
100
|
+
output = [{}]
|
101
|
+
|
102
|
+
expect(to_tuples[input]).to eql(output)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
context 'array of tuples' do
|
107
|
+
let(:input) { [:foo, { bar: :BAZ }, :qux] }
|
108
|
+
|
109
|
+
it 'returns an array with tuples only' do
|
110
|
+
output = [{ bar: :BAZ }]
|
111
|
+
|
112
|
+
expect(to_tuples[input]).to eql(output)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
82
116
|
end
|
@@ -172,6 +172,28 @@ describe Transproc::HashTransformations do
|
|
172
172
|
expect(input).to eql(output)
|
173
173
|
end
|
174
174
|
|
175
|
+
it 'returns new hash with keys nested under the existing key' do
|
176
|
+
nest = t(:nest!, :baz, ['two'])
|
177
|
+
|
178
|
+
input = { 'foo' => 'bar', baz: { 'one' => nil }, 'two' => false }
|
179
|
+
output = { 'foo' => 'bar', baz: { 'one' => nil, 'two' => false } }
|
180
|
+
|
181
|
+
nest[input]
|
182
|
+
|
183
|
+
expect(input).to eql(output)
|
184
|
+
end
|
185
|
+
|
186
|
+
it 'rewrites the existing key if its value is not a hash' do
|
187
|
+
nest = t(:nest!, :baz, ['two'])
|
188
|
+
|
189
|
+
input = { 'foo' => 'bar', baz: 'one', 'two' => false }
|
190
|
+
output = { 'foo' => 'bar', baz: { 'two' => false } }
|
191
|
+
|
192
|
+
nest[input]
|
193
|
+
|
194
|
+
expect(input).to eql(output)
|
195
|
+
end
|
196
|
+
|
175
197
|
it 'returns new hash with an empty hash under a new key when nest-keys are missing' do
|
176
198
|
nest = t(:nest!, :baz, ['foo'])
|
177
199
|
|
data/spec/unit/recursion_spec.rb
CHANGED
@@ -1,6 +1,58 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Transproc::Recursion do
|
4
|
+
describe '.recursion' do
|
5
|
+
let(:original) do
|
6
|
+
{
|
7
|
+
'foo' => 'bar',
|
8
|
+
'bar' => {
|
9
|
+
'foo' => 'bar',
|
10
|
+
'bar' => ['foo', 'bar', 'baz'],
|
11
|
+
'baz' => 'foo'
|
12
|
+
},
|
13
|
+
'baz' => 'bar'
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
let(:input) { original.dup }
|
18
|
+
|
19
|
+
let(:output) do
|
20
|
+
{
|
21
|
+
'foo' => 'bar',
|
22
|
+
'bar' => {
|
23
|
+
'foo' => 'bar',
|
24
|
+
'bar' => ['foo', 'bar'],
|
25
|
+
}
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
context 'when function is non-destructive' do
|
30
|
+
let(:map) do
|
31
|
+
t(:recursion, -> enum {
|
32
|
+
enum.reject { |v| v == 'baz' }
|
33
|
+
})
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'applies funtions to all items recursively' do
|
37
|
+
expect(map[input]).to eql(output)
|
38
|
+
expect(input).to eql(original)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'when function is destructive' do
|
43
|
+
let(:map) do
|
44
|
+
t(:recursion, -> enum {
|
45
|
+
enum.reject! { |v| v == 'baz' }
|
46
|
+
})
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'applies funtions to all items recursively and destructively' do
|
50
|
+
expect(map[input]).to eql(output)
|
51
|
+
expect(input).to eql(output)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
4
56
|
describe '.array_recursion' do
|
5
57
|
let(:original) do
|
6
58
|
[
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Transproc::Registry do
|
4
|
+
before do
|
5
|
+
module FooModule
|
6
|
+
extend Transproc::Registry
|
7
|
+
|
8
|
+
def foo(value, prefix)
|
9
|
+
[prefix, '_', value].join
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module BarModule
|
14
|
+
include FooModule
|
15
|
+
|
16
|
+
def bar(*args)
|
17
|
+
foo(*args).upcase
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
module BazModule
|
22
|
+
extend Transproc::Registry
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '.[]' do
|
27
|
+
it 'builds function from the method' do
|
28
|
+
fn = ::FooModule[:foo, 'baz']
|
29
|
+
|
30
|
+
expect(fn['qux']).to eql 'baz_qux'
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'builds function from the proc' do
|
34
|
+
fun = -> value, prefix { [prefix, '_', value].join }
|
35
|
+
fn = ::FooModule[fun, 'baz']
|
36
|
+
|
37
|
+
expect(fn['qux']).to eql 'baz_qux'
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'builds function using methods from included modules' do
|
41
|
+
fn = ::BarModule[:bar, 'baz']
|
42
|
+
|
43
|
+
expect(fn['qux']).to eql 'BAZ_QUX'
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'can access methods from included modules directly' do
|
47
|
+
fn = ::BarModule[:foo, 'baz']
|
48
|
+
|
49
|
+
expect(fn['qux']).to eql 'baz_qux'
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'cannot access undefined methods' do
|
53
|
+
module ::BarModule
|
54
|
+
undef_method :foo
|
55
|
+
end
|
56
|
+
|
57
|
+
expect { ::BarModule[:foo, 'baz'] }.to raise_error(NameError)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe '.uses' do
|
62
|
+
it 'forwards methods to another module directly' do
|
63
|
+
expect { ::BazModule[:baz, 'baz'] }.to raise_error(NameError)
|
64
|
+
|
65
|
+
module BazModule
|
66
|
+
uses :foo, as: :ffoo, from: FooModule
|
67
|
+
uses :bar, from: BarModule
|
68
|
+
end
|
69
|
+
|
70
|
+
ffoo = ::BazModule[:ffoo, 'baz']
|
71
|
+
bar = ::BazModule[:bar, 'baz']
|
72
|
+
|
73
|
+
expect(ffoo['qux']).to eql 'baz_qux'
|
74
|
+
expect(bar['qux']).to eql 'BAZ_QUX'
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe '#t' do
|
79
|
+
it 'is an alias for .[]' do
|
80
|
+
module FooModule
|
81
|
+
def qux(value, *args)
|
82
|
+
t(:foo, *args)[value]
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
fn = ::FooModule[:foo, 'baz']
|
87
|
+
|
88
|
+
expect(fn['qux']).to eql 'baz_qux'
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
after { Object.send :remove_const, :BazModule }
|
93
|
+
after { Object.send :remove_const, :BarModule }
|
94
|
+
after { Object.send :remove_const, :FooModule }
|
95
|
+
end
|
data/spec/unit/transproc_spec.rb
CHANGED
@@ -52,13 +52,17 @@ describe Transproc do
|
|
52
52
|
|
53
53
|
describe 'handling malformed input' do
|
54
54
|
it 'raises a Transproc::MalformedInputError' do
|
55
|
-
Transproc.register(:im_dangerous, ->() {
|
56
|
-
raise ArgumentError, 'sorry, you got some bad apples in your input'
|
57
|
-
})
|
58
|
-
|
59
55
|
expect {
|
60
|
-
Transproc(:
|
56
|
+
Transproc(:to_integer)[{}]
|
61
57
|
}.to raise_error(Transproc::MalformedInputError)
|
58
|
+
|
59
|
+
begin
|
60
|
+
Transproc(:to_integer)[{}]
|
61
|
+
rescue Transproc::MalformedInputError => e
|
62
|
+
expect(e.message).to include('to_integer')
|
63
|
+
expect(e.message).to include("undefined method `to_i'")
|
64
|
+
expect(e.backtrace).to eql(e.original_error.backtrace)
|
65
|
+
end
|
62
66
|
end
|
63
67
|
end
|
64
68
|
end
|
data/transproc.gemspec
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: transproc
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Piotr Solnica
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-06-
|
11
|
+
date: 2015-06-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -44,14 +44,14 @@ dependencies:
|
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '3.
|
47
|
+
version: '3.3'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '3.
|
54
|
+
version: '3.3'
|
55
55
|
description: Transform Ruby objects in functional style
|
56
56
|
email:
|
57
57
|
- piotr.solnica@gmail.com
|
@@ -83,10 +83,12 @@ files:
|
|
83
83
|
- lib/transproc/functions.rb
|
84
84
|
- lib/transproc/hash.rb
|
85
85
|
- lib/transproc/recursion.rb
|
86
|
+
- lib/transproc/registry.rb
|
86
87
|
- lib/transproc/version.rb
|
87
88
|
- rakelib/mutant.rake
|
88
89
|
- rakelib/rubocop.rake
|
89
90
|
- spec/spec_helper.rb
|
91
|
+
- spec/support/mutant.rb
|
90
92
|
- spec/unit/array_transformations_spec.rb
|
91
93
|
- spec/unit/class_transformations_spec.rb
|
92
94
|
- spec/unit/coercions_spec.rb
|
@@ -95,6 +97,7 @@ files:
|
|
95
97
|
- spec/unit/function_spec.rb
|
96
98
|
- spec/unit/hash_transformations_spec.rb
|
97
99
|
- spec/unit/recursion_spec.rb
|
100
|
+
- spec/unit/registry_spec.rb
|
98
101
|
- spec/unit/transproc_spec.rb
|
99
102
|
- transproc.gemspec
|
100
103
|
homepage: http://solnic.github.io/transproc/
|
@@ -123,6 +126,7 @@ specification_version: 4
|
|
123
126
|
summary: Transform Ruby objects in functional style
|
124
127
|
test_files:
|
125
128
|
- spec/spec_helper.rb
|
129
|
+
- spec/support/mutant.rb
|
126
130
|
- spec/unit/array_transformations_spec.rb
|
127
131
|
- spec/unit/class_transformations_spec.rb
|
128
132
|
- spec/unit/coercions_spec.rb
|
@@ -131,4 +135,5 @@ test_files:
|
|
131
135
|
- spec/unit/function_spec.rb
|
132
136
|
- spec/unit/hash_transformations_spec.rb
|
133
137
|
- spec/unit/recursion_spec.rb
|
138
|
+
- spec/unit/registry_spec.rb
|
134
139
|
- spec/unit/transproc_spec.rb
|