core-pipeline 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 951f6c7809d29d8048837b0b24d497af8b1566c7990c9e0b296e556ff7b839ff
4
- data.tar.gz: 8151a7bff9a6cc159c604e6a15c9182e454a65f6aad577ea94ccf30e3d0c46d4
3
+ metadata.gz: 8a6b3ef16c4e8c95532c84fbcfeffa10e3d8b17b710f8a829ea0ea3735039195
4
+ data.tar.gz: e29f64c2b55dda1e75d022c4cca2f6a2c80fe00e13760ea678335abf74b57da4
5
5
  SHA512:
6
- metadata.gz: 56dd23992f750ff65a2585df138c93ae44e8d9cf2e5f511cc96ff0d1143d577f6b7035b45968f43f255bb5990ef0960de9106433e784f0481ae94b207d092acf
7
- data.tar.gz: 815e79983f3a73e589f1e0be6c170de6bc853ff541fdcdecfc1761cc572d5b1b626976af83b9278a234c755411d9ca02d11c8a238eba0f61b9d698077658fe06
6
+ metadata.gz: ccb44ec883364a2a633407dc257f44958386b817210278545e9b0291067437e553c6470616e53bab83876d04db2cebe0100ad727971a68aa955ebff7c3a87f9b
7
+ data.tar.gz: 6c1bb9fb0905caf54a3aef8b33bb224bc23779a66d66353b48345f61fe8a8e27c54653c65cf0d563f8073e387263675e9afe4ba0245bcfc9cf03fd8465a6b22b
data/CHANGELOG.md CHANGED
@@ -1,7 +1,15 @@
1
+ ## [v0.2.0](https://github.com/metabahn/corerb/releases/tag/2021-10-24)
2
+
3
+ *released on 2021-10-24*
4
+
5
+ * `chg` [#77](https://github.com/metabahn/corerb/pull/77) Add recompile support to pipelines ([bryanp](https://github.com/bryanp))
6
+
1
7
  ## [v0.1.0](https://github.com/metabahn/corerb/releases/tag/2021-07-15)
2
8
 
3
9
  *released on 2021-07-15*
4
10
 
11
+ * `fix` [#81](https://github.com/metabahn/corerb/pull/81) Tie up loose ends around curried pipeline actions ([bryanp](https://github.com/bryanp))
12
+ * `add` [#80](https://github.com/metabahn/corerb/pull/80) Define curried pipeline actions ([bryanp](https://github.com/bryanp))
5
13
  * `fix` [#69](https://github.com/metabahn/corerb/pull/69) Allow pipelines to be called at the class level ([bryanp](https://github.com/bryanp))
6
14
  * `add` [#64](https://github.com/metabahn/corerb/pull/64) Add a method for checking if a callable pipeline will call any actions ([bryanp](https://github.com/bryanp))
7
15
  * `chg` [#63](https://github.com/metabahn/corerb/pull/63) Improve inspect output for callable pipelines ([bryanp](https://github.com/bryanp))
@@ -15,21 +15,21 @@ module Core
15
15
  class << self
16
16
  # [public] Builds an action for a given target.
17
17
  #
18
- def build(target = nil, *args, before: nil, after: nil, context: nil, &block)
18
+ def build(target = nil, *args, before: nil, after: nil, context: nil, curry: false, &block)
19
19
  if block
20
- Actions::Block.new(target, before: before, after: after, context: context, &block)
20
+ Actions::Block.new(target, before: before, after: after, context: context, curry: curry, &block)
21
21
  else
22
- build_target(target, *args, before: before, after: after, context: context)
22
+ build_target(target, *args, before: before, after: after, context: context, curry: curry)
23
23
  end
24
24
  end
25
25
 
26
- private def build_target(first, *args, before:, after:, context:)
26
+ private def build_target(first, *args, before:, after:, context:, curry:)
27
27
  case first
28
28
  when String, Symbol
29
- if (target = build_target(args[0], *args[1..], before: before, after: after, context: context))
29
+ if (target = build_target(args[0], *args[1..], before: before, after: after, context: context, curry: curry))
30
30
  target
31
31
  else
32
- Actions::Method.new(first, before: before, after: after, context: context)
32
+ Actions::Method.new(first, before: before, after: after, context: context, curry: curry)
33
33
  end
34
34
  when NilClass
35
35
  nil
@@ -55,11 +55,12 @@ module Core
55
55
 
56
56
  include Is::Inspectable
57
57
 
58
- def initialize(name, before: nil, after: nil, context: nil)
58
+ def initialize(name, before: nil, after: nil, context: nil, curry: false)
59
59
  @name = (name || self.class.build_name).to_sym
60
60
  @before = before
61
61
  @after = after
62
62
  @context = context
63
+ @curry = curry
63
64
  end
64
65
 
65
66
  # [public] The action name.
@@ -74,6 +75,16 @@ module Core
74
75
  #
75
76
  attr_reader :after
76
77
 
78
+ # [public] The context this action should be called in.
79
+ #
80
+ attr_reader :context
81
+
82
+ # [public] Returns `true` if the action should be curried.
83
+ #
84
+ def curried?
85
+ @curry == true
86
+ end
87
+
77
88
  # [public] Finalizes the action for the given context.
78
89
  #
79
90
  def finalize(context)
@@ -10,10 +10,10 @@ module Core
10
10
  class Block < Action
11
11
  include Is::Inspectable
12
12
 
13
- def initialize(name = nil, before: nil, after: nil, context: nil, &block)
13
+ def initialize(name = nil, before: nil, after: nil, context: nil, curry: false, &block)
14
14
  @block = block
15
15
 
16
- super(name, before: before, after: after, context: context)
16
+ super(name, before: before, after: after, context: context, curry: curry)
17
17
  end
18
18
 
19
19
  # [public]
@@ -21,13 +21,19 @@ module Core
21
21
  def finalize(context)
22
22
  case @context
23
23
  when NilClass
24
- context.define_method(@name, &@block)
24
+ unless context.method_defined?(@name)
25
+ context.define_method(@name, &@block)
26
+ end
27
+
25
28
  @name
26
29
  else
27
30
  if @block.binding.receiver.equal?(@context)
28
31
  @block
29
32
  else
30
- @context.define_singleton_method(@name, &@block)
33
+ unless context.singleton_class.method_defined?(@name)
34
+ @context.define_singleton_method(@name, &@block)
35
+ end
36
+
31
37
  @context.method(@name).to_proc
32
38
  end
33
39
  end
@@ -10,10 +10,10 @@ module Core
10
10
  class Method < Action
11
11
  include Is::Inspectable
12
12
 
13
- def initialize(name = nil, before: nil, after: nil, context: nil)
13
+ def initialize(name = nil, before: nil, after: nil, context: nil, curry: false)
14
14
  @method = nil
15
15
 
16
- super(name, before: before, after: after, context: context)
16
+ super(name, before: before, after: after, context: context, curry: curry)
17
17
  end
18
18
 
19
19
  # [public]
@@ -18,11 +18,24 @@ module Core
18
18
  @mutex = Mutex.new
19
19
  @actions = []
20
20
  @skipped = []
21
+ @compiled = false
22
+ end
23
+
24
+ def initialize_copy(...)
25
+ @actions = @actions.clone
26
+ @skipped = @skipped.clone
27
+ super
21
28
  end
22
29
 
23
30
  attr_reader :actions
24
31
  attr_reader :skipped
25
32
 
33
+ # [public] Relocates to another object context.
34
+ #
35
+ def relocate(object)
36
+ @object = object
37
+ end
38
+
26
39
  # [public] Returns true if the pipeline will call any actions.
27
40
  #
28
41
  def any?
@@ -31,14 +44,16 @@ module Core
31
44
 
32
45
  # [public] Defines a pipeline action.
33
46
  #
34
- def action(*args, before: nil, after: nil, context: nil, &block)
35
- @actions << Action.build(*args, before: before, after: after, context: context, &block)
47
+ def action(*args, before: nil, after: nil, context: nil, curry: false, &block)
48
+ @actions << Action.build(*args, before: before, after: after, context: context, curry: curry, &block)
49
+ recompile if compiled?
36
50
  end
37
51
 
38
52
  # [public] Skips the given action.
39
53
  #
40
54
  def skip(name)
41
55
  @skipped << name.to_sym
56
+ recompile if compiled?
42
57
  end
43
58
 
44
59
  # [public] Replaces existing actions with actions from the given pipeline.
@@ -48,6 +63,7 @@ module Core
48
63
  @mutex.synchronize do
49
64
  @actions.clear
50
65
  @actions.concat(pipeline.pipeline.actions)
66
+ recompile if compiled?
51
67
  end
52
68
  else
53
69
  raise ArgumentError, "expected a pipeline"
@@ -59,6 +75,7 @@ module Core
59
75
  def include(pipeline)
60
76
  if (pipeline.is_a?(Class) || pipeline.is_a?(Module)) && pipeline.ancestors.include?(Is::Pipeline)
61
77
  @actions.concat(pipeline.pipeline.actions)
78
+ recompile if compiled?
62
79
  else
63
80
  raise ArgumentError, "expected a pipeline"
64
81
  end
@@ -70,6 +87,7 @@ module Core
70
87
  if (pipeline.is_a?(Class) || pipeline.is_a?(Module)) && pipeline.ancestors.include?(Is::Pipeline)
71
88
  pipeline.pipeline.actions.each do |action|
72
89
  @actions.delete(action)
90
+ recompile if compiled?
73
91
  end
74
92
  else
75
93
  raise ArgumentError, "expected a pipeline"
@@ -84,9 +102,18 @@ module Core
84
102
 
85
103
  private def compile
86
104
  instance_eval(Compiler.compile(self, @object), __FILE__, __LINE__ - 1)
87
-
105
+ @compiled = true
88
106
  self
89
107
  end
108
+
109
+ private def recompile
110
+ singleton_class.remove_method(:call)
111
+ compile
112
+ end
113
+
114
+ private def compiled?
115
+ @compiled == true
116
+ end
90
117
  end
91
118
  end
92
119
  end
@@ -28,7 +28,11 @@ module Core
28
28
 
29
29
  compiled << case action
30
30
  when Symbol
31
- "object.#{action}(...); "
31
+ if object.private_method_defined?(action)
32
+ "object.send(#{action.inspect}, ...); "
33
+ else
34
+ "object.#{action}(...); "
35
+ end
32
36
  else
33
37
  "@__finalized_actions[#{object_id}].call(...); "
34
38
  end
@@ -57,7 +61,13 @@ module Core
57
61
  ordered_actions(callable).reject { |action|
58
62
  callable.skipped.include?(action.name)
59
63
  }.map { |action|
60
- action.finalize(object)
64
+ finalized = action.finalize(object)
65
+
66
+ if action.curried?
67
+ wrap_finalized_action_for_context(finalized, action.context || object)
68
+ else
69
+ finalized
70
+ end
61
71
  }.each_with_object({}) { |action, lookup|
62
72
  lookup[action.object_id] = action
63
73
  }
@@ -113,6 +123,205 @@ module Core
113
123
 
114
124
  ordered
115
125
  end
126
+
127
+ private def wrap_finalized_action_for_context(finalized, context)
128
+ case finalized
129
+ when Symbol
130
+ wrapped_name = :"curried_#{finalized}"
131
+ method = context.instance_method(finalized)
132
+ signature = [
133
+ build_method_arguments_signature(method),
134
+ build_method_keywords_signature(method),
135
+ "&block"
136
+ ].compact.join(", ")
137
+
138
+ code = <<~CODE
139
+ def #{wrapped_name}(*args, **kwargs, &block)
140
+ #{finalized}(#{signature})
141
+ end
142
+ CODE
143
+
144
+ context.class_eval(code, __FILE__, __LINE__ - 1)
145
+
146
+ wrapped_name
147
+ else
148
+ if callable_accepts_keyword_arguments?(finalized)
149
+ finalized_keywords = callable_keywords(finalized)
150
+
151
+ if callable_accepts_arguments?(finalized)
152
+ argument_count = callable_arguments(finalized).count
153
+
154
+ if callable_accepts_keyword_arguments_splat?(finalized)
155
+ if callable_accepts_arguments_splat?(finalized)
156
+ if RbConfig::CONFIG["RUBY_PROGRAM_VERSION"] < "3"
157
+ proc { |*args, **kwargs, &block|
158
+ finalized.call(*args.take(argument_count), *args[argument_count..], **kwargs.slice(*finalized_keywords), **kwargs.reject { |kwarg, _| finalized_keywords.include?(kwarg) }, &block)
159
+ }
160
+ else
161
+ proc { |*args, **kwargs, &block|
162
+ finalized.call(*args.take(argument_count), *args[argument_count..], **kwargs.slice(*finalized_keywords), **kwargs.except(*finalized_keywords), &block)
163
+ }
164
+ end
165
+ elsif RbConfig::CONFIG["RUBY_PROGRAM_VERSION"] < "3"
166
+ proc { |*args, **kwargs, &block|
167
+ finalized.call(*args.take(argument_count), **kwargs.slice(*finalized_keywords), **kwargs.reject { |kwarg, _| finalized_keywords.include?(kwarg) }, &block)
168
+ }
169
+ else
170
+ proc { |*args, **kwargs, &block|
171
+ finalized.call(*args.take(argument_count), **kwargs.slice(*finalized_keywords), **kwargs.except(*finalized_keywords), &block)
172
+ }
173
+ end
174
+ elsif callable_accepts_arguments_splat?(finalized)
175
+ proc { |*args, **kwargs, &block|
176
+ finalized.call(*args.take(argument_count), *args[argument_count..], **kwargs.slice(*finalized_keywords), &block)
177
+ }
178
+ else
179
+ proc { |*args, **kwargs, &block|
180
+ finalized.call(*args.take(argument_count), **kwargs.slice(*finalized_keywords), &block)
181
+ }
182
+ end
183
+ elsif callable_accepts_keyword_arguments_splat?(finalized)
184
+ if callable_accepts_arguments_splat?(finalized)
185
+ if RbConfig::CONFIG["RUBY_PROGRAM_VERSION"] < "3"
186
+ proc { |*args, **kwargs, &block|
187
+ finalized.call(*args, **kwargs.slice(*finalized_keywords), **kwargs.reject { |kwarg, _| finalized_keywords.include?(kwarg) }, &block)
188
+ }
189
+ else
190
+ proc { |*args, **kwargs, &block|
191
+ finalized.call(*args, **kwargs.slice(*finalized_keywords), **kwargs.except(*finalized_keywords), &block)
192
+ }
193
+ end
194
+ elsif RbConfig::CONFIG["RUBY_PROGRAM_VERSION"] < "3"
195
+ proc { |*, **kwargs, &block|
196
+ finalized.call(**kwargs.slice(*finalized_keywords), **kwargs.reject { |kwarg, _| finalized_keywords.include?(kwarg) }, &block)
197
+ }
198
+ else
199
+ proc { |*, **kwargs, &block|
200
+ finalized.call(**kwargs.slice(*finalized_keywords), **kwargs.except(*finalized_keywords), &block)
201
+ }
202
+ end
203
+ elsif callable_accepts_arguments_splat?(finalized)
204
+ proc { |*args, **kwargs, &block|
205
+ finalized.call(*args, **kwargs.slice(*finalized_keywords), &block)
206
+ }
207
+ else
208
+ proc { |*, **kwargs, &block|
209
+ finalized.call(**kwargs.slice(*finalized_keywords), &block)
210
+ }
211
+ end
212
+ elsif callable_accepts_arguments?(finalized)
213
+ argument_count = callable_arguments(finalized).count
214
+
215
+ if callable_accepts_keyword_arguments_splat?(finalized)
216
+ if callable_accepts_arguments_splat?(finalized)
217
+ proc { |*args, **kwargs, &block|
218
+ finalized.call(*args.take(argument_count), *args[argument_count..], **kwargs, &block)
219
+ }
220
+ else
221
+ proc { |*args, **kwargs, &block|
222
+ finalized.call(*args.take(argument_count), **kwargs, &block)
223
+ }
224
+ end
225
+ elsif callable_accepts_arguments_splat?(finalized)
226
+ proc { |*args, **, &block|
227
+ finalized.call(*args.take(argument_count), *args[argument_count..], &block)
228
+ }
229
+ else
230
+ proc { |*args, **, &block|
231
+ finalized.call(*args.take(argument_count), &block)
232
+ }
233
+ end
234
+ elsif callable_accepts_keyword_arguments_splat?(finalized)
235
+ if callable_accepts_arguments_splat?(finalized)
236
+ proc { |*args, **kwargs, &block|
237
+ finalized.call(*args, **kwargs, &block)
238
+ }
239
+ else
240
+ proc { |*, **kwargs, &block|
241
+ finalized.call(**kwargs, &block)
242
+ }
243
+ end
244
+ elsif callable_accepts_arguments_splat?(finalized)
245
+ proc { |*args, **, &block|
246
+ finalized.call(*args, &block)
247
+ }
248
+ else
249
+ proc { |*, **, &block|
250
+ finalized.call(&block)
251
+ }
252
+ end
253
+ end
254
+ end
255
+
256
+ private def build_method_arguments_signature(method)
257
+ return if method.arity == 0
258
+
259
+ arguments = callable_arguments(method)
260
+
261
+ if arguments.any?
262
+ if callable_accepts_arguments_splat?(method)
263
+ "*args.take(#{arguments.count}), *args[#{arguments.count}..]"
264
+ else
265
+ "*args.take(#{arguments.count})"
266
+ end
267
+ elsif callable_accepts_arguments_splat?(method)
268
+ "*args"
269
+ end
270
+ end
271
+
272
+ private def build_method_keywords_signature(method)
273
+ return if method.arity == 0
274
+
275
+ keywords = callable_keywords(method)
276
+
277
+ if keywords.any?
278
+ keywords_string = keywords.map { |keyword| ":#{keyword}" }.join(", ")
279
+
280
+ if callable_accepts_keyword_arguments_splat?(method)
281
+ if RbConfig::CONFIG["RUBY_PROGRAM_VERSION"] < "3"
282
+ "**kwargs.slice(#{keywords_string}), **kwargs.reject { |kwarg, _| [#{keywords_string}].include?(kwarg) }"
283
+ else
284
+ "**kwargs.slice(#{keywords_string}), **kwargs.except(*#{keywords_string})"
285
+ end
286
+ else
287
+ "**kwargs.slice(#{keywords_string})"
288
+ end
289
+ elsif callable_accepts_keyword_arguments_splat?(method)
290
+ "**kwargs"
291
+ end
292
+ end
293
+
294
+ private def callable_accepts_arguments_splat?(callable)
295
+ callable.parameters.any? { |type, _| type == :rest }
296
+ end
297
+
298
+ private def callable_accepts_keyword_arguments_splat?(callable)
299
+ callable.parameters.any? { |type, _| type == :keyrest }
300
+ end
301
+
302
+ private def callable_accepts_arguments?(callable)
303
+ callable.parameters.any? { |type, _| type == :req || type == :opt }
304
+ end
305
+
306
+ private def callable_accepts_keyword_arguments?(callable)
307
+ callable.parameters.any? { |type, _| type == :keyreq || type == :key }
308
+ end
309
+
310
+ private def callable_arguments(callable)
311
+ callable.parameters.select { |type, _|
312
+ type == :req || type == :opt
313
+ }.map { |_, name|
314
+ name
315
+ }
316
+ end
317
+
318
+ private def callable_keywords(callable)
319
+ callable.parameters.select { |type, _|
320
+ type == :keyreq || type == :key
321
+ }.map { |_, name|
322
+ name
323
+ }
324
+ end
116
325
  end
117
326
  end
118
327
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Core
4
4
  module Pipeline
5
- VERSION = "0.1.1"
5
+ VERSION = "0.2.0"
6
6
 
7
7
  def self.version
8
8
  VERSION
data/lib/is/pipeline.rb CHANGED
@@ -27,8 +27,8 @@ module Is
27
27
  extends :definition do
28
28
  # [public] Defines a pipeline action.
29
29
  #
30
- def action(*args, before: nil, after: nil, context: nil, &block)
31
- pipeline.action(*args, before: before, after: after, context: context, &block)
30
+ def action(*args, before: nil, after: nil, context: nil, curry: false, &block)
31
+ pipeline.action(*args, before: before, after: after, context: context, curry: curry, &block)
32
32
  end
33
33
 
34
34
  # [public] Skips the given action.
@@ -62,6 +62,41 @@ module Is
62
62
  def call(...)
63
63
  @pipeline.call(self, ...)
64
64
  end
65
+
66
+ # [public] Defines a pipeline action, isolated to the instance.
67
+ #
68
+ def action(*args, before: nil, after: nil, context: nil, curry: false, &block)
69
+ pipeline.relocate(singleton_class)
70
+ pipeline.action(*args, before: before, after: after, context: context, curry: curry, &block)
71
+ end
72
+
73
+ # [public] Skips the given action, isolated to the instance.
74
+ #
75
+ def skip_action(name)
76
+ pipeline.relocate(singleton_class)
77
+ pipeline.skip(name)
78
+ end
79
+
80
+ # [public] Replaces existing actions with actions from the given pipeline, isolated to the instance.
81
+ #
82
+ def use_pipeline(other_pipeline)
83
+ pipeline.relocate(singleton_class)
84
+ pipeline.use(other_pipeline)
85
+ end
86
+
87
+ # [public] Includes actions from the given pipeline, isolated to the instance.
88
+ #
89
+ def include_pipeline(other_pipeline)
90
+ pipeline.relocate(singleton_class)
91
+ pipeline.include(other_pipeline)
92
+ end
93
+
94
+ # [public] Excludes actions from the given pipeline, isolated to the instance.
95
+ #
96
+ def exclude_pipeline(other_pipeline)
97
+ pipeline.relocate(singleton_class)
98
+ pipeline.exclude(other_pipeline)
99
+ end
65
100
  end
66
101
 
67
102
  extends :implementation, prepend: true do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: core-pipeline
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bryan Powell
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-07-16 00:00:00.000000000 Z
11
+ date: 2021-10-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: core-extension
@@ -89,7 +89,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
89
89
  - !ruby/object:Gem::Version
90
90
  version: '0'
91
91
  requirements: []
92
- rubygems_version: 3.2.15
92
+ rubygems_version: 3.2.22
93
93
  signing_key:
94
94
  specification_version: 4
95
95
  summary: Turns Ruby objects into pipelines.