transproc 0.1.3 → 0.2.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/.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")
|