transproc 0.1.3 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +7 -0
- data/CHANGELOG.md +23 -1
- data/Gemfile +3 -0
- data/README.md +30 -12
- data/lib/transproc.rb +61 -6
- data/lib/transproc/all.rb +1 -0
- data/lib/transproc/array.rb +94 -19
- data/lib/transproc/coercions.rb +140 -28
- data/lib/transproc/composer.rb +43 -0
- data/lib/transproc/conditional.rb +56 -0
- data/lib/transproc/function.rb +95 -4
- data/lib/transproc/hash.rb +218 -41
- data/lib/transproc/recursion.rb +61 -20
- data/lib/transproc/version.rb +1 -1
- data/spec/integration/array_spec.rb +2 -2
- data/spec/integration/coercions_spec.rb +8 -2
- data/spec/integration/composer_spec.rb +1 -1
- data/spec/integration/conditional_spec.rb +23 -0
- data/spec/integration/function_spec.rb +56 -0
- data/spec/integration/hash_spec.rb +124 -52
- data/transproc.gemspec +2 -2
- metadata +10 -5
data/lib/transproc/recursion.rb
CHANGED
@@ -1,29 +1,70 @@
|
|
1
|
+
require 'transproc/conditional'
|
2
|
+
|
1
3
|
module Transproc
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
4
|
+
# Recursive transformation functions
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
# require 'transproc/recursion'
|
8
|
+
#
|
9
|
+
# include Transproc::Helper
|
10
|
+
#
|
11
|
+
# fn = t(:hash_recursion, t(:symbolize_keys))
|
12
|
+
#
|
13
|
+
# fn["name" => "Jane", "address" => { "street" => "Street 1", "zipcode" => "123" }]
|
14
|
+
# # => {:name=>"Jane", :address=>{:street=>"Street 1", :zipcode=>"123"}}
|
15
|
+
#
|
16
|
+
# @api public
|
17
|
+
module Recursion
|
18
|
+
extend Functions
|
19
|
+
|
20
|
+
IF_ARRAY = -> fn { Transproc(:is, Array, fn) }
|
13
21
|
|
14
|
-
|
15
|
-
result = fn[value]
|
22
|
+
IF_HASH = -> fn { Transproc(:is, Hash, fn) }
|
16
23
|
|
17
|
-
|
18
|
-
|
24
|
+
# Recursively apply the provided transformation function to an array
|
25
|
+
#
|
26
|
+
# @example
|
27
|
+
# Transproc(:array_recursion, -> s { s.compact })[
|
28
|
+
# [['Joe', 'Jane', nil], ['Smith', 'Doe', nil]]
|
29
|
+
# ]
|
30
|
+
# # => [["Joe", "Jane"], ["Smith", "Doe"]]
|
31
|
+
#
|
32
|
+
# @param [Array]
|
33
|
+
#
|
34
|
+
# @return [Array]
|
35
|
+
#
|
36
|
+
# @api public
|
37
|
+
def array_recursion(value, fn)
|
38
|
+
result = fn[value]
|
39
|
+
guarded = IF_ARRAY[-> v { Transproc(:array_recursion, fn)[v] }]
|
19
40
|
|
20
|
-
|
21
|
-
|
22
|
-
else
|
23
|
-
result[key] = item
|
41
|
+
result.map! do |item|
|
42
|
+
guarded[item]
|
24
43
|
end
|
25
44
|
end
|
26
45
|
|
27
|
-
|
46
|
+
# Recursively apply the provided transformation function to a hash
|
47
|
+
#
|
48
|
+
# @example
|
49
|
+
# Transproc(:hash_recursion, Transproc(:symbolize_keys))[
|
50
|
+
# ["name" => "Jane", "address" => { "street" => "Street 1", "zipcode" => "123" }]
|
51
|
+
# ]
|
52
|
+
# # => {:name=>"Jane", :address=>{:street=>"Street 1", :zipcode=>"123"}}
|
53
|
+
#
|
54
|
+
# @param [Hash]
|
55
|
+
#
|
56
|
+
# @return [Hash]
|
57
|
+
#
|
58
|
+
# @api public
|
59
|
+
def hash_recursion(value, fn)
|
60
|
+
result = fn[value]
|
61
|
+
guarded = IF_HASH[-> v { Transproc(:hash_recursion, fn)[v] }]
|
62
|
+
|
63
|
+
result.keys.each do |key|
|
64
|
+
result[key] = guarded[result.delete(key)]
|
65
|
+
end
|
66
|
+
|
67
|
+
result
|
68
|
+
end
|
28
69
|
end
|
29
70
|
end
|
data/lib/transproc/version.rb
CHANGED
@@ -56,8 +56,8 @@ describe 'Array transformations with Transproc' do
|
|
56
56
|
wrap =
|
57
57
|
t(
|
58
58
|
:map_array,
|
59
|
-
t(:nest, :user, [:name, :title])
|
60
|
-
t(:
|
59
|
+
t(:nest, :user, [:name, :title]) +
|
60
|
+
t(:map_value, :user, t(:nest, :task, [:title]))
|
61
61
|
)
|
62
62
|
|
63
63
|
input = [{ name: 'Jane', title: 'One' }]
|
@@ -7,6 +7,12 @@ describe 'Transproc / Coercions' do
|
|
7
7
|
end
|
8
8
|
end
|
9
9
|
|
10
|
+
describe 'to_symbol' do
|
11
|
+
it 'turns string into a symbol' do
|
12
|
+
expect(t(:to_symbol)['test']).to eql(:test)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
10
16
|
describe 'to_integer' do
|
11
17
|
it 'turns string into an integer' do
|
12
18
|
expect(t(:to_integer)['1']).to eql(1)
|
@@ -61,13 +67,13 @@ describe 'Transproc / Coercions' do
|
|
61
67
|
describe 'to_boolean' do
|
62
68
|
subject(:coercer) { t(:to_boolean) }
|
63
69
|
|
64
|
-
Transproc::TRUE_VALUES.each do |value|
|
70
|
+
Transproc::Coercions::TRUE_VALUES.each do |value|
|
65
71
|
it "turns #{value.inspect} to true" do
|
66
72
|
expect(coercer[value]).to be(true)
|
67
73
|
end
|
68
74
|
end
|
69
75
|
|
70
|
-
Transproc::FALSE_VALUES.each do |value|
|
76
|
+
Transproc::Coercions::FALSE_VALUES.each do |value|
|
71
77
|
it "turns #{value.inspect} to false" do
|
72
78
|
expect(coercer[value]).to be(false)
|
73
79
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Conditional transformations with Transproc' do
|
4
|
+
describe 'guard' do
|
5
|
+
let(:fn) { t(:guard, ->(value) { value.is_a?(::String) }, t(:to_integer)) }
|
6
|
+
|
7
|
+
context 'when predicate returns truthy value' do
|
8
|
+
it 'applies the transformation and returns the result' do
|
9
|
+
input = '2'
|
10
|
+
|
11
|
+
expect(fn[input]).to eql(2)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
context 'when predicate returns falsey value' do
|
16
|
+
it 'returns the original value' do
|
17
|
+
input = { 'foo' => 'bar' }
|
18
|
+
|
19
|
+
expect(fn[input]).to eql('foo' => 'bar')
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Transproc::Function" do
|
4
|
+
describe "#>>" do
|
5
|
+
it "composes named functions" do
|
6
|
+
f1 = t(:symbolize_keys)
|
7
|
+
f2 = t(:rename_keys, user_name: :name)
|
8
|
+
|
9
|
+
f3 = f1 >> f2
|
10
|
+
|
11
|
+
expect(f3.to_ast).to eql(
|
12
|
+
[
|
13
|
+
:symbolize_keys, [],
|
14
|
+
[
|
15
|
+
:rename_keys, [ user_name: :name ]
|
16
|
+
]
|
17
|
+
]
|
18
|
+
)
|
19
|
+
|
20
|
+
expect(f3['user_name' => 'Jane']).to eql(name: 'Jane')
|
21
|
+
|
22
|
+
f4 = f3 >> t(:nest, :details, [:name])
|
23
|
+
|
24
|
+
expect(f4.to_ast).to eql(
|
25
|
+
[
|
26
|
+
:symbolize_keys, [],
|
27
|
+
[
|
28
|
+
:rename_keys, [ user_name: :name ]
|
29
|
+
],
|
30
|
+
[
|
31
|
+
:nest, [:details, [:name]]
|
32
|
+
]
|
33
|
+
]
|
34
|
+
)
|
35
|
+
|
36
|
+
expect(f4['user_name' => 'Jane']).to eql(details: { name: 'Jane' })
|
37
|
+
end
|
38
|
+
|
39
|
+
it "composes anonymous functions" do
|
40
|
+
# TODO: Use Transproc -> (v) { v.to_s } after release of jruby-9k
|
41
|
+
f1 = Transproc proc { |v, m| v * m }, 2
|
42
|
+
f2 = Transproc proc { |v| v.to_s }
|
43
|
+
|
44
|
+
f3 = f1 >> f2
|
45
|
+
|
46
|
+
expect(f3.to_ast).to eql(
|
47
|
+
[
|
48
|
+
f1.fn, [2],
|
49
|
+
[
|
50
|
+
f2.fn, []
|
51
|
+
]
|
52
|
+
]
|
53
|
+
)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -1,6 +1,30 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe 'Hash mapping with Transproc' do
|
4
|
+
describe 'map_keys' do
|
5
|
+
it 'returns a new hash with given proc applied to keys' do
|
6
|
+
map_keys = t(:map_keys, ->(key) { key.strip })
|
7
|
+
|
8
|
+
input = { ' foo ' => 'bar' }
|
9
|
+
output = { 'foo' => 'bar' }
|
10
|
+
|
11
|
+
expect(map_keys[input]).to eql(output)
|
12
|
+
expect(input).to eql(' foo ' => 'bar')
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe 'map_keys!' do
|
17
|
+
it 'returns updated hash with given proc applied to keys' do
|
18
|
+
map_keys = t(:map_keys!, ->(key) { key.strip })
|
19
|
+
|
20
|
+
input = { ' foo ' => 'bar' }
|
21
|
+
output = { 'foo' => 'bar' }
|
22
|
+
|
23
|
+
expect(map_keys[input]).to eql(output)
|
24
|
+
expect(input).to eql('foo' => 'bar')
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
4
28
|
describe 'symbolize_keys' do
|
5
29
|
it 'returns a new hash with symbolized keys' do
|
6
30
|
symbolize_keys = t(:symbolize_keys)
|
@@ -26,6 +50,104 @@ describe 'Hash mapping with Transproc' do
|
|
26
50
|
end
|
27
51
|
end
|
28
52
|
|
53
|
+
describe 'stringify_keys' do
|
54
|
+
it 'returns a new hash with stringified keys' do
|
55
|
+
stringify_keys = t(:stringify_keys)
|
56
|
+
|
57
|
+
input = { foo: 'bar' }
|
58
|
+
output = { 'foo' => 'bar' }
|
59
|
+
|
60
|
+
expect(stringify_keys[input]).to eql(output)
|
61
|
+
expect(input).to eql(foo: 'bar')
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe 'stringify_keys!' do
|
66
|
+
it 'returns a new hash with stringified keys' do
|
67
|
+
stringify_keys = t(:stringify_keys!)
|
68
|
+
|
69
|
+
input = { foo: 'bar' }
|
70
|
+
output = { 'foo' => 'bar' }
|
71
|
+
|
72
|
+
expect(stringify_keys[input]).to eql(output)
|
73
|
+
expect(input).to eql('foo' => 'bar')
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe 'map_values' do
|
78
|
+
it 'returns a new hash with given proc applied to values' do
|
79
|
+
map_values = t(:map_values, ->(value) { value.strip })
|
80
|
+
|
81
|
+
input = { 'foo' => ' bar ' }
|
82
|
+
output = { 'foo' => 'bar' }
|
83
|
+
|
84
|
+
expect(map_values[input]).to eql(output)
|
85
|
+
expect(input).to eql('foo' => ' bar ')
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe 'map_values!' do
|
90
|
+
it 'returns updated hash with given proc applied to values' do
|
91
|
+
map_values = t(:map_values!, ->(value) { value.strip })
|
92
|
+
|
93
|
+
input = { 'foo' => ' bar ' }
|
94
|
+
output = { 'foo' => 'bar' }
|
95
|
+
|
96
|
+
expect(map_values[input]).to eql(output)
|
97
|
+
expect(input).to eql('foo' => 'bar')
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
describe 'rename_keys' do
|
102
|
+
it 'returns a new hash with applied functions' do
|
103
|
+
map = t(:rename_keys, 'foo' => :foo)
|
104
|
+
|
105
|
+
input = { 'foo' => 'bar', :bar => 'baz' }
|
106
|
+
output = { foo: 'bar', bar: 'baz' }
|
107
|
+
|
108
|
+
expect(map[input]).to eql(output)
|
109
|
+
expect(input).to eql('foo' => 'bar', :bar => 'baz')
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
describe 'rename_keys!' do
|
114
|
+
it 'returns updated hash with applied functions' do
|
115
|
+
map = t(:rename_keys!, 'foo' => :foo)
|
116
|
+
|
117
|
+
input = { 'foo' => 'bar', :bar => 'baz' }
|
118
|
+
output = { foo: 'bar', bar: 'baz' }
|
119
|
+
|
120
|
+
map[input]
|
121
|
+
|
122
|
+
expect(input).to eql(output)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
describe 'map_value' do
|
127
|
+
it 'applies function to value under specified key' do
|
128
|
+
transformation = t(:map_value, :user, t(:symbolize_keys))
|
129
|
+
|
130
|
+
input = { user: { 'name' => 'Jane' } }
|
131
|
+
output = { user: { name: 'Jane' } }
|
132
|
+
|
133
|
+
expect(transformation[input]).to eql(output)
|
134
|
+
expect(input).to eql(user: { 'name' => 'Jane' })
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
describe 'map_value!' do
|
139
|
+
it 'applies function to value under specified key' do
|
140
|
+
transformation = t(:map_value!, :user, t(:symbolize_keys))
|
141
|
+
|
142
|
+
input = { user: { 'name' => 'Jane' } }
|
143
|
+
output = { user: { name: 'Jane' } }
|
144
|
+
|
145
|
+
transformation[input]
|
146
|
+
|
147
|
+
expect(input).to eql(output)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
29
151
|
describe 'nest' do
|
30
152
|
it 'returns new hash with keys nested under a new key' do
|
31
153
|
nest = t(:nest, :baz, ['foo'])
|
@@ -101,60 +223,10 @@ describe 'Hash mapping with Transproc' do
|
|
101
223
|
end
|
102
224
|
end
|
103
225
|
|
104
|
-
describe 'map_hash' do
|
105
|
-
it 'returns a new hash with applied functions' do
|
106
|
-
map = t(:map_hash, 'foo' => :foo)
|
107
|
-
|
108
|
-
input = { 'foo' => 'bar', :bar => 'baz' }
|
109
|
-
output = { foo: 'bar', bar: 'baz' }
|
110
|
-
|
111
|
-
expect(map[input]).to eql(output)
|
112
|
-
expect(input).to eql('foo' => 'bar', :bar => 'baz')
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
describe 'map_hash!' do
|
117
|
-
it 'returns updated hash with applied functions' do
|
118
|
-
map = t(:map_hash!, 'foo' => :foo)
|
119
|
-
|
120
|
-
input = { 'foo' => 'bar', :bar => 'baz' }
|
121
|
-
output = { foo: 'bar', bar: 'baz' }
|
122
|
-
|
123
|
-
map[input]
|
124
|
-
|
125
|
-
expect(input).to eql(output)
|
126
|
-
end
|
127
|
-
end
|
128
|
-
|
129
|
-
describe 'map_key' do
|
130
|
-
it 'applies function to value under specified key' do
|
131
|
-
transformation = t(:map_key, :user, t(:symbolize_keys))
|
132
|
-
|
133
|
-
input = { user: { 'name' => 'Jane' } }
|
134
|
-
output = { user: { name: 'Jane' } }
|
135
|
-
|
136
|
-
expect(transformation[input]).to eql(output)
|
137
|
-
expect(input).to eql(user: { 'name' => 'Jane' })
|
138
|
-
end
|
139
|
-
end
|
140
|
-
|
141
|
-
describe 'map_key!' do
|
142
|
-
it 'applies function to value under specified key' do
|
143
|
-
transformation = t(:map_key!, :user, t(:symbolize_keys))
|
144
|
-
|
145
|
-
input = { user: { 'name' => 'Jane' } }
|
146
|
-
output = { user: { name: 'Jane' } }
|
147
|
-
|
148
|
-
transformation[input]
|
149
|
-
|
150
|
-
expect(input).to eql(output)
|
151
|
-
end
|
152
|
-
end
|
153
|
-
|
154
226
|
describe 'nested transform' do
|
155
227
|
it 'applies functions to nested hashes' do
|
156
228
|
symbolize_keys = t(:symbolize_keys)
|
157
|
-
map_user_key = t(:
|
229
|
+
map_user_key = t(:map_value, :user, symbolize_keys)
|
158
230
|
|
159
231
|
transformation = symbolize_keys >> map_user_key
|
160
232
|
|
@@ -168,7 +240,7 @@ describe 'Hash mapping with Transproc' do
|
|
168
240
|
describe 'combining transformations' do
|
169
241
|
it 'applies functions to the hash' do
|
170
242
|
symbolize_keys = t(:symbolize_keys)
|
171
|
-
map = t(:
|
243
|
+
map = t(:rename_keys, user_name: :name, user_email: :email)
|
172
244
|
|
173
245
|
transformation = symbolize_keys >> map
|
174
246
|
|
data/transproc.gemspec
CHANGED
@@ -8,9 +8,9 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.version = Transproc::VERSION.dup
|
9
9
|
spec.authors = ["Piotr Solnica"]
|
10
10
|
spec.email = ["piotr.solnica@gmail.com"]
|
11
|
-
spec.summary = %q{
|
11
|
+
spec.summary = %q{Transform Ruby objects in functional style}
|
12
12
|
spec.description = spec.summary
|
13
|
-
spec.homepage = ""
|
13
|
+
spec.homepage = "http://solnic.github.io/transproc/"
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
16
16
|
spec.files = `git ls-files -z`.split("\x0")
|