transproc 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,4 @@
1
1
  module Transproc
2
-
3
2
  # Conditional transformation functions
4
3
  #
5
4
  # @example
@@ -0,0 +1,16 @@
1
+ module Transproc
2
+ Error = Class.new(StandardError)
3
+ FunctionNotFoundError = Class.new(Error)
4
+ FunctionAlreadyRegisteredError = Class.new(Error)
5
+
6
+ class MalformedInputError < Error
7
+ def initialize(function, value, error)
8
+ @function = function
9
+ @value = value
10
+ @original_error = error
11
+ super("failed to call function #{function} on #{value}, #{error}")
12
+ end
13
+
14
+ attr_reader :function, :value, :original_error
15
+ end
16
+ end
@@ -1,3 +1,5 @@
1
+ require 'transproc/composite'
2
+
1
3
  module Transproc
2
4
  # Transformation proc wrapper allowing composition of multiple procs into
3
5
  # a data-transformation pipeline.
@@ -33,8 +35,10 @@ module Transproc
33
35
  # @alias []
34
36
  #
35
37
  # @api public
36
- def call(value)
37
- fn[value, *args]
38
+ def call(*value)
39
+ fn[*value, *args]
40
+ rescue => ex
41
+ raise MalformedInputError.new(@fn, value, ex)
38
42
  end
39
43
  alias_method :[], :call
40
44
 
@@ -48,7 +52,7 @@ module Transproc
48
52
  #
49
53
  # @api public
50
54
  def compose(other)
51
- Composite.new(self, right: other)
55
+ Composite.new(self, other)
52
56
  end
53
57
  alias_method :+, :compose
54
58
  alias_method :>>, :compose
@@ -59,54 +63,8 @@ module Transproc
59
63
  #
60
64
  # @api public
61
65
  def to_ast
62
- identifier = Proc === fn ? fn : fn.name
66
+ identifier = fn.is_a?(::Proc) ? fn : fn.name
63
67
  [identifier, args]
64
68
  end
65
-
66
- # Composition of two functions
67
- #
68
- # @api private
69
- class Composite < Function
70
- alias_method :left, :fn
71
-
72
- # @return [Proc]
73
- #
74
- # @api private
75
- attr_reader :right
76
-
77
- # @api private
78
- def initialize(fn, options = {})
79
- super
80
- @right = options.fetch(:right)
81
- end
82
-
83
- # Call right side with the result from the left side
84
- #
85
- # @param [Object] value The input value
86
- #
87
- # @return [Object]
88
- #
89
- # @api public
90
- def call(value)
91
- right[left[value]]
92
- end
93
- alias_method :[], :call
94
-
95
- # @see Function#compose
96
- #
97
- # @api public
98
- def compose(other)
99
- Composite.new(self, right: other)
100
- end
101
- alias_method :+, :compose
102
- alias_method :>>, :compose
103
-
104
- # @see Function#to_ast
105
- #
106
- # @api public
107
- def to_ast
108
- left.to_ast << right.to_ast
109
- end
110
- end
111
69
  end
112
70
  end
@@ -145,10 +145,60 @@ module Transproc
145
145
  hash
146
146
  end
147
147
 
148
+ # Same as `:reject_keys` but mutates the hash
149
+ #
150
+ # @see HashTransformations.reject_keys
151
+ #
152
+ # @api public
153
+ def reject_keys!(hash, keys)
154
+ hash.reject { |k,_| keys.include?(k) }
155
+ end
156
+
157
+ # Rejects specified keys from a hash
158
+ #
159
+ # @example
160
+ # Transproc(:reject_keys, [:name])[name: 'Jane', email: 'jane@doe.org']
161
+ # # => {:email => "jane@doe.org"}
162
+ #
163
+ # @param [Hash] hash The input hash
164
+ # @param [Array] keys The keys to be rejected
165
+ #
166
+ # @return [Hash]
167
+ #
168
+ # @api public
169
+ def reject_keys(hash, keys)
170
+ reject_keys!(Hash[hash], keys)
171
+ end
172
+
173
+ # Same as `:accept_keys` but mutates the hash
174
+ #
175
+ # @see HashTransformations.reject_keys
176
+ #
177
+ # @api public
178
+ def accept_keys!(hash, keys)
179
+ reject_keys!(hash, hash.keys - keys)
180
+ end
181
+
182
+ # Accepts specified keys from a hash
183
+ #
184
+ # @example
185
+ # Transproc(:accept_keys, [:name])[name: 'Jane', email: 'jane@doe.org']
186
+ # # => {:email => "jane@doe.org"}
187
+ #
188
+ # @param [Hash] hash The input hash
189
+ # @param [Array] keys The keys to be accepted
190
+ #
191
+ # @return [Hash]
192
+ #
193
+ # @api public
194
+ def accept_keys(hash, keys)
195
+ accept_keys!(Hash[hash], keys)
196
+ end
197
+
148
198
  # Map a key in a hash with the provided transformation function
149
199
  #
150
200
  # @example
151
- # Transproc(:map_value, -> s { s.upcase })['name' => 'jane']
201
+ # Transproc(:map_value, 'name', -> s { s.upcase })['name' => 'jane']
152
202
  # # => {"name" => "JANE"}
153
203
  #
154
204
  # @param [Hash]
@@ -211,7 +261,7 @@ module Transproc
211
261
  # @return [Hash]
212
262
  #
213
263
  # @api public
214
- def unwrap(hash, root, keys)
264
+ def unwrap(hash, root, keys = nil)
215
265
  copy = Hash[hash].merge(root => Hash[hash[root]])
216
266
  unwrap!(copy, root, keys)
217
267
  end
@@ -0,0 +1,19 @@
1
+ module Transproc
2
+ # Transformation functions for Objects
3
+ #
4
+ # @example
5
+ # require 'transproc/object'
6
+ #
7
+ # include Transproc::Helper
8
+ #
9
+ # fn = t(:set_ivars, { name: 'Jane', age: 25 })
10
+ #
11
+ # fn[Object.new]
12
+ # # => #<Object:0x007f73afe7d6f8 @name="Jane", @age=25>
13
+ #
14
+ # @api public
15
+ module ObjectTransformations
16
+ extend Functions
17
+
18
+ end
19
+ end
@@ -1,3 +1,3 @@
1
1
  module Transproc
2
- VERSION = '0.2.0'.freeze
2
+ VERSION = '0.2.1'.freeze
3
3
  end
@@ -0,0 +1,16 @@
1
+ desc "Run mutant against a specific subject"
2
+ task :mutant do
3
+ subject = ARGV.last
4
+ if subject == 'mutant'
5
+ abort "usage: rake mutant SUBJECT\nexample: rake mutant Transproc::Recursion"
6
+ else
7
+ opts = {
8
+ 'include' => 'lib',
9
+ 'require' => 'transproc/all',
10
+ 'use' => 'rspec',
11
+ 'ignore-subject' => "#{subject}#respond_to_missing?"
12
+ }.to_a.map { |k, v| "--#{k} #{v}" }.join(' ')
13
+
14
+ exec("bundle exec mutant #{opts} #{subject}")
15
+ end
16
+ end
@@ -0,0 +1,18 @@
1
+ begin
2
+ require 'rubocop/rake_task'
3
+
4
+ Rake::Task[:default].enhance [:rubocop]
5
+
6
+ RuboCop::RakeTask.new do |task|
7
+ task.options << '--display-cop-names'
8
+ end
9
+
10
+ namespace :rubocop do
11
+ desc 'Generate a configuration file acting as a TODO list.'
12
+ task :auto_gen_config do
13
+ exec 'bundle exec rubocop --auto-gen-config'
14
+ end
15
+ end
16
+
17
+ rescue LoadError
18
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,8 +1,10 @@
1
- if RUBY_ENGINE == "rbx"
2
- require "codeclimate-test-reporter"
1
+ if RUBY_ENGINE == 'rbx'
2
+ require 'codeclimate-test-reporter'
3
3
  CodeClimate::TestReporter.start
4
4
  end
5
5
 
6
+ require 'equalizer'
7
+ require 'anima'
6
8
  require 'transproc/all'
7
9
 
8
10
  begin
@@ -0,0 +1,166 @@
1
+ require 'spec_helper'
2
+
3
+ describe Transproc::ArrayTransformations do
4
+ describe '.extract_key' do
5
+ it 'extracts values by key from all hashes' do
6
+ extract_key = t(:extract_key, 'name')
7
+
8
+ original = [
9
+ { 'name' => 'Alice', 'role' => 'sender' },
10
+ { 'name' => 'Bob', 'role' => 'receiver' },
11
+ { 'role' => 'listener' }
12
+ ]
13
+
14
+ input = original
15
+
16
+ output = ['Alice', 'Bob', nil]
17
+
18
+ expect(extract_key[input]).to eql(output)
19
+ expect(input).to eql(original)
20
+ end
21
+ end
22
+
23
+ describe '.extract_key!' do
24
+ it 'extracts values by key from all hashes' do
25
+ extract_key = t(:extract_key!, 'name')
26
+
27
+ input = [
28
+ { 'name' => 'Alice', 'role' => 'sender' },
29
+ { 'name' => 'Bob', 'role' => 'receiver' },
30
+ { 'role' => 'listener' }
31
+ ]
32
+
33
+ output = ['Alice', 'Bob', nil]
34
+
35
+ extract_key[input]
36
+
37
+ expect(input).to eql(output)
38
+ end
39
+ end
40
+
41
+ describe '.map_array' do
42
+ it 'applies funtions to all values' do
43
+ map = t(:map_array, t(:symbolize_keys))
44
+
45
+ original = [
46
+ { 'name' => 'Jane', 'title' => 'One' },
47
+ { 'name' => 'Jane', 'title' => 'Two' }
48
+ ]
49
+
50
+ input = original
51
+
52
+ output = [
53
+ { name: 'Jane', title: 'One' },
54
+ { name: 'Jane', title: 'Two' }
55
+ ]
56
+
57
+ expect(map[input]).to eql(output)
58
+ expect(input).to eql(original)
59
+ end
60
+ end
61
+
62
+ describe '.map_array!' do
63
+ it 'updates array with the result of the function applied to each value' do
64
+ map = t(:map_array!, t(:symbolize_keys))
65
+
66
+ input = [
67
+ { 'name' => 'Jane', 'title' => 'One' },
68
+ { 'name' => 'Jane', 'title' => 'Two' }
69
+ ]
70
+
71
+ output = [
72
+ { name: 'Jane', title: 'One' },
73
+ { name: 'Jane', title: 'Two' }
74
+ ]
75
+
76
+ map[input]
77
+
78
+ expect(input).to eql(output)
79
+ end
80
+ end
81
+
82
+ describe '.wrap' do
83
+ it 'returns a new array with wrapped hashes' do
84
+ wrap = t(:wrap, :task, [:title])
85
+
86
+ input = [{ name: 'Jane', title: 'One' }]
87
+ output = [{ name: 'Jane', task: { title: 'One' } }]
88
+
89
+ expect(wrap[input]).to eql(output)
90
+ end
91
+
92
+ it 'returns a array new with deeply wrapped hashes' do
93
+ wrap =
94
+ t(
95
+ :map_array,
96
+ t(:nest, :user, [:name, :title]) +
97
+ t(:map_value, :user, t(:nest, :task, [:title]))
98
+ )
99
+
100
+ input = [{ name: 'Jane', title: 'One' }]
101
+ output = [{ user: { name: 'Jane', task: { title: 'One' } } }]
102
+
103
+ expect(wrap[input]).to eql(output)
104
+ end
105
+ end
106
+
107
+ describe '.group' do
108
+ it 'returns a new array with grouped hashes' do
109
+ group = t(:group, :tasks, [:title])
110
+
111
+ input = [{ name: 'Jane', title: 'One' }, { name: 'Jane', title: 'Two' }]
112
+ output = [{ name: 'Jane', tasks: [{ title: 'One' }, { title: 'Two' }] }]
113
+
114
+ expect(group[input]).to eql(output)
115
+ end
116
+ end
117
+
118
+ describe '.combine' do
119
+ let(:input) do
120
+ [
121
+ # parent users
122
+ [
123
+ { name: 'Jane', email: 'jane@doe.org' },
124
+ { name: 'Joe', email: 'joe@doe.org' }
125
+ ],
126
+ [
127
+ [
128
+ # user tasks
129
+ [
130
+ { user: 'Jane', title: 'One' },
131
+ { user: 'Jane', title: 'Two' },
132
+ { user: 'Joe', title: 'Three' }
133
+ ],
134
+ [
135
+ # task tags
136
+ [
137
+ { task: 'One', tag: 'red' },
138
+ { task: 'Three', tag: 'blue' }
139
+ ]
140
+ ]
141
+ ]
142
+ ]
143
+ ]
144
+ end
145
+
146
+ let(:output) do
147
+ [
148
+ { name: 'Jane', email: 'jane@doe.org', tasks: [
149
+ { user: 'Jane', title: 'One', tags: [{ task: 'One', tag: 'red' }] },
150
+ { user: 'Jane', title: 'Two', tags: [] } ]
151
+ },
152
+ { name: 'Joe', email: 'joe@doe.org', tasks: [
153
+ { user: 'Joe', title: 'Three', tags: [{ task: 'Three', tag: 'blue' }] } ]
154
+ }
155
+ ]
156
+ end
157
+
158
+ it 'merges hashes from arrays using provided join keys' do
159
+ combine = t(:combine, [
160
+ [:tasks, { name: :user }, [[:tags, title: :task]]]
161
+ ])
162
+
163
+ expect(combine[input]).to eql(output)
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+
3
+ describe Transproc::ClassTransformations do
4
+ describe '.constructor_inject' do
5
+ let(:klass) do
6
+ Struct.new(:name, :age) { include Equalizer.new(:name, :age) }
7
+ end
8
+
9
+ it 'returns a new object initialized with the given arguments' do
10
+ constructor_inject = t(:constructor_inject, klass)
11
+
12
+ input = ['Jane', 25]
13
+ output = klass.new(*input)
14
+ result = constructor_inject[*input]
15
+
16
+ expect(result).to eql(output)
17
+ expect(result).to be_instance_of(klass)
18
+ end
19
+ end
20
+
21
+ describe '.set_ivars' do
22
+ let(:klass) do
23
+ Class.new do
24
+ include Anima.new(:name, :age)
25
+
26
+ attr_reader :test
27
+
28
+ def initialize(*args)
29
+ super
30
+ @test = true
31
+ end
32
+ end
33
+ end
34
+
35
+ it 'allocates a new object and sets instance variables from hash key/value pairs' do
36
+ set_ivars = t(:set_ivars, klass)
37
+
38
+ input = { name: 'Jane', age: 25 }
39
+ output = klass.new(input)
40
+ result = set_ivars[input]
41
+
42
+ expect(result).to eql(output)
43
+ expect(result.test).to be(nil)
44
+ expect(result).to be_instance_of(klass)
45
+ end
46
+ end
47
+ end