transproc 0.2.4 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -15,13 +15,13 @@ module Transproc
15
15
  #
16
16
  # @api public
17
17
  module Recursion
18
- extend Functions
18
+ extend Registry
19
19
 
20
- IF_ENUMERABLE = -> fn { Transproc(:is, Enumerable, fn) }
20
+ IF_ENUMERABLE = -> fn { Conditional[:is, Enumerable, fn] }
21
21
 
22
- IF_ARRAY = -> fn { Transproc(:is, Array, fn) }
22
+ IF_ARRAY = -> fn { Conditional[:is, Array, fn] }
23
23
 
24
- IF_HASH = -> fn { Transproc(:is, Hash, fn) }
24
+ IF_HASH = -> fn { Conditional[:is, Hash, fn] }
25
25
 
26
26
  # Recursively apply the provided transformation function to an enumerable
27
27
  #
@@ -43,7 +43,7 @@ module Transproc
43
43
  # @return [Enumerable]
44
44
  #
45
45
  # @api public
46
- def recursion(value, fn)
46
+ def self.recursion(value, fn)
47
47
  result = fn[value]
48
48
  guarded = IF_ENUMERABLE[-> v { recursion(v, fn) }]
49
49
 
@@ -74,7 +74,7 @@ module Transproc
74
74
  # @return [Array]
75
75
  #
76
76
  # @api public
77
- def array_recursion(value, fn)
77
+ def self.array_recursion(value, fn)
78
78
  result = fn[value]
79
79
  guarded = IF_ARRAY[-> v { array_recursion(v, fn) }]
80
80
 
@@ -96,7 +96,7 @@ module Transproc
96
96
  # @return [Hash]
97
97
  #
98
98
  # @api public
99
- def hash_recursion(value, fn)
99
+ def self.hash_recursion(value, fn)
100
100
  result = fn[value]
101
101
  guarded = IF_HASH[-> v { hash_recursion(v, fn) }]
102
102
 
@@ -106,5 +106,9 @@ module Transproc
106
106
 
107
107
  result
108
108
  end
109
+
110
+ # @deprecated Register methods globally
111
+ (methods - Registry.methods - Registry.instance_methods)
112
+ .each { |name| Transproc.register name, t(name) }
109
113
  end
110
114
  end
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  module Transproc
2
4
  # Container to define transproc functions in, and access them via `[]` method
3
5
  # from the outside of the module
@@ -6,7 +8,7 @@ module Transproc
6
8
  # module FooMethods
7
9
  # extend Transproc::Registry
8
10
  #
9
- # def foo(name, prefix)
11
+ # def self.foo(name, prefix)
10
12
  # [prefix, '_', name].join
11
13
  # end
12
14
  # end
@@ -15,10 +17,9 @@ module Transproc
15
17
  # fn['qux'] # => 'qux_baz'
16
18
  #
17
19
  # module BarMethods
18
- # # extend Transproc::Registry
19
- # include FooMethods
20
+ # extend FooMethods
20
21
  #
21
- # def bar(*args)
22
+ # def self.bar(*args)
22
23
  # foo(*args).upcase
23
24
  # end
24
25
  # end
@@ -31,10 +32,11 @@ module Transproc
31
32
  #
32
33
  # @api public
33
34
  module Registry
34
- # Builds the transproc function either from a Proc, or from the module method
35
+ # Builds the transformation
35
36
  #
36
37
  # @param [Proc, Symbol] fn
37
- # Either a proc, or a name of the module's function to be wrapped to transproc
38
+ # A proc, a name of the module's own function, or a name of imported
39
+ # procedure from another module
38
40
  # @param [Object, Array] args
39
41
  # Args to be carried by the transproc
40
42
  #
@@ -42,85 +44,81 @@ module Transproc
42
44
  #
43
45
  # @alias :t
44
46
  #
45
- # @api public
46
47
  def [](fn, *args)
47
- fun = fn.is_a?(Proc) ? fn : method(fn).to_proc
48
- Transproc::Function.new(fun, args: args)
48
+ Function.new(fetch(fn), args: args, name: fn)
49
49
  end
50
50
  alias_method :t, :[]
51
51
 
52
- # Forwards the named method (transproc) to another module
52
+ # Imports either a method (converted to a proc) from another module, or
53
+ # all methods from that module.
53
54
  #
54
- # Allows using transprocs from other modules without including those
55
- # modules as a whole
55
+ # If the external module is a registry, looks for its imports too.
56
56
  #
57
57
  # @example
58
58
  # module Foo
59
- # extend Transproc::Registry
60
- #
61
- # def foo(value)
59
+ # def self.foo(value)
62
60
  # value.upcase
63
61
  # end
64
62
  #
65
- # def bar(value)
63
+ # def self.bar(value)
66
64
  # value.downcase
67
65
  # end
68
66
  # end
69
67
  #
68
+ # module Qux
69
+ # def self.qux(value)
70
+ # value.reverse
71
+ # end
72
+ # end
73
+ #
70
74
  # module Bar
71
75
  # extend Transproc::Registry
72
76
  #
73
- # uses :foo, from: Foo, as: :baz
74
- # uses :bar, from: Foo
77
+ # import :foo, from: Foo, as: :baz
78
+ # import :bar, from: Foo
79
+ # import Qux
75
80
  # end
76
81
  #
77
82
  # Bar[:baz]['Qux'] # => 'QUX'
78
83
  # Bar[:bar]['Qux'] # => 'qux'
84
+ # Bar[:qux]['Qux'] # => 'xuQ'
79
85
  #
80
- # @param [String, Symbol] name
81
- # @option [Class] :from The module to take the method from
82
- # @option [String, Symbol] :as
86
+ # @param [Module, #to_sym] name
87
+ # @option [Module] :from The module to take the method from
88
+ # @option [#to_sym] :as
83
89
  # The name of imported transproc inside the current module
84
90
  #
85
- # @return [undefined]
91
+ # @return [itself] self
92
+ #
93
+ # @alias :import
86
94
  #
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) }
95
+ def import(source, options = nil)
96
+ @store = store.import(source, options)
97
+ self
92
98
  end
99
+ alias_method :uses, :import
93
100
 
94
- # @api private
95
- def self.extended(target)
96
- target.extend(ClassMethods)
101
+ # The store of procedures imported from external modules
102
+ #
103
+ # @return [Transproc::Store]
104
+ #
105
+ def store
106
+ @store ||= Store.new
97
107
  end
98
108
 
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
109
+ # Gets the procedure for creating a transproc
110
+ #
111
+ # @param [#call, Symbol] fn
112
+ # Either the procedure, or the name of the method of the current module,
113
+ # or the registered key of imported procedure in a store.
114
+ #
115
+ # @return [#call]
116
+ #
117
+ def fetch(fn)
118
+ return fn unless fn.instance_of? Symbol
119
+ respond_to?(fn) ? method(fn) : store.fetch(fn)
120
+ rescue
121
+ raise FunctionNotFoundError.new(fn, self)
124
122
  end
125
123
  end
126
124
  end
@@ -0,0 +1,59 @@
1
+ # encoding: utf-8
2
+
3
+ # ==============================================================================
4
+ # Examples for testing transproc functions
5
+ # ==============================================================================
6
+
7
+ shared_context :call_transproc do
8
+ let!(:initial) { input.dup rescue input }
9
+ let!(:function) { described_class[*arguments] }
10
+ let!(:result) { function[input] }
11
+ end
12
+
13
+ shared_examples :transforming_data do
14
+ it '[returns the expected output]' do
15
+ expect(result).to eql(output), <<-REPORT.gsub(/.+\|/, "")
16
+ |
17
+ |fn = #{described_class}#{Array[*arguments]}
18
+ |
19
+ |fn[#{input}]
20
+ |
21
+ | expected: #{output}
22
+ | got: #{result}
23
+ REPORT
24
+ end
25
+ end
26
+
27
+ shared_examples :transforming_immutable_data do
28
+ include_context :call_transproc
29
+
30
+ it_behaves_like :transforming_data
31
+
32
+ it '[keeps input unchanged]' do
33
+ expect(input).to eql(initial), <<-REPORT.gsub(/.+\|/, "")
34
+ |
35
+ |fn = #{described_class}#{Array[*arguments]}
36
+ |
37
+ |expected: not to change #{initial}
38
+ | got: changed it to #{input}
39
+ REPORT
40
+ end
41
+ end
42
+
43
+ shared_examples :mutating_input_data do
44
+ include_context :call_transproc
45
+
46
+ it_behaves_like :transforming_data
47
+
48
+ it '[changes input]' do
49
+ expect(input).to eql(output), <<-REPORT.gsub(/.+\|/, "")
50
+ |
51
+ |fn = #{described_class}#{Array[*arguments]}
52
+ |
53
+ |fn[#{input}]
54
+ |
55
+ |expected: to change input to #{output}
56
+ | got: #{input}
57
+ REPORT
58
+ end
59
+ end
@@ -0,0 +1,94 @@
1
+ # encoding: utf-8
2
+
3
+ module Transproc
4
+ # Immutable collection of named procedures from external modules
5
+ #
6
+ # @api private
7
+ #
8
+ class Store
9
+ # @!attribute [r] methods
10
+ #
11
+ # @return [Hash] The associated list of imported procedures
12
+ #
13
+ attr_reader :methods
14
+
15
+ # @!scope class
16
+ # @!name new(methods = {})
17
+ # Creates an immutable store with a hash of procedures
18
+ #
19
+ # @param [Hash] methods
20
+ #
21
+ # @return [Transproc::Store]
22
+
23
+ # @private
24
+ def initialize(methods = {})
25
+ @methods = methods.dup.freeze
26
+ freeze
27
+ end
28
+
29
+ # Returns a procedure by its key in the collection
30
+ #
31
+ # @param [Symbol] key
32
+ #
33
+ # @return [Proc]
34
+ #
35
+ def fetch(key)
36
+ methods.fetch(key.to_sym)
37
+ end
38
+
39
+ # Imports proc(s) to the collection from another module
40
+ #
41
+ # @overload add(source)
42
+ # @param (see #import_methods)
43
+ # @return (see #import_methods)
44
+ #
45
+ # @overload add(source, options)
46
+ # @param (see #import_method)
47
+ # @return (see #import_method)
48
+ #
49
+ def import(source, options = nil)
50
+ return import_methods(source) if source.instance_of?(Module)
51
+ import_method(source, options)
52
+ end
53
+
54
+ protected
55
+
56
+ # Creates new immutable collection from the current one,
57
+ # updated with either the module's singleton method,
58
+ # or the proc having been imported from another module.
59
+ #
60
+ # @param [Symbol] source The name of the method, or imported proc
61
+ # @param [Hash] options
62
+ # @option options [Module] :from
63
+ # The module whose method or imported proc should be added
64
+ # @option options [Symbol] :as
65
+ # The key for the proc in the current collection
66
+ #
67
+ # @return [Transproc::Store]
68
+ #
69
+ def import_method(source, options)
70
+ src = options.fetch(:from)
71
+ name = source.to_sym
72
+ key = options.fetch(:as) { name }.to_sym
73
+ fn = src.is_a?(Registry) ? src.fetch(name) : src.method(name)
74
+
75
+ self.class.new(methods.merge(key => fn))
76
+ end
77
+
78
+ # Creates new immutable collection from the current one,
79
+ # updated with all singleton methods and imported methods
80
+ # from the other module
81
+ #
82
+ # @param [Module] source The module to import procedures from
83
+ #
84
+ # @return [Transproc::Store]
85
+ #
86
+ def import_methods(source)
87
+ list = source.public_methods - Registry.instance_methods - Module.methods
88
+ list -= [:initialize] # for compatibility with Rubinius
89
+ list += source.store.methods.keys if source.is_a? Registry
90
+
91
+ list.inject(self) { |a, e| a.import_method(e, from: source) }
92
+ end
93
+ end # class Store
94
+ end # module Transproc
@@ -0,0 +1,11 @@
1
+ module Transproc
2
+ module Deprecations
3
+ def self.announce(name, msg)
4
+ warn <<-MSG.gsub(/^\s+/, '')
5
+ #{name} is deprecated and will be removed in 1.0.0.
6
+ #{msg}
7
+ #{caller.detect { |l| !l.include?('lib/transproc')}}
8
+ MSG
9
+ end
10
+ end
11
+ end
@@ -1,3 +1,3 @@
1
1
  module Transproc
2
- VERSION = '0.2.4'.freeze
2
+ VERSION = '0.3.0'.freeze
3
3
  end
@@ -1,9 +1,11 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Transproc::ArrayTransformations do
4
+ let(:hashes) { Transproc::HashTransformations }
5
+
4
6
  describe '.extract_key' do
5
7
  it 'extracts values by key from all hashes' do
6
- extract_key = t(:extract_key, 'name')
8
+ extract_key = described_class.t(:extract_key, 'name')
7
9
 
8
10
  original = [
9
11
  { 'name' => 'Alice', 'role' => 'sender' },
@@ -22,7 +24,7 @@ describe Transproc::ArrayTransformations do
22
24
 
23
25
  describe '.extract_key!' do
24
26
  it 'extracts values by key from all hashes' do
25
- extract_key = t(:extract_key!, 'name')
27
+ extract_key = described_class.t(:extract_key!, 'name')
26
28
 
27
29
  input = [
28
30
  { 'name' => 'Alice', 'role' => 'sender' },
@@ -39,7 +41,7 @@ describe Transproc::ArrayTransformations do
39
41
 
40
42
  describe '.insert_key' do
41
43
  it 'wraps values to tuples with given key' do
42
- insert_key = t(:insert_key, 'name')
44
+ insert_key = described_class.t(:insert_key, 'name')
43
45
 
44
46
  original = ['Alice', 'Bob', nil]
45
47
 
@@ -58,7 +60,7 @@ describe Transproc::ArrayTransformations do
58
60
 
59
61
  describe '.insert_key!' do
60
62
  it 'wraps values to tuples with given key' do
61
- insert_key = t(:insert_key!, 'name')
63
+ insert_key = described_class.t(:insert_key!, 'name')
62
64
 
63
65
  original = ['Alice', 'Bob', nil]
64
66
 
@@ -77,7 +79,7 @@ describe Transproc::ArrayTransformations do
77
79
 
78
80
  describe '.add_keys' do
79
81
  it 'returns a new array with missed keys added to tuples' do
80
- add_keys = t(:add_keys, [:foo, :bar, :baz])
82
+ add_keys = described_class.t(:add_keys, [:foo, :bar, :baz])
81
83
 
82
84
  original = [{ foo: 'bar' }, { bar: 'baz' }]
83
85
 
@@ -95,7 +97,7 @@ describe Transproc::ArrayTransformations do
95
97
 
96
98
  describe '.add_keys!' do
97
99
  it 'adds missed keys to tuples' do
98
- add_keys = t(:add_keys!, [:foo, :bar, :baz])
100
+ add_keys = described_class.t(:add_keys!, [:foo, :bar, :baz])
99
101
 
100
102
  original = [{ foo: 'bar' }, { bar: 'baz' }]
101
103
 
@@ -113,7 +115,7 @@ describe Transproc::ArrayTransformations do
113
115
 
114
116
  describe '.map_array' do
115
117
  it 'applies funtions to all values' do
116
- map = t(:map_array, t(:symbolize_keys))
118
+ map = described_class.t(:map_array, hashes[:symbolize_keys])
117
119
 
118
120
  original = [
119
121
  { 'name' => 'Jane', 'title' => 'One' },
@@ -134,7 +136,7 @@ describe Transproc::ArrayTransformations do
134
136
 
135
137
  describe '.map_array!' do
136
138
  it 'updates array with the result of the function applied to each value' do
137
- map = t(:map_array!, t(:symbolize_keys))
139
+ map = described_class.t(:map_array!, hashes[:symbolize_keys])
138
140
 
139
141
  input = [
140
142
  { 'name' => 'Jane', 'title' => 'One' },
@@ -154,7 +156,7 @@ describe Transproc::ArrayTransformations do
154
156
 
155
157
  describe '.wrap' do
156
158
  it 'returns a new array with wrapped hashes' do
157
- wrap = t(:wrap, :task, [:title])
159
+ wrap = described_class.t(:wrap, :task, [:title])
158
160
 
159
161
  input = [{ name: 'Jane', title: 'One' }]
160
162
  output = [{ name: 'Jane', task: { title: 'One' } }]
@@ -164,10 +166,10 @@ describe Transproc::ArrayTransformations do
164
166
 
165
167
  it 'returns a array new with deeply wrapped hashes' do
166
168
  wrap =
167
- t(
169
+ described_class.t(
168
170
  :map_array,
169
- t(:nest, :user, [:name, :title]) +
170
- t(:map_value, :user, t(:nest, :task, [:title]))
171
+ hashes[:nest, :user, [:name, :title]] +
172
+ hashes[:map_value, :user, hashes[:nest, :task, [:title]]]
171
173
  )
172
174
 
173
175
  input = [{ name: 'Jane', title: 'One' }]
@@ -177,7 +179,7 @@ describe Transproc::ArrayTransformations do
177
179
  end
178
180
 
179
181
  it 'adds data to the existing tuples' do
180
- wrap = t(:wrap, :task, [:title])
182
+ wrap = described_class.t(:wrap, :task, [:title])
181
183
 
182
184
  input = [{ name: 'Jane', task: { priority: 1 }, title: 'One' }]
183
185
  output = [{ name: 'Jane', task: { priority: 1, title: 'One' } }]
@@ -187,7 +189,7 @@ describe Transproc::ArrayTransformations do
187
189
  end
188
190
 
189
191
  describe '.group' do
190
- subject(:group) { t(:group, :tasks, [:title]) }
192
+ subject(:group) { described_class.t(:group, :tasks, [:title]) }
191
193
 
192
194
  it 'returns a new array with grouped hashes' do
193
195
  input = [{ name: 'Jane', title: 'One' }, { name: 'Jane', title: 'Two' }]
@@ -241,7 +243,7 @@ describe Transproc::ArrayTransformations do
241
243
  end
242
244
 
243
245
  describe '.ungroup' do
244
- subject(:ungroup) { t(:ungroup, :tasks, [:title]) }
246
+ subject(:ungroup) { described_class.t(:ungroup, :tasks, [:title]) }
245
247
 
246
248
  it 'returns a new array with ungrouped hashes' do
247
249
  input = [{ name: 'Jane', tasks: [{ title: 'One' }, { title: 'Two' }] }]
@@ -294,8 +296,8 @@ describe Transproc::ArrayTransformations do
294
296
  end
295
297
 
296
298
  describe '.combine' do
297
- let(:input) do
298
- [
299
+ it 'merges hashes from arrays using provided join keys' do
300
+ input = [
299
301
  # parent users
300
302
  [
301
303
  { name: 'Jane', email: 'jane@doe.org' },
@@ -319,22 +321,24 @@ describe Transproc::ArrayTransformations do
319
321
  ]
320
322
  ]
321
323
  ]
322
- end
323
324
 
324
- let(:output) do
325
- [
325
+ output = [
326
326
  { name: 'Jane', email: 'jane@doe.org', tasks: [
327
327
  { user: 'Jane', title: 'One', tags: [{ task: 'One', tag: 'red' }] },
328
328
  { user: 'Jane', title: 'Two', tags: [] }]
329
329
  },
330
- { name: 'Joe', email: 'joe@doe.org', tasks: [
331
- { user: 'Joe', title: 'Three', tags: [{ task: 'Three', tag: 'blue' }] }]
330
+ {
331
+ name: 'Joe', email: 'joe@doe.org', tasks: [
332
+ {
333
+ user: 'Joe', title: 'Three', tags: [
334
+ { task: 'Three', tag: 'blue' }
335
+ ]
336
+ }
337
+ ]
332
338
  }
333
339
  ]
334
- end
335
340
 
336
- it 'merges hashes from arrays using provided join keys' do
337
- combine = t(:combine, [
341
+ combine = described_class.t(:combine, [
338
342
  [:tasks, { name: :user }, [[:tags, title: :task]]]
339
343
  ])
340
344