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.
@@ -1,31 +1,74 @@
1
1
  module Transproc
2
+ # Transproc helper that adds `t` method as a shortcut for `Transproc` method
3
+ #
4
+ # @example
5
+ # include Transproc::Helper
6
+ #
7
+ # t(:to_string)
8
+ #
9
+ # @api public
2
10
  module Helper
11
+ # @see Transproc
12
+ #
13
+ # @api public
3
14
  def t(*args, &block)
4
15
  Transproc(*args, &block)
5
16
  end
6
17
  end
7
18
 
19
+ # Helper extension handy for composing many functions in multiple steps
20
+ #
21
+ # @example
22
+ # include Transproc::Composer
23
+ #
24
+ # fn = compose do |fns|
25
+ # fns << t(:map_array, t(:symbolize_keys))
26
+ # fns << t(:map_array, t(:nest, :address, [:city, :zipcode]))
27
+ # end
28
+ #
29
+ # fn.call [{ 'city' => 'NYC', 'zipcode' => '123' }]
30
+ # # => [{ address: { city: 'NYC', zipcode: '123' }]
31
+ #
32
+ # @api public
8
33
  module Composer
9
34
  include Helper
10
35
 
36
+ # @api private
11
37
  class Factory
12
38
  attr_reader :fns, :default
13
39
 
40
+ # @api private
14
41
  def initialize(default = nil)
15
42
  @fns = []
16
43
  @default = default
17
44
  end
18
45
 
46
+ # @api private
19
47
  def <<(other)
20
48
  fns.concat(Array(other).compact)
21
49
  self
22
50
  end
23
51
 
52
+ # @api private
24
53
  def to_fn
25
54
  fns.reduce(:+) || default
26
55
  end
27
56
  end
28
57
 
58
+ # Gather and compose functions and fall-back to a default one if provided
59
+ #
60
+ # @example
61
+ # include Transproc::Composer
62
+ #
63
+ # fn = compose(-> v { v }) do |fns|
64
+ # fns << t(:to_string) if something
65
+ # end
66
+ #
67
+ # fn[1] # => "1"
68
+ #
69
+ # @see Composer
70
+ #
71
+ # @api public
29
72
  def compose(default = nil)
30
73
  factory = Factory.new(default)
31
74
  yield(factory)
@@ -0,0 +1,56 @@
1
+ module Transproc
2
+
3
+ # Conditional transformation functions
4
+ #
5
+ # @example
6
+ # require 'transproc/conditional'
7
+ #
8
+ # include Transproc::Helper
9
+ #
10
+ # fn = t(:guard, -> s { s.is_a?(::String) }, -> s { s.to_sym })
11
+ #
12
+ # [fn[2], fn['Jane']]
13
+ # # => [2, :Jane]
14
+ #
15
+ # @api public
16
+ module Conditional
17
+ extend Functions
18
+
19
+ # Apply the transformation function to subject if the predicate returns true, or return un-modified
20
+ #
21
+ # @example
22
+ # [2, 'Jane'].map do |subject|
23
+ # Transproc(:guard, -> s { s.is_a?(::String) }, -> s { s.to_sym })[subject]
24
+ # end
25
+ # # => [2, :Jane]
26
+ #
27
+ # @param [Mixed]
28
+ #
29
+ # @return [Mixed]
30
+ #
31
+ # @api public
32
+ def guard(value, predicate, fn)
33
+ predicate[value] ? fn[value] : value
34
+ end
35
+
36
+ # Calls a function when type-check passes
37
+ #
38
+ # @example
39
+ # fn = Transproc(:is, Array, -> arr { arr.map(&:upcase) })
40
+ # fn.call(['a', 'b', 'c']) # => ['A', 'B', 'C']
41
+ #
42
+ # fn = Transproc(:is, Array, -> arr { arr.map(&:upcase) })
43
+ # fn.call('foo') # => "foo"
44
+ #
45
+ # @param [Object]
46
+ # @param [Class]
47
+ # @param [Proc]
48
+ #
49
+ # @return [Object]
50
+ #
51
+ # @api public
52
+ def is(value, type, fn)
53
+ guard(value, -> v { v.is_a?(type) }, fn)
54
+ end
55
+ end
56
+ end
@@ -1,21 +1,112 @@
1
1
  module Transproc
2
+ # Transformation proc wrapper allowing composition of multiple procs into
3
+ # a data-transformation pipeline.
4
+ #
5
+ # This is used by Transproc to wrap registered methods.
6
+ #
7
+ # @api private
2
8
  class Function
3
- attr_reader :fn, :args
9
+ # Wrapped proc or another composite function
10
+ #
11
+ # @return [Proc,Composed]
12
+ #
13
+ # @api private
14
+ attr_reader :fn
4
15
 
5
- def initialize(fn, args = [])
16
+ # Additional arguments that will be passed to the wrapped proc
17
+ #
18
+ # @return [Array]
19
+ #
20
+ # @api private
21
+ attr_reader :args
22
+
23
+ # @api private
24
+ def initialize(fn, options = {})
6
25
  @fn = fn
7
- @args = args
26
+ @args = options.fetch(:args) { [] }
8
27
  end
9
28
 
29
+ # Call the wrapped proc
30
+ #
31
+ # @param [Object] value The input value
32
+ #
33
+ # @alias []
34
+ #
35
+ # @api public
10
36
  def call(value)
11
37
  fn[value, *args]
12
38
  end
13
39
  alias_method :[], :call
14
40
 
41
+ # Compose this function with another function or a proc
42
+ #
43
+ # @param [Proc,Function]
44
+ #
45
+ # @return [Composite]
46
+ #
47
+ # @alias :>>
48
+ #
49
+ # @api public
15
50
  def compose(other)
16
- self.class.new(-> *input { other[fn[*input]] }, args)
51
+ Composite.new(self, right: other)
17
52
  end
18
53
  alias_method :+, :compose
19
54
  alias_method :>>, :compose
55
+
56
+ # Return a simple AST representation of this function
57
+ #
58
+ # @return [Array]
59
+ #
60
+ # @api public
61
+ def to_ast
62
+ identifier = Proc === fn ? fn : fn.name
63
+ [identifier, args]
64
+ 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
20
111
  end
21
112
  end
@@ -1,57 +1,234 @@
1
+ require 'transproc/coercions'
2
+
1
3
  module Transproc
2
- register(:symbolize_keys) do |hash|
3
- Transproc(:symbolize_keys!)[Hash[hash]]
4
- end
4
+ # Transformation functions for Hash objects
5
+ #
6
+ # @example
7
+ # require 'transproc/hash'
8
+ #
9
+ # include Transproc::Helper
10
+ #
11
+ # fn = t(:symbolize_keys) >> t(:nest, :address, [:street, :zipcode])
12
+ #
13
+ # fn["street" => "Street 1", "zipcode" => "123"]
14
+ # # => {:address => {:street => "Street 1", :zipcode => "123"}}
15
+ #
16
+ # @api public
17
+ module HashTransformations
18
+ extend Functions
5
19
 
6
- register(:symbolize_keys!) do |hash|
7
- hash.keys.each { |key| hash[key.to_sym] = hash.delete(key) }
8
- hash
9
- end
20
+ # Map all keys in a hash with the provided transformation function
21
+ #
22
+ # @example
23
+ # Transproc(:map_keys, -> s { s.upcase })['name' => 'Jane']
24
+ # # => {"NAME" => "Jane"}
25
+ #
26
+ # @param [Hash]
27
+ #
28
+ # @return [Hash]
29
+ #
30
+ # @api public
31
+ def map_keys(hash, fn)
32
+ map_keys!(Hash[hash], fn)
33
+ end
10
34
 
11
- register(:map_hash) do |hash, mapping|
12
- Transproc(:map_hash!, mapping)[Hash[hash]]
13
- end
35
+ # Same as `:map_keys` but mutates the hash
36
+ #
37
+ # @see HashTransformations.map_keys
38
+ #
39
+ # @api public
40
+ def map_keys!(hash, fn)
41
+ hash.keys.each { |key| hash[fn[key]] = hash.delete(key) }
42
+ hash
43
+ end
14
44
 
15
- register(:map_hash!) do |hash, mapping|
16
- mapping.each { |k, v| hash[v] = hash.delete(k) }
17
- hash
18
- end
45
+ # Symbolize all keys in a hash
46
+ #
47
+ # @example
48
+ # Transproc(:symbolize_keys)['name' => 'Jane']
49
+ # # => {:name => "Jane"}
50
+ #
51
+ # @param [Hash]
52
+ #
53
+ # @return [Hash]
54
+ #
55
+ # @api public
56
+ def symbolize_keys(hash)
57
+ symbolize_keys!(Hash[hash])
58
+ end
19
59
 
20
- register(:map_key) do |hash, key, fn|
21
- hash.merge(key => fn[hash[key]])
22
- end
60
+ # Same as `:symbolize_keys` but mutates the hash
61
+ #
62
+ # @see HashTransformations.symbolize_keys!
63
+ #
64
+ # @api public
65
+ def symbolize_keys!(hash)
66
+ map_keys!(hash, Transproc(:to_symbol).fn)
67
+ end
23
68
 
24
- register(:map_key!) do |hash, key, fn|
25
- hash.update(key => fn[hash[key]])
26
- end
69
+ # Stringify all keys in a hash
70
+ #
71
+ # @example
72
+ # Transproc(:stringify_keys)[:name => 'Jane']
73
+ # # => {"name" => "Jane"}
74
+ #
75
+ # @param [Hash]
76
+ #
77
+ # @return [Hash]
78
+ #
79
+ # @api public
80
+ def stringify_keys(hash)
81
+ stringify_keys!(Hash[hash])
82
+ end
27
83
 
28
- register(:nest) do |hash, key, keys|
29
- Transproc(:nest!, key, keys)[Hash[hash]]
30
- end
84
+ # Same as `:stringify_keys` but mutates the hash
85
+ #
86
+ # @see HashTransformations.stringify_keys
87
+ #
88
+ # @api public
89
+ def stringify_keys!(hash)
90
+ map_keys!(hash, Transproc(:to_string).fn)
91
+ end
31
92
 
32
- register(:nest!) do |hash, root, keys|
33
- nest_keys = hash.keys & keys
93
+ # Map all values in a hash using transformation function
94
+ #
95
+ # @example
96
+ # Transproc(:map_values, -> v { v.upcase })[:name => 'Jane']
97
+ # # => {"name" => "JANE"}
98
+ #
99
+ # @param [Hash]
100
+ #
101
+ # @return [Hash]
102
+ #
103
+ # @api public
104
+ def map_values(hash, fn)
105
+ map_values!(Hash[hash], fn)
106
+ end
34
107
 
35
- if nest_keys.size > 0
36
- child = Hash[nest_keys.zip(nest_keys.map { |key| hash.delete(key) })]
37
- hash.update(root => child)
38
- else
39
- hash.update(root => {})
108
+ # Same as `:map_values` but mutates the hash
109
+ #
110
+ # @see HashTransformations.map_values
111
+ #
112
+ # @param [Hash]
113
+ #
114
+ # @return [Hash]
115
+ #
116
+ # @api public
117
+ def map_values!(hash, fn)
118
+ hash.each { |key, value| hash[key] = fn[value] }
119
+ hash
40
120
  end
41
- end
42
121
 
43
- register(:unwrap) do |hash, root, keys|
44
- copy = Hash[hash].merge(root => Hash[hash[root]])
45
- Transproc(:unwrap!, root, keys)[copy]
46
- end
122
+ # Rename all keys in a hash using provided mapping hash
123
+ #
124
+ # @example
125
+ # Transproc(:rename_keys, user_name: :name)[user_name: 'Jane']
126
+ # # => {:name => "Jane"}
127
+ #
128
+ # @param [Hash] hash The input hash
129
+ # @param [Hash] mapping The key-rename mapping
130
+ #
131
+ # @return [Hash]
132
+ #
133
+ # @api public
134
+ def rename_keys(hash, mapping)
135
+ rename_keys!(Hash[hash], mapping)
136
+ end
137
+
138
+ # Same as `:rename_keys` but mutates the hash
139
+ #
140
+ # @see HashTransformations.rename_keys
141
+ #
142
+ # @api public
143
+ def rename_keys!(hash, mapping)
144
+ mapping.each { |k, v| hash[v] = hash.delete(k) }
145
+ hash
146
+ end
147
+
148
+ # Map a key in a hash with the provided transformation function
149
+ #
150
+ # @example
151
+ # Transproc(:map_value, -> s { s.upcase })['name' => 'jane']
152
+ # # => {"name" => "JANE"}
153
+ #
154
+ # @param [Hash]
155
+ #
156
+ # @return [Hash]
157
+ #
158
+ # @api public
159
+ def map_value(hash, key, fn)
160
+ hash.merge(key => fn[hash[key]])
161
+ end
162
+
163
+ # Same as `:map_value` but mutates the hash
164
+ #
165
+ # @see HashTransformations.map_value!
166
+ #
167
+ # @api public
168
+ def map_value!(hash, key, fn)
169
+ hash.update(key => fn[hash[key]])
170
+ end
47
171
 
48
- register(:unwrap!) do |hash, root, keys|
49
- if nested_hash = hash[root]
50
- keys ||= nested_hash.keys
51
- hash.update(Hash[keys.zip(keys.map { |key| nested_hash.delete(key) })])
52
- hash.delete(root) if nested_hash.empty?
172
+ # Nest values from specified keys under a new key
173
+ #
174
+ # @example
175
+ # Transproc(:nest, :address, [:street, :zipcode])[street: 'Street', zipcode: '123']
176
+ # # => {address: {street: "Street", zipcode: "123"}}
177
+ #
178
+ # @param [Hash]
179
+ #
180
+ # @return [Hash]
181
+ #
182
+ # @api public
183
+ def nest(hash, key, keys)
184
+ nest!(Hash[hash], key, keys)
53
185
  end
54
186
 
55
- hash
187
+ # Same as `:nest` but mutates the hash
188
+ #
189
+ # @see HashTransformations.nest
190
+ #
191
+ # @api public
192
+ def nest!(hash, root, keys)
193
+ nest_keys = hash.keys & keys
194
+
195
+ if nest_keys.size > 0
196
+ child = Hash[nest_keys.zip(nest_keys.map { |key| hash.delete(key) })]
197
+ hash.update(root => child)
198
+ else
199
+ hash.update(root => {})
200
+ end
201
+ end
202
+
203
+ # Collapse a nested hash from a specified key
204
+ #
205
+ # @example
206
+ # Transproc(:unwrap, :address, [:street, :zipcode])[address: { street: 'Street', zipcode: '123' }]
207
+ # # => {street: "Street", zipcode: "123"}
208
+ #
209
+ # @param [Hash]
210
+ #
211
+ # @return [Hash]
212
+ #
213
+ # @api public
214
+ def unwrap(hash, root, keys)
215
+ copy = Hash[hash].merge(root => Hash[hash[root]])
216
+ unwrap!(copy, root, keys)
217
+ end
218
+
219
+ # Same as `:unwrap` but mutates the hash
220
+ #
221
+ # @see HashTransformations.unwrap
222
+ #
223
+ # @api public
224
+ def unwrap!(hash, root, keys = nil)
225
+ if nested_hash = hash[root]
226
+ keys ||= nested_hash.keys
227
+ hash.update(Hash[keys.zip(keys.map { |key| nested_hash.delete(key) })])
228
+ hash.delete(root) if nested_hash.empty?
229
+ end
230
+
231
+ hash
232
+ end
56
233
  end
57
234
  end