symbiont-ruby 0.0.0 → 0.1.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.
@@ -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