transproc 0.2.4 → 0.3.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.
@@ -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