core-pipeline 0.0.0 → 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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +28 -0
- data/lib/core/pipeline/action.rb +43 -7
- data/lib/core/pipeline/actions/block.rb +22 -11
- data/lib/core/pipeline/actions/method.rb +13 -4
- data/lib/core/pipeline/callable.rb +124 -0
- data/lib/core/pipeline/compiler.rb +250 -29
- data/lib/core/pipeline/signals/halted.rb +4 -0
- data/lib/core/pipeline/signals/rejected.rb +3 -0
- data/lib/core/pipeline/version.rb +1 -1
- data/lib/is/pipeline.rb +54 -38
- metadata +10 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 53ee7741ae5354263c5f7d426eb7ad9b500c01bb4a1ee4c665ce11d054819cea
|
4
|
+
data.tar.gz: 8c7e495b2681c54b59e38cc066987e0f52e3ba8a5673f8285c821a38eea98e9d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 757e21b99dbb927b0d75f4dbd8f036e3235a982aa9a449260bcb4e4ff09f3a20afb788c3ddcd213d2a75d5b44e105f69bf512a6515991cc97282e584e4a617f7
|
7
|
+
data.tar.gz: 241b54f107db94c7fa5afeb1a075d2df1b3cd92c97bc54c9cb3935c283d6b2085d67eb1aeb85d2cb724b0cc6785f3bacb64062233ab50c5150f44bfd70603636
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,31 @@
|
|
1
|
+
## [v0.3.0](https://github.com/metabahn/corerb/releases/tag/2021-11-02)
|
2
|
+
|
3
|
+
*released on 2021-11-02*
|
4
|
+
|
5
|
+
* `chg` [#97](https://github.com/metabahn/corerb/pull/97) Designate internal state with leading and trailing double underscores ([bryanp](https://github.com/bryanp))
|
6
|
+
* `chg` [#89](https://github.com/metabahn/corerb/pull/89) Allow the pipeline compiler to be extended ([bryanp](https://github.com/bryanp))
|
7
|
+
|
8
|
+
## [v0.2.0](https://github.com/metabahn/corerb/releases/tag/2021-10-24)
|
9
|
+
|
10
|
+
*released on 2021-10-24*
|
11
|
+
|
12
|
+
* `chg` [#77](https://github.com/metabahn/corerb/pull/77) Add recompile support to pipelines ([bryanp](https://github.com/bryanp))
|
13
|
+
|
14
|
+
## [v0.1.0](https://github.com/metabahn/corerb/releases/tag/2021-07-15)
|
15
|
+
|
16
|
+
*released on 2021-07-15*
|
17
|
+
|
18
|
+
* `fix` [#81](https://github.com/metabahn/corerb/pull/81) Tie up loose ends around curried pipeline actions ([bryanp](https://github.com/bryanp))
|
19
|
+
* `add` [#80](https://github.com/metabahn/corerb/pull/80) Define curried pipeline actions ([bryanp](https://github.com/bryanp))
|
20
|
+
* `fix` [#69](https://github.com/metabahn/corerb/pull/69) Allow pipelines to be called at the class level ([bryanp](https://github.com/bryanp))
|
21
|
+
* `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))
|
22
|
+
* `chg` [#63](https://github.com/metabahn/corerb/pull/63) Improve inspect output for callable pipelines ([bryanp](https://github.com/bryanp))
|
23
|
+
* `chg` [#61](https://github.com/metabahn/corerb/pull/61) Refactor pipelines to be more performant for new instances ([bryanp](https://github.com/bryanp))
|
24
|
+
* `fix` [#59](https://github.com/metabahn/corerb/pull/59) Prevent collisions in auto-defined action names ([bryanp](https://github.com/bryanp))
|
25
|
+
* `chg` [#58](https://github.com/metabahn/corerb/pull/58) Define pipeline actions with context ([bryanp](https://github.com/bryanp))
|
26
|
+
* `chg` [#57](https://github.com/metabahn/corerb/pull/57) Avoid cache lookups for action calls when the action is just a method ([bryanp](https://github.com/bryanp))
|
27
|
+
* `add` [#54](https://github.com/metabahn/corerb/pull/54) Make all pipeline objects inspectable ([bryanp](https://github.com/bryanp))
|
28
|
+
|
1
29
|
## [v0.0.0](https://github.com/metabahn/corerb/releases/tag/2021-07-07)
|
2
30
|
|
3
31
|
*released on 2021-07-07*
|
data/lib/core/pipeline/action.rb
CHANGED
@@ -1,28 +1,35 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "is/inspectable"
|
4
|
+
require "is/stateful"
|
5
|
+
require "securerandom"
|
6
|
+
|
3
7
|
module Core
|
4
8
|
module Pipeline
|
5
9
|
# [public] A pipeline action.
|
6
10
|
#
|
7
11
|
class Action
|
12
|
+
include Is::Stateful
|
13
|
+
state :__used_random_suffixes__, default: []
|
14
|
+
|
8
15
|
class << self
|
9
16
|
# [public] Builds an action for a given target.
|
10
17
|
#
|
11
|
-
def build(target = nil, *args,
|
18
|
+
def build(target = nil, *args, before: nil, after: nil, context: nil, curry: false, &block)
|
12
19
|
if block
|
13
|
-
Actions::Block.new(target, before: before, after: after, &block)
|
20
|
+
Actions::Block.new(target, before: before, after: after, context: context, curry: curry, &block)
|
14
21
|
else
|
15
|
-
build_target(target, *args, before: before, after: after)
|
22
|
+
build_target(target, *args, before: before, after: after, context: context, curry: curry)
|
16
23
|
end
|
17
24
|
end
|
18
25
|
|
19
|
-
private def build_target(first, *args, before:, after:)
|
26
|
+
private def build_target(first, *args, before:, after:, context:, curry:)
|
20
27
|
case first
|
21
28
|
when String, Symbol
|
22
|
-
if (target = build_target(args[0], *args[1..], before: before, after: after))
|
29
|
+
if (target = build_target(args[0], *args[1..], before: before, after: after, context: context, curry: curry))
|
23
30
|
target
|
24
31
|
else
|
25
|
-
Actions::Method.new(first, before: before, after: after)
|
32
|
+
Actions::Method.new(first, before: before, after: after, context: context, curry: curry)
|
26
33
|
end
|
27
34
|
when NilClass
|
28
35
|
nil
|
@@ -30,11 +37,30 @@ module Core
|
|
30
37
|
raise ArgumentError, "unsupported action type"
|
31
38
|
end
|
32
39
|
end
|
40
|
+
|
41
|
+
def build_name
|
42
|
+
suffix = generate_random_suffix
|
43
|
+
if __used_random_suffixes__.include?(suffix)
|
44
|
+
build_name
|
45
|
+
else
|
46
|
+
__used_random_suffixes__ << suffix
|
47
|
+
"action_#{suffix}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def generate_random_suffix
|
52
|
+
SecureRandom.hex(8)
|
53
|
+
end
|
33
54
|
end
|
34
55
|
|
35
|
-
|
56
|
+
include Is::Inspectable
|
57
|
+
|
58
|
+
def initialize(name, before: nil, after: nil, context: nil, curry: false)
|
59
|
+
@name = (name || self.class.build_name).to_sym
|
36
60
|
@before = before
|
37
61
|
@after = after
|
62
|
+
@context = context
|
63
|
+
@curry = curry
|
38
64
|
end
|
39
65
|
|
40
66
|
# [public] The action name.
|
@@ -49,6 +75,16 @@ module Core
|
|
49
75
|
#
|
50
76
|
attr_reader :after
|
51
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
|
+
|
52
88
|
# [public] Finalizes the action for the given context.
|
53
89
|
#
|
54
90
|
def finalize(context)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
3
|
+
require "is/inspectable"
|
4
4
|
|
5
5
|
module Core
|
6
6
|
module Pipeline
|
@@ -8,24 +8,35 @@ module Core
|
|
8
8
|
# [public] A pipeline action defined as a block.
|
9
9
|
#
|
10
10
|
class Block < Action
|
11
|
-
|
12
|
-
def build_name
|
13
|
-
"action_#{SecureRandom.hex(8)}"
|
14
|
-
end
|
15
|
-
end
|
11
|
+
include Is::Inspectable
|
16
12
|
|
17
|
-
def initialize(name = nil, before
|
18
|
-
@name = name || self.class.build_name
|
13
|
+
def initialize(name = nil, before: nil, after: nil, context: nil, curry: false, &block)
|
19
14
|
@block = block
|
20
15
|
|
21
|
-
super(before: before, after: after)
|
16
|
+
super(name, before: before, after: after, context: context, curry: curry)
|
22
17
|
end
|
23
18
|
|
24
19
|
# [public]
|
25
20
|
#
|
26
21
|
def finalize(context)
|
27
|
-
context
|
28
|
-
|
22
|
+
case @context
|
23
|
+
when NilClass
|
24
|
+
unless context.method_defined?(@name)
|
25
|
+
context.define_method(@name, &@block)
|
26
|
+
end
|
27
|
+
|
28
|
+
@name
|
29
|
+
else
|
30
|
+
if @block.binding.receiver.equal?(@context)
|
31
|
+
@block
|
32
|
+
else
|
33
|
+
unless context.singleton_class.method_defined?(@name)
|
34
|
+
@context.define_singleton_method(@name, &@block)
|
35
|
+
end
|
36
|
+
|
37
|
+
@context.method(@name).to_proc
|
38
|
+
end
|
39
|
+
end
|
29
40
|
end
|
30
41
|
end
|
31
42
|
end
|
@@ -1,22 +1,31 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "is/inspectable"
|
4
|
+
|
3
5
|
module Core
|
4
6
|
module Pipeline
|
5
7
|
module Actions
|
6
8
|
# [public] A pipeline action defined as a method.
|
7
9
|
#
|
8
10
|
class Method < Action
|
9
|
-
|
10
|
-
|
11
|
+
include Is::Inspectable
|
12
|
+
|
13
|
+
def initialize(name = nil, before: nil, after: nil, context: nil, curry: false)
|
11
14
|
@method = nil
|
12
15
|
|
13
|
-
super(before: before, after: after)
|
16
|
+
super(name, before: before, after: after, context: context, curry: curry)
|
14
17
|
end
|
15
18
|
|
16
19
|
# [public]
|
17
20
|
#
|
18
21
|
def finalize(context)
|
19
|
-
context
|
22
|
+
case @context
|
23
|
+
when NilClass
|
24
|
+
context.instance_method(@name)
|
25
|
+
@name
|
26
|
+
else
|
27
|
+
@context.method(@name).to_proc
|
28
|
+
end
|
20
29
|
end
|
21
30
|
end
|
22
31
|
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "is/inspectable"
|
4
|
+
|
5
|
+
require_relative "action"
|
6
|
+
require_relative "compiler"
|
7
|
+
|
8
|
+
module Core
|
9
|
+
module Pipeline
|
10
|
+
include Is::Inspectable
|
11
|
+
inspects without: [:@object, :@mutex]
|
12
|
+
|
13
|
+
# [public] Calls a pipeline on a pipelined object.
|
14
|
+
#
|
15
|
+
class Callable
|
16
|
+
def initialize(object)
|
17
|
+
@object = object
|
18
|
+
@mutex = Mutex.new
|
19
|
+
@actions = []
|
20
|
+
@skipped = []
|
21
|
+
@compiled = false
|
22
|
+
@compiler = Compiler
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize_copy(...)
|
26
|
+
@actions = @actions.clone
|
27
|
+
@skipped = @skipped.clone
|
28
|
+
super
|
29
|
+
end
|
30
|
+
|
31
|
+
attr_reader :actions
|
32
|
+
attr_reader :skipped
|
33
|
+
|
34
|
+
# [public]
|
35
|
+
#
|
36
|
+
attr_writer :compiler
|
37
|
+
|
38
|
+
# [public] Relocates to another object context.
|
39
|
+
#
|
40
|
+
def relocate(object)
|
41
|
+
@object = object
|
42
|
+
end
|
43
|
+
|
44
|
+
# [public] Returns true if the pipeline will call any actions.
|
45
|
+
#
|
46
|
+
def any?
|
47
|
+
(@actions.map(&:name) - @skipped).any?
|
48
|
+
end
|
49
|
+
|
50
|
+
# [public] Defines a pipeline action.
|
51
|
+
#
|
52
|
+
def action(*args, before: nil, after: nil, context: nil, curry: false, &block)
|
53
|
+
@actions << Action.build(*args, before: before, after: after, context: context, curry: curry, &block)
|
54
|
+
recompile if compiled?
|
55
|
+
end
|
56
|
+
|
57
|
+
# [public] Skips the given action.
|
58
|
+
#
|
59
|
+
def skip(name)
|
60
|
+
@skipped << name.to_sym
|
61
|
+
recompile if compiled?
|
62
|
+
end
|
63
|
+
|
64
|
+
# [public] Replaces existing actions with actions from the given pipeline.
|
65
|
+
#
|
66
|
+
def use(pipeline)
|
67
|
+
if (pipeline.is_a?(Class) || pipeline.is_a?(Module)) && pipeline.ancestors.include?(Is::Pipeline)
|
68
|
+
@mutex.synchronize do
|
69
|
+
@actions.clear
|
70
|
+
@actions.concat(pipeline.pipeline.actions)
|
71
|
+
recompile if compiled?
|
72
|
+
end
|
73
|
+
else
|
74
|
+
raise ArgumentError, "expected a pipeline"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# [public] Includes actions from the given pipeline.
|
79
|
+
#
|
80
|
+
def include(pipeline)
|
81
|
+
if (pipeline.is_a?(Class) || pipeline.is_a?(Module)) && pipeline.ancestors.include?(Is::Pipeline)
|
82
|
+
@actions.concat(pipeline.pipeline.actions)
|
83
|
+
recompile if compiled?
|
84
|
+
else
|
85
|
+
raise ArgumentError, "expected a pipeline"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# [public] Excludes actions from the given pipeline.
|
90
|
+
#
|
91
|
+
def exclude(pipeline)
|
92
|
+
if (pipeline.is_a?(Class) || pipeline.is_a?(Module)) && pipeline.ancestors.include?(Is::Pipeline)
|
93
|
+
pipeline.pipeline.actions.each do |action|
|
94
|
+
@actions.delete(action)
|
95
|
+
recompile if compiled?
|
96
|
+
end
|
97
|
+
else
|
98
|
+
raise ArgumentError, "expected a pipeline"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# [public] Calls the pipeline in context of an object with the given arguments.
|
103
|
+
#
|
104
|
+
def call(object, ...)
|
105
|
+
compile.call(object, ...)
|
106
|
+
end
|
107
|
+
|
108
|
+
private def compile
|
109
|
+
instance_eval(@compiler.compile(self, @object), __FILE__, __LINE__ - 1)
|
110
|
+
@compiled = true
|
111
|
+
self
|
112
|
+
end
|
113
|
+
|
114
|
+
private def recompile
|
115
|
+
singleton_class.remove_method(:call)
|
116
|
+
compile
|
117
|
+
end
|
118
|
+
|
119
|
+
private def compiled?
|
120
|
+
@compiled == true
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -5,30 +5,29 @@ require_relative "signals/rejected"
|
|
5
5
|
|
6
6
|
module Core
|
7
7
|
module Pipeline
|
8
|
-
# [public] Compiles a pipeline
|
8
|
+
# [public] Compiles a pipeline into evalable code.
|
9
9
|
#
|
10
10
|
class Compiler
|
11
11
|
class << self
|
12
|
-
# [public] Compiles a pipeline
|
12
|
+
# [public] Compiles a callable pipeline in context of an object.
|
13
13
|
#
|
14
|
-
def compile(
|
15
|
-
|
16
|
-
def call(...)
|
17
|
-
#{
|
14
|
+
def compile(callable, object)
|
15
|
+
<<~CODE
|
16
|
+
def call(object, ...)
|
17
|
+
#{compile_call(callable, object)}
|
18
18
|
rescue Core::Pipeline::Signals::Halted => error
|
19
19
|
error.value
|
20
20
|
end
|
21
21
|
CODE
|
22
|
-
|
23
|
-
pipeline.instance_eval(compiled, __FILE__, __LINE__ - 1)
|
24
22
|
end
|
25
23
|
|
26
|
-
|
24
|
+
# [public]
|
25
|
+
#
|
26
|
+
private def compile_call(callable, object)
|
27
27
|
compiled = +""
|
28
|
-
|
29
|
-
finalized_actions(pipeline).each_key do |object_id|
|
28
|
+
finalized_actions(callable, object).each_value do |action|
|
30
29
|
compiled << "begin\n"
|
31
|
-
compiled <<
|
30
|
+
compiled << compile_action(action, object)
|
32
31
|
compiled << "rescue Core::Pipeline::Signals::Rejected\n"
|
33
32
|
compiled << "end\n"
|
34
33
|
end
|
@@ -36,38 +35,61 @@ module Core
|
|
36
35
|
compiled
|
37
36
|
end
|
38
37
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
38
|
+
# [public]
|
39
|
+
#
|
40
|
+
private def compile_action(action, object)
|
41
|
+
case action[:finalized]
|
42
|
+
when Symbol
|
43
|
+
if object.private_method_defined?(action[:finalized])
|
44
|
+
"object.send(#{action[:finalized].inspect}, ...)\n"
|
45
|
+
else
|
46
|
+
"object.#{action[:finalized]}(...)\n"
|
47
|
+
end
|
48
|
+
else
|
49
|
+
"@__finalized_actions__[#{action[:object_id]}][:finalized].call(...)\n"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
private def finalized_actions(callable, object)
|
54
|
+
validate_skipped_actions(callable)
|
55
|
+
finalized_actions = build_finalized_actions(callable, object)
|
56
|
+
callable.instance_variable_set(:@__finalized_actions__, finalized_actions)
|
43
57
|
end
|
44
58
|
|
45
|
-
private def validate_skipped_actions(
|
46
|
-
|
47
|
-
unless
|
48
|
-
raise "cannot skip unknown action `#{
|
59
|
+
private def validate_skipped_actions(callable)
|
60
|
+
callable.skipped.each do |skipped|
|
61
|
+
unless callable.actions.any? { |action| action.name == skipped }
|
62
|
+
raise "cannot skip unknown action `#{skipped}`"
|
49
63
|
end
|
50
64
|
end
|
51
65
|
end
|
52
66
|
|
53
|
-
private def build_finalized_actions(
|
54
|
-
ordered_actions(
|
55
|
-
|
67
|
+
private def build_finalized_actions(callable, object)
|
68
|
+
ordered_actions(callable).reject { |action|
|
69
|
+
callable.skipped.include?(action.name)
|
56
70
|
}.map { |action|
|
57
|
-
action.finalize(
|
71
|
+
finalized = action.finalize(object)
|
72
|
+
|
73
|
+
finalized = if action.curried?
|
74
|
+
wrap_finalized_action_for_context(finalized, action.context || object)
|
75
|
+
else
|
76
|
+
finalized
|
77
|
+
end
|
78
|
+
|
79
|
+
{object: action, finalized: finalized, object_id: finalized.object_id}
|
58
80
|
}.each_with_object({}) { |action, lookup|
|
59
|
-
lookup[action
|
81
|
+
lookup[action[:object_id]] = action
|
60
82
|
}
|
61
83
|
end
|
62
84
|
|
63
|
-
private def ordered_actions(
|
64
|
-
return
|
85
|
+
private def ordered_actions(callable)
|
86
|
+
return callable.actions if callable.actions.empty?
|
65
87
|
|
66
88
|
marked = []
|
67
89
|
ordered = []
|
68
|
-
working =
|
90
|
+
working = callable.actions.dup
|
69
91
|
|
70
|
-
|
92
|
+
callable.actions.each do |action|
|
71
93
|
unless action.before || action.after
|
72
94
|
ordered << action
|
73
95
|
working.delete(action)
|
@@ -110,6 +132,205 @@ module Core
|
|
110
132
|
|
111
133
|
ordered
|
112
134
|
end
|
135
|
+
|
136
|
+
private def wrap_finalized_action_for_context(finalized, context)
|
137
|
+
case finalized
|
138
|
+
when Symbol
|
139
|
+
wrapped_name = :"curried_#{finalized}"
|
140
|
+
method = context.instance_method(finalized)
|
141
|
+
signature = [
|
142
|
+
build_method_arguments_signature(method),
|
143
|
+
build_method_keywords_signature(method),
|
144
|
+
"&block"
|
145
|
+
].compact.join(", ")
|
146
|
+
|
147
|
+
code = <<~CODE
|
148
|
+
def #{wrapped_name}(*args, **kwargs, &block)
|
149
|
+
self.#{finalized}(#{signature})
|
150
|
+
end
|
151
|
+
CODE
|
152
|
+
|
153
|
+
context.class_eval(code, __FILE__, __LINE__ - 1)
|
154
|
+
|
155
|
+
wrapped_name
|
156
|
+
else
|
157
|
+
if callable_accepts_keyword_arguments?(finalized)
|
158
|
+
finalized_keywords = callable_keywords(finalized)
|
159
|
+
|
160
|
+
if callable_accepts_arguments?(finalized)
|
161
|
+
argument_count = callable_arguments(finalized).count
|
162
|
+
|
163
|
+
if callable_accepts_keyword_arguments_splat?(finalized)
|
164
|
+
if callable_accepts_arguments_splat?(finalized)
|
165
|
+
if RbConfig::CONFIG["RUBY_PROGRAM_VERSION"] < "3"
|
166
|
+
proc { |*args, **kwargs, &block|
|
167
|
+
finalized.call(*args.take(argument_count), *args[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), *args[argument_count..], **kwargs.slice(*finalized_keywords), **kwargs.except(*finalized_keywords), &block)
|
172
|
+
}
|
173
|
+
end
|
174
|
+
elsif RbConfig::CONFIG["RUBY_PROGRAM_VERSION"] < "3"
|
175
|
+
proc { |*args, **kwargs, &block|
|
176
|
+
finalized.call(*args.take(argument_count), **kwargs.slice(*finalized_keywords), **kwargs.reject { |kwarg, _| finalized_keywords.include?(kwarg) }, &block)
|
177
|
+
}
|
178
|
+
else
|
179
|
+
proc { |*args, **kwargs, &block|
|
180
|
+
finalized.call(*args.take(argument_count), **kwargs.slice(*finalized_keywords), **kwargs.except(*finalized_keywords), &block)
|
181
|
+
}
|
182
|
+
end
|
183
|
+
elsif callable_accepts_arguments_splat?(finalized)
|
184
|
+
proc { |*args, **kwargs, &block|
|
185
|
+
finalized.call(*args.take(argument_count), *args[argument_count..], **kwargs.slice(*finalized_keywords), &block)
|
186
|
+
}
|
187
|
+
else
|
188
|
+
proc { |*args, **kwargs, &block|
|
189
|
+
finalized.call(*args.take(argument_count), **kwargs.slice(*finalized_keywords), &block)
|
190
|
+
}
|
191
|
+
end
|
192
|
+
elsif callable_accepts_keyword_arguments_splat?(finalized)
|
193
|
+
if callable_accepts_arguments_splat?(finalized)
|
194
|
+
if RbConfig::CONFIG["RUBY_PROGRAM_VERSION"] < "3"
|
195
|
+
proc { |*args, **kwargs, &block|
|
196
|
+
finalized.call(*args, **kwargs.slice(*finalized_keywords), **kwargs.reject { |kwarg, _| finalized_keywords.include?(kwarg) }, &block)
|
197
|
+
}
|
198
|
+
else
|
199
|
+
proc { |*args, **kwargs, &block|
|
200
|
+
finalized.call(*args, **kwargs.slice(*finalized_keywords), **kwargs.except(*finalized_keywords), &block)
|
201
|
+
}
|
202
|
+
end
|
203
|
+
elsif RbConfig::CONFIG["RUBY_PROGRAM_VERSION"] < "3"
|
204
|
+
proc { |*, **kwargs, &block|
|
205
|
+
finalized.call(**kwargs.slice(*finalized_keywords), **kwargs.reject { |kwarg, _| finalized_keywords.include?(kwarg) }, &block)
|
206
|
+
}
|
207
|
+
else
|
208
|
+
proc { |*, **kwargs, &block|
|
209
|
+
finalized.call(**kwargs.slice(*finalized_keywords), **kwargs.except(*finalized_keywords), &block)
|
210
|
+
}
|
211
|
+
end
|
212
|
+
elsif callable_accepts_arguments_splat?(finalized)
|
213
|
+
proc { |*args, **kwargs, &block|
|
214
|
+
finalized.call(*args, **kwargs.slice(*finalized_keywords), &block)
|
215
|
+
}
|
216
|
+
else
|
217
|
+
proc { |*, **kwargs, &block|
|
218
|
+
finalized.call(**kwargs.slice(*finalized_keywords), &block)
|
219
|
+
}
|
220
|
+
end
|
221
|
+
elsif callable_accepts_arguments?(finalized)
|
222
|
+
argument_count = callable_arguments(finalized).count
|
223
|
+
|
224
|
+
if callable_accepts_keyword_arguments_splat?(finalized)
|
225
|
+
if callable_accepts_arguments_splat?(finalized)
|
226
|
+
proc { |*args, **kwargs, &block|
|
227
|
+
finalized.call(*args.take(argument_count), *args[argument_count..], **kwargs, &block)
|
228
|
+
}
|
229
|
+
else
|
230
|
+
proc { |*args, **kwargs, &block|
|
231
|
+
finalized.call(*args.take(argument_count), **kwargs, &block)
|
232
|
+
}
|
233
|
+
end
|
234
|
+
elsif callable_accepts_arguments_splat?(finalized)
|
235
|
+
proc { |*args, **, &block|
|
236
|
+
finalized.call(*args.take(argument_count), *args[argument_count..], &block)
|
237
|
+
}
|
238
|
+
else
|
239
|
+
proc { |*args, **, &block|
|
240
|
+
finalized.call(*args.take(argument_count), &block)
|
241
|
+
}
|
242
|
+
end
|
243
|
+
elsif callable_accepts_keyword_arguments_splat?(finalized)
|
244
|
+
if callable_accepts_arguments_splat?(finalized)
|
245
|
+
proc { |*args, **kwargs, &block|
|
246
|
+
finalized.call(*args, **kwargs, &block)
|
247
|
+
}
|
248
|
+
else
|
249
|
+
proc { |*, **kwargs, &block|
|
250
|
+
finalized.call(**kwargs, &block)
|
251
|
+
}
|
252
|
+
end
|
253
|
+
elsif callable_accepts_arguments_splat?(finalized)
|
254
|
+
proc { |*args, **, &block|
|
255
|
+
finalized.call(*args, &block)
|
256
|
+
}
|
257
|
+
else
|
258
|
+
proc { |*, **, &block|
|
259
|
+
finalized.call(&block)
|
260
|
+
}
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
private def build_method_arguments_signature(method)
|
266
|
+
return if method.arity == 0
|
267
|
+
|
268
|
+
arguments = callable_arguments(method)
|
269
|
+
|
270
|
+
if arguments.any?
|
271
|
+
if callable_accepts_arguments_splat?(method)
|
272
|
+
"*args.take(#{arguments.count}), *args[#{arguments.count}..]"
|
273
|
+
else
|
274
|
+
"*args.take(#{arguments.count})"
|
275
|
+
end
|
276
|
+
elsif callable_accepts_arguments_splat?(method)
|
277
|
+
"*args"
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
private def build_method_keywords_signature(method)
|
282
|
+
return if method.arity == 0
|
283
|
+
|
284
|
+
keywords = callable_keywords(method)
|
285
|
+
|
286
|
+
if keywords.any?
|
287
|
+
keywords_string = keywords.map { |keyword| ":#{keyword}" }.join(", ")
|
288
|
+
|
289
|
+
if callable_accepts_keyword_arguments_splat?(method)
|
290
|
+
if RbConfig::CONFIG["RUBY_PROGRAM_VERSION"] < "3"
|
291
|
+
"**kwargs.slice(#{keywords_string}), **kwargs.reject { |kwarg, _| [#{keywords_string}].include?(kwarg) }"
|
292
|
+
else
|
293
|
+
"**kwargs.slice(#{keywords_string}), **kwargs.except(*#{keywords_string})"
|
294
|
+
end
|
295
|
+
else
|
296
|
+
"**kwargs.slice(#{keywords_string})"
|
297
|
+
end
|
298
|
+
elsif callable_accepts_keyword_arguments_splat?(method)
|
299
|
+
"**kwargs"
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
private def callable_accepts_arguments_splat?(callable)
|
304
|
+
callable.parameters.any? { |type, _| type == :rest }
|
305
|
+
end
|
306
|
+
|
307
|
+
private def callable_accepts_keyword_arguments_splat?(callable)
|
308
|
+
callable.parameters.any? { |type, _| type == :keyrest }
|
309
|
+
end
|
310
|
+
|
311
|
+
private def callable_accepts_arguments?(callable)
|
312
|
+
callable.parameters.any? { |type, _| type == :req || type == :opt }
|
313
|
+
end
|
314
|
+
|
315
|
+
private def callable_accepts_keyword_arguments?(callable)
|
316
|
+
callable.parameters.any? { |type, _| type == :keyreq || type == :key }
|
317
|
+
end
|
318
|
+
|
319
|
+
private def callable_arguments(callable)
|
320
|
+
callable.parameters.select { |type, _|
|
321
|
+
type == :req || type == :opt
|
322
|
+
}.map { |_, name|
|
323
|
+
name
|
324
|
+
}
|
325
|
+
end
|
326
|
+
|
327
|
+
private def callable_keywords(callable)
|
328
|
+
callable.parameters.select { |type, _|
|
329
|
+
type == :keyreq || type == :key
|
330
|
+
}.map { |_, name|
|
331
|
+
name
|
332
|
+
}
|
333
|
+
end
|
113
334
|
end
|
114
335
|
end
|
115
336
|
end
|
data/lib/is/pipeline.rb
CHANGED
@@ -1,10 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "is/async"
|
4
3
|
require "is/extension"
|
5
4
|
require "is/stateful"
|
6
5
|
|
7
|
-
require_relative "../core/pipeline/
|
6
|
+
require_relative "../core/pipeline/callable"
|
8
7
|
require_relative "../core/pipeline/compiler"
|
9
8
|
require_relative "../core/pipeline/signals/halted"
|
10
9
|
require_relative "../core/pipeline/signals/rejected"
|
@@ -15,70 +14,92 @@ module Is
|
|
15
14
|
module Pipeline
|
16
15
|
extend Is::Extension
|
17
16
|
|
17
|
+
applies do |extended:|
|
18
|
+
# [public] The current callable pipeline.
|
19
|
+
#
|
20
|
+
state :pipeline, default: Core::Pipeline::Callable.new(extended ? singleton_class : self)
|
21
|
+
end
|
22
|
+
|
18
23
|
extends dependencies: [
|
19
24
|
Is::Stateful
|
20
25
|
]
|
21
26
|
|
22
|
-
extends :
|
27
|
+
extends :definition do
|
23
28
|
# [public] Defines a pipeline action.
|
24
29
|
#
|
25
|
-
def action(*args, before: nil, after: nil, &block)
|
26
|
-
|
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)
|
27
32
|
end
|
28
33
|
|
29
34
|
# [public] Skips the given action.
|
30
35
|
#
|
31
36
|
def skip_action(name)
|
32
|
-
|
37
|
+
pipeline.skip(name)
|
33
38
|
end
|
34
39
|
|
35
40
|
# [public] Replaces existing actions with actions from the given pipeline.
|
36
41
|
#
|
37
|
-
def use_pipeline(
|
38
|
-
|
39
|
-
@__actions.clear
|
40
|
-
@__actions.concat(pipeline.__actions)
|
41
|
-
else
|
42
|
-
raise ArgumentError, "expected a pipeline"
|
43
|
-
end
|
42
|
+
def use_pipeline(other_pipeline)
|
43
|
+
pipeline.use(other_pipeline)
|
44
44
|
end
|
45
45
|
|
46
46
|
# [public] Includes actions from the given pipeline.
|
47
47
|
#
|
48
|
-
def include_pipeline(
|
49
|
-
|
50
|
-
@__actions.concat(pipeline.__actions)
|
51
|
-
else
|
52
|
-
raise ArgumentError, "expected a pipeline"
|
53
|
-
end
|
48
|
+
def include_pipeline(other_pipeline)
|
49
|
+
pipeline.include(other_pipeline)
|
54
50
|
end
|
55
51
|
|
56
52
|
# [public] Excludes actions from the given pipeline.
|
57
53
|
#
|
58
|
-
def exclude_pipeline(
|
59
|
-
|
60
|
-
pipeline.__actions.each do |action|
|
61
|
-
@__actions.delete(action)
|
62
|
-
end
|
63
|
-
else
|
64
|
-
raise ArgumentError, "expected a pipeline"
|
65
|
-
end
|
54
|
+
def exclude_pipeline(other_pipeline)
|
55
|
+
pipeline.exclude(other_pipeline)
|
66
56
|
end
|
67
57
|
end
|
68
58
|
|
69
|
-
extends :
|
59
|
+
extends :implementation do
|
70
60
|
# [public] Calls a pipeline with arguments.
|
71
61
|
#
|
72
62
|
def call(...)
|
73
|
-
|
74
|
-
|
75
|
-
|
63
|
+
@pipeline.call(self, ...)
|
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
|
76
93
|
|
77
|
-
|
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)
|
78
99
|
end
|
79
100
|
end
|
80
101
|
|
81
|
-
extends :
|
102
|
+
extends :implementation, prepend: true do
|
82
103
|
# [public] Halts the execution of the pipeline, setting the pipeline's current value to the given object.
|
83
104
|
#
|
84
105
|
private def halt(value = nil)
|
@@ -91,10 +112,5 @@ module Is
|
|
91
112
|
raise Core::Pipeline::Signals::Rejected
|
92
113
|
end
|
93
114
|
end
|
94
|
-
|
95
|
-
applied do
|
96
|
-
state :__actions, default: []
|
97
|
-
state :__skipped_actions, default: []
|
98
|
-
end
|
99
115
|
end
|
100
116
|
end
|
metadata
CHANGED
@@ -1,43 +1,43 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: core-pipeline
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.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-
|
11
|
+
date: 2021-11-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name: core-
|
14
|
+
name: core-extension
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '0.
|
19
|
+
version: '0.3'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '0.
|
26
|
+
version: '0.3'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name: core-
|
28
|
+
name: core-inspect
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '0.
|
33
|
+
version: '0.0'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '0.
|
40
|
+
version: '0.0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: core-state
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -64,6 +64,7 @@ files:
|
|
64
64
|
- lib/core/pipeline/action.rb
|
65
65
|
- lib/core/pipeline/actions/block.rb
|
66
66
|
- lib/core/pipeline/actions/method.rb
|
67
|
+
- lib/core/pipeline/callable.rb
|
67
68
|
- lib/core/pipeline/compiler.rb
|
68
69
|
- lib/core/pipeline/signals/halted.rb
|
69
70
|
- lib/core/pipeline/signals/rejected.rb
|
@@ -88,7 +89,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
88
89
|
- !ruby/object:Gem::Version
|
89
90
|
version: '0'
|
90
91
|
requirements: []
|
91
|
-
rubygems_version: 3.2.
|
92
|
+
rubygems_version: 3.2.22
|
92
93
|
signing_key:
|
93
94
|
specification_version: 4
|
94
95
|
summary: Turns Ruby objects into pipelines.
|