symbiont-ruby 0.0.0 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Symbiont
4
+ # Mediator service object that controls the logic of creating triggers and calling them.
5
+ # Acts as a factory for trigerrs and a mediator for the proc invocation by them.
6
+ #
7
+ # @api public
8
+ # @since 0.1.0
9
+ module Executor
10
+ class << self
11
+ # Starts execution of a proc object in the context of the passed object with the selected
12
+ # direction of method dispatching. Delegates execution to a public trigger.
13
+ #
14
+ # @param required_context [Object]
15
+ # @param context_direction [Array<Symbol>]
16
+ # @param closure [Proc]
17
+ # @return void
18
+ #
19
+ # @see Symbiont::Trigger#__evaluate__
20
+ #
21
+ # @api public
22
+ # @since 0.1.0
23
+ def evaluate(required_context, context_direction = Trigger::IOK, &closure)
24
+ public_trigger(required_context, context_direction, &closure).__evaluate__
25
+ end
26
+
27
+ # Starts execution of a proc object in the context of the passed object with the selected
28
+ # direction of method dispatching. Delegates execution to a private trigger.
29
+ #
30
+ # @param required_context [Object]
31
+ # @param context_direction [Array<Symbol>]
32
+ # @param closure [Proc]
33
+ # @return void
34
+ #
35
+ # @see Symbiont::Trigger#__evaluate__
36
+ #
37
+ # @api public
38
+ # @since 0.1.0
39
+ def evaluate_private(required_context, context_direction = Trigger::IOK, &closure)
40
+ private_trigger(required_context, context_direction, &closure).__evaluate__
41
+ end
42
+
43
+ # Factory method that instantiates a public trigger with the desired execution context,
44
+ # the direction of method dispatching and the closure that needs to be performed.
45
+ #
46
+ # @param context_direction [Array<Symbol>]
47
+ # @param closure [Proc]
48
+ # @return [Symbiont::PublicTrigger]
49
+ #
50
+ # @see Symbiont::PublicTrigger
51
+ # @see Symbiont::Trigger
52
+ #
53
+ # @api public
54
+ # @since 0.1.0
55
+ def public_trigger(required_context, context_direction = Trigger::IOK, &closure)
56
+ PublicTrigger.new(required_context, closure, context_direction)
57
+ end
58
+
59
+ # Factory method that instantiates a private trigger with the desired execution context,
60
+ # the direction of method dispatching and the closure that needs to be performed.
61
+ #
62
+ # @param required_context [Any]
63
+ # @param context_direction [Array<Symbol>]
64
+ # @param closure [Proc]
65
+ # @return [Symbiont::PrivateTrigger]
66
+ #
67
+ # @see Symbiont::PrivateTrigger
68
+ # @see Symbiont::Trigger
69
+ #
70
+ # @api public
71
+ # @since 0.1.0
72
+ def private_trigger(required_context, context_direction = Trigger::IOK, &closure)
73
+ PrivateTrigger.new(required_context, closure, context_direction)
74
+ end
75
+
76
+ # Gets the method object taken from the context that can respond to it.
77
+ # Considers only public methods.
78
+ #
79
+ # @param method_name [Symbol,String]
80
+ # @param required_context [Any]
81
+ # @param context_direction [Array<Symbol>]
82
+ # @param closure [Proc]
83
+ # @return [Method]
84
+ #
85
+ # @see Symbiont::Trigger#method
86
+ #
87
+ # @api public
88
+ # @since 0.1.0
89
+ def public_method(method_name, required_context, context_direction = Trigger::IOK, &closure)
90
+ public_trigger(required_context, context_direction, &closure).method(method_name)
91
+ end
92
+
93
+ # Gets the method object taken from the context that can respond to it.
94
+ # Considers private methods and public methods.
95
+ #
96
+ # @param method_name [Symbol,String]
97
+ # @param required_context [Any]
98
+ # @param context_direction [Array<Symbol>]
99
+ # @param closure [Proc]
100
+ # @return [Method]
101
+ #
102
+ # @see Symbiont::Trigger#method
103
+ #
104
+ # @api public
105
+ # @since 0.1.0
106
+ def private_method(method_name, required_context, context_direction = Trigger::IOK, &closure)
107
+ private_trigger(required_context, context_direction, &closure).method(method_name)
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Symbiont
4
+ # A trigger that considers both public and private methods of executable contexts
5
+ # during method dispatching.
6
+ #
7
+ # @api private
8
+ # @since 0.1.0
9
+ class PrivateTrigger < Trigger
10
+ # @param method_name [String,Symbol]
11
+ # @option include_private [Boolean]
12
+ # @raise NoMethodError
13
+ # @return [Objcet]
14
+ #
15
+ # @see Symbiont::Trigger#__actual_context__
16
+ #
17
+ # @since 0.1.0
18
+ def __actual_context__(method_name)
19
+ __directed_contexts__.find do |context|
20
+ context.respond_to?(method_name, true)
21
+ end || super
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Symbiont
4
+ # A trigger that considers only public methods of executable contexts
5
+ # during method dispatching.
6
+ #
7
+ # @api private
8
+ # @since 0.1.0
9
+ class PublicTrigger < Trigger
10
+ # @param method_name [String,Symbol]
11
+ # @option include_private [Boolean]
12
+ # @raise NoMethodError
13
+ # @return [Object]
14
+ #
15
+ # @see Symbiont::Trigger#__actual_context__
16
+ #
17
+ # @since 0.1.0
18
+ def __actual_context__(method_name)
19
+ __directed_contexts__.find do |context|
20
+ context.respond_to?(method_name, false)
21
+ end || super
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,269 @@
1
+ # frozen_string_literal: true
2
+
3
+ # A class that controls the logic of closure execution in the context of other objects.
4
+ # Responsible for dispatching the methods which should be executed in a certain context.
5
+ # Delegation variations depends on the order of contexts.
6
+ #
7
+ # Trigger supports 3 contexts:
8
+ #
9
+ # * closure context;
10
+ # * passed object's context;
11
+ # * global ::Kernel context.
12
+ #
13
+ # If no context is able to respond to the required method - ContextNoMethodError exception is raised
14
+ # (ContextNoMethodError inherits from NoMethodError).
15
+ #
16
+ # @api private
17
+ # @since 0.1.0
18
+ class Symbiont::Trigger < BasicObject
19
+ # Indicates the direction of context method resolving algorithm.
20
+ # Direction: initial context => outer context => kernel context.
21
+ #
22
+ # @return [Array<Symbol>]
23
+ #
24
+ # @api public
25
+ # @since 0.1.0
26
+ IOK = %i[__inner_context__ __outer_context__ __kernel_context__].freeze
27
+
28
+ # Indicates the direction of context method resolving algorithm.
29
+ # Direction: outer context => initial context => kernel context.
30
+ #
31
+ # @return [Array<Symbol>]
32
+ #
33
+ # @api public
34
+ # @since 0.1.0
35
+ OIK = %i[__outer_context__ __inner_context__ __kernel_context__].freeze
36
+
37
+ # Indicates the direction of context method resolving algorithm.
38
+ # Direction: outer context => kernel context => initial context.
39
+ #
40
+ # @return [Array<Symbol>]
41
+ #
42
+ # @api public
43
+ # @since 0.1.0
44
+ OKI = %i[__outer_context__ __kernel_context__ __inner_context__].freeze
45
+
46
+ # Indicates the direction of context method resolving algorithm.
47
+ # Direction: initial context => kernel context => outer context.
48
+ #
49
+ # @return [Array<Symbol>]
50
+ #
51
+ # @api public
52
+ # @since 0.1.0
53
+ IKO = %i[__inner_context__ __kernel_context__ __outer_context__].freeze
54
+
55
+ # Indicates the direction of context method resolving algorithm.
56
+ # Direction: kernel context => outer context => initial context.
57
+ #
58
+ # @return [Array<Symbol>]
59
+ #
60
+ # @api public
61
+ # @since 0.1.0
62
+ KOI = %i[__kernel_context__ __outer_context__ __inner_context__].freeze
63
+
64
+ # Indicates the direction of context method resolving algorithm.
65
+ # Direction: kernel context => initial context => outer context.
66
+ #
67
+ # @return [Array<Symbol>]
68
+ #
69
+ # api public
70
+ # @since 0.1.0
71
+ KIO = %i[__kernel_context__ __inner_context__ __outer_context__].freeze
72
+
73
+ # Is raised when closure (__outer_context__ instance attribute) isnt a Proc.
74
+ #
75
+ # @api public
76
+ # @since 0.1.0
77
+ IncompatibleclosureObjectError = ::Class.new(::ArgumentError)
78
+
79
+ # Is raised when chosen direction (__context_direction__ instance attribute) is not supported
80
+ # by a trigger. Supports only: OIK, OKI, IOK, IKO, KOI, KIO.
81
+ #
82
+ # @api public
83
+ # @since 0.1.0
84
+ IncompatibleContextDirectionError = ::Class.new(::ArgumentError)
85
+
86
+ # Is raised when no one is able to respond to the required method.
87
+ #
88
+ # @see #__actual_context__
89
+ #
90
+ # @api public
91
+ # @since 0.1.0
92
+ ContextNoMethodError = ::Class.new(::NoMethodError)
93
+
94
+ # Returns an object that should be used as the main context for
95
+ # context method resolving algorithm.
96
+ #
97
+ # @return [Object]
98
+ #
99
+ # @since 0.1.0
100
+ attr_reader :__inner_context__
101
+
102
+ # Returns a binding object of corresponding closure (see __closure__).
103
+ # Used as an outer context for context method resolving algorithm.
104
+ #
105
+ # @return [Object]
106
+ #
107
+ # @api private
108
+ # @since 0.1.0
109
+ attr_reader :__outer_context__
110
+
111
+ # Returns Kernel object that will be used as Kernel context for
112
+ # context method resolving algorithm.
113
+ #
114
+ # @return [::Kernel]
115
+ #
116
+ # @api private
117
+ # @since 0.1.0
118
+ attr_reader :__kernel_context__
119
+
120
+ # Returns proc object that will be triggered in many contexts: initial, outer and kernel.
121
+ #
122
+ # @return [Proc]
123
+ #
124
+ # @api private
125
+ # @since 0.1.0
126
+ attr_reader :__closure__
127
+
128
+ # Returns an array of symbols tha represents the direction of contexts.
129
+ # that represents an access method to each of them.
130
+ #
131
+ # @return [Array<Symbol>]
132
+ #
133
+ # @api private
134
+ # @since 0.1.0
135
+ attr_reader :__context_direction__
136
+
137
+ # Instantiates trigger object with corresponding initial context, closure and context resolving
138
+ # direction.
139
+ #
140
+ # @param initial_context [Object]
141
+ # Main context which should be used for instance_eval on.
142
+ # @param closure [Proc]
143
+ # closure that will be executed in a set of contexts (initial => outer => kernel by default).
144
+ # An actual context (#__actual_context__) will be passed to a closure as an attribute.
145
+ # @raise IncompatibleclosureObjectError
146
+ # Raises when received closure attribte isnt a Proc.
147
+ # @raise IncompatibleContextDirectionError
148
+ # Is raised when chosen direction is not supported by a trigger.
149
+ # Supports only OIK, OKI, IOK, IKO, KOI, KIO (see corresponding constant value above).
150
+ #
151
+ # @api private
152
+ # @since 0.1.0
153
+ def initialize(initial_context, closure, context_direction = IOK)
154
+ unless closure.is_a?(::Proc)
155
+ ::Kernel.raise(IncompatibleclosureObjectError, 'closure attribute should be a proc')
156
+ end
157
+
158
+ # rubocop:disable Layout/SpaceAroundKeyword
159
+ unless(context_direction == IOK || context_direction == OIK || context_direction == OKI ||
160
+ context_direction == IKO || context_direction == KOI || context_direction == KIO)
161
+ ::Kernel.raise(
162
+ IncompatibleContextDirectionError,
163
+ 'Incompatible context direction attribute. ' \
164
+ 'You should use one of this: OIK, OKI, IOK, IKO, KOI, KIO.'
165
+ )
166
+ end
167
+ # rubocop:enable Layout/SpaceAroundKeyword
168
+
169
+ @__closure__ = closure
170
+ @__context_direction__ = context_direction
171
+ @__inner_context__ = initial_context
172
+ @__outer_context__ = ::Kernel.eval('self', closure.binding)
173
+ @__kernel_context__ = ::Kernel
174
+ end
175
+
176
+ # Triggers a closure in multiple contexts.
177
+ #
178
+ # @return void
179
+ #
180
+ # @see #method_missing
181
+ #
182
+ # @api private
183
+ # @since 0.1.0
184
+ def __evaluate__
185
+ instance_eval(&__closure__)
186
+ end
187
+
188
+ # Returns a collection of the all contexts sorted by chosen direction.
189
+ #
190
+ # @return [Array<Object>]
191
+ #
192
+ # @see #__context_direction__
193
+ #
194
+ # @api private
195
+ # @since 0.1.0
196
+ def __directed_contexts__
197
+ __context_direction__.map { |direction| __send__(direction) }
198
+ end
199
+
200
+ # Returns the first context that is able to respond to the required method.
201
+ # The context is chosen in the context direction order (see #__context_direction__).
202
+ # Raises NoMethodError excepition when no one of the contexts are able to respond to
203
+ # the required method.
204
+ # Basicaly, abstract implementation raises NoMethodError.
205
+ #
206
+ # @param method_name [Symbol,String] Method that a context should respond to.
207
+ # @raise NoMethodError
208
+ #
209
+ # @see #__context_direction__
210
+ #
211
+ # @api private
212
+ # @since 0.1.0
213
+ def __actual_context__(method_name)
214
+ ::Kernel.raise ContextNoMethodError, "No one is able to respond to #{method_name}"
215
+ end
216
+
217
+ # Delegates method invocation to the corresponding actual context.
218
+ #
219
+ # @param method_name [String,Symbol] Method name
220
+ # @param arguments [Mixed] Method arguments
221
+ # @param block [Proc] Block
222
+ # @raise NoMethodError
223
+ # Is rased when no one of the contexts are able to respond tothe required method.
224
+ # @return void
225
+ #
226
+ # @see #__actual_context__
227
+ #
228
+ # @api private
229
+ # @since 0.1.0
230
+ def method_missing(method_name, *arguments, &block) # rubocop:disable Style/MethodMissing
231
+ __actual_context__(method_name).send(method_name, *arguments, &block)
232
+ end
233
+
234
+ # Checks that the actual context is able to respond to a required method.
235
+ #
236
+ # @param method_name [String,Symbol] Method name
237
+ # @param _include_private [Boolean] Include private methods
238
+ # @raise NoMethodError
239
+ # Is raised when no one of the contexts are able to respond to the required method.
240
+ # @return [Boolean] Is the actual context able to respond to the required method.
241
+ #
242
+ # @see #method_missing
243
+ # @see #__actual_context__
244
+ #
245
+ # @api private
246
+ # @since 0.1.0
247
+ # :nocov:
248
+ def respond_to_missing?(method_name, _include_private = false)
249
+ !!__actual_context__(method_name)
250
+ end
251
+ # :nocov:
252
+
253
+ # Returns a corresponding metehod object of the actual context.
254
+ #
255
+ # @param method_name [String,Symbol] Method name
256
+ # @raise NoMethodError
257
+ # Is raised when no one of the contexts able to respond to the required method.
258
+ # @return [Method]
259
+ #
260
+ # @see #method_missing
261
+ # @see #respond_to_missing?
262
+ # @see #__actual_context__
263
+ #
264
+ # @api private
265
+ # @since 0.1.0
266
+ def method(method_name)
267
+ __actual_context__(method_name).method(method_name)
268
+ end
269
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Symbiont
4
- VERSION = '0.0.0'
4
+ VERSION = '0.1.0'
5
5
  end
data/lib/symbiont.rb CHANGED
@@ -1,5 +1,42 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # @api public
4
+ # @since 0.1.0
3
5
  module Symbiont
4
6
  require_relative 'symbiont/version'
7
+ require_relative 'symbiont/trigger'
8
+ require_relative 'symbiont/public_trigger'
9
+ require_relative 'symbiont/private_trigger'
10
+ require_relative 'symbiont/executor'
11
+ require_relative 'symbiont/context'
12
+
13
+ # @see Symbiont::Trigger::IOK
14
+ # @api public
15
+ # @since 0.1.0
16
+ IOK = Trigger::IOK
17
+
18
+ # @see Symbiont::Trigger::OIK
19
+ # @api public
20
+ # @since 0.1.0
21
+ OIK = Trigger::OIK
22
+
23
+ # @see Symbiont::Trigger::OKI
24
+ # @api public
25
+ # @since 0.1.0
26
+ OKI = Trigger::OKI
27
+
28
+ # @see Symbiont::Trigger::IKO
29
+ # @api public
30
+ # @since 0.1.0
31
+ IKO = Trigger::IKO
32
+
33
+ # @see Symbiont::Trigger::IOK
34
+ # @api public
35
+ # @since 0.1.0
36
+ KOI = Trigger::KOI
37
+
38
+ # @see Symbiont::Trigger::KIO
39
+ # @api public
40
+ # @since 0.1.0
41
+ KIO = Trigger::KIO
5
42
  end
@@ -1,28 +1,40 @@
1
1
  # coding: utf-8
2
2
 
3
- lib = File.expand_path("../lib", __FILE__)
3
+ lib = File.expand_path('../lib', __FILE__)
4
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
- require "symbiont/version"
5
+ require 'symbiont/version'
6
6
 
7
7
  Gem::Specification.new do |spec|
8
- spec.name = "symbiont-ruby"
9
- spec.version = Symbiont::VERSION
10
- spec.authors = ["Rustam Ibragimov"]
11
- spec.email = ["iamdaiver@icloud.com"]
12
- spec.summary = %q{Write a short summary, because RubyGems requires one.}
13
- spec.description = %q{Write a longer description or delete this line.}
14
- spec.homepage = "https://github.com/0exp/symbiont-ruby"
15
- spec.license = "MIT"
8
+ spec.required_ruby_version = '>= 2.2.7'
16
9
 
17
- spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
- f.match(%r{^(test|spec|features)/})
19
- end
10
+ spec.name = 'symbiont-ruby'
11
+ spec.version = Symbiont::VERSION
12
+ spec.author = 'Rustam Ibragimov'
13
+ spec.email = 'iamdaiver@icloud.com'
14
+ spec.summary = 'Evaluate proc-objects in many contexts simultaneously'
15
+ spec.description = 'Symbiont is a cool implementation of proc-objects execution algorithm: ' \
16
+ 'in the context of other object, but with the preservation of ' \
17
+ 'the closed environment of the proc object and with the ability of ' \
18
+ 'control the method dispatch inside it. A proc object is executed in ' \
19
+ 'three contexts: in the context of required object, in the context of '\
20
+ 'a closed proc\'s environment and in the global (Kernel) context.'
20
21
 
21
- spec.bindir = "exe"
22
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+ spec.homepage = 'https://github.com/0exp/symbiont-ruby'
23
+ spec.license = 'MIT'
24
+ spec.bindir = "bin"
23
25
  spec.require_paths = ["lib"]
24
26
 
25
- spec.add_development_dependency "bundler", "~> 1.16"
26
- spec.add_development_dependency "rake", "~> 10.0"
27
- spec.add_development_dependency "rspec", "~> 3.0"
27
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
28
+ f.match(%r{^(spec|features)/})
29
+ end
30
+
31
+ spec.add_development_dependency "rspec", "~> 3.7"
32
+ spec.add_development_dependency "rubocop", "~> 0.53"
33
+ spec.add_development_dependency "rubocop-rspec", "~> 1.24"
34
+ spec.add_development_dependency "simplecov", "~> 0.15"
35
+ spec.add_development_dependency "simplecov-json", "~> 0.2"
36
+ spec.add_development_dependency "coveralls", "~> 0.7"
37
+ spec.add_development_dependency "pry"
38
+ spec.add_development_dependency "rake"
39
+ spec.add_development_dependency "bundler"
28
40
  end