trailblazer-activity 0.13.0 → 0.14.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/.github/dependabot.yml +6 -0
- data/.github/workflows/ci.yml +2 -2
- data/CHANGES.md +14 -1
- data/lib/trailblazer/activity/adds.rb +153 -0
- data/lib/trailblazer/activity/circuit.rb +7 -10
- data/lib/trailblazer/activity/config.rb +12 -13
- data/lib/trailblazer/activity/schema/intermediate.rb +2 -1
- data/lib/trailblazer/activity/schema.rb +0 -2
- data/lib/trailblazer/activity/task_wrap/extension.rb +90 -0
- data/lib/trailblazer/activity/task_wrap/pipeline.rb +33 -38
- data/lib/trailblazer/activity/task_wrap.rb +2 -23
- data/lib/trailblazer/activity/testing.rb +40 -6
- data/lib/trailblazer/activity/version.rb +1 -1
- data/lib/trailblazer/activity.rb +5 -6
- data/trailblazer-activity.gemspec +1 -1
- metadata +7 -5
- data/lib/trailblazer/activity/task_wrap/variable_mapping.rb +0 -83
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2c710a0a76320ef3d041b5abeff53c24b34b1923caff08e3c92656919af62431
|
4
|
+
data.tar.gz: b235c975eff4e9174110270e73c75e56ec3d10fd3038616039a5de27322043d3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 38bff15d697d6362b36df62be35a05248510ac6c4326962a1e12c0c3fb041da30aa39c1c6ab8c591d77cbcb186534a4284bfeba33b6cc79e4ef684ef7837e9b9
|
7
|
+
data.tar.gz: 586078a93ac567f69c079b017f054513e4069b8613e63badb19a0b61a8f72fbdfb22e3fb98fa8d97c233a4ee1b2818537d9e4df72d2fe0c7a2b7dcdb0b4a5bf4
|
data/.github/workflows/ci.yml
CHANGED
@@ -6,10 +6,10 @@ jobs:
|
|
6
6
|
fail-fast: false
|
7
7
|
matrix:
|
8
8
|
# Due to https://github.com/actions/runner/issues/849, we have to use quotes for '3.0'
|
9
|
-
ruby: [2.5, 2.6, 2.7, '3.0', head, jruby, jruby-head]
|
9
|
+
ruby: [2.5, 2.6, 2.7, '3.0', 3.1, head, jruby, jruby-head]
|
10
10
|
runs-on: ubuntu-latest
|
11
11
|
steps:
|
12
|
-
- uses: actions/checkout@
|
12
|
+
- uses: actions/checkout@v3
|
13
13
|
- uses: ruby/setup-ruby@v1
|
14
14
|
with:
|
15
15
|
ruby-version: ${{ matrix.ruby }}
|
data/CHANGES.md
CHANGED
@@ -1,3 +1,16 @@
|
|
1
|
+
# 0.14.0
|
2
|
+
|
3
|
+
* Remove `Pipeline.insert_before` and friends. Pipeline is now altered using ADDS mechanics, just
|
4
|
+
as we do it with the `Sequence` in the `trailblazer-activity-dsl-linear` gem.
|
5
|
+
* `Pipeline::Merge` is now `TaskWrap::Extension`. The "pre-friendly interface" you used to leverage for creating
|
6
|
+
taskWrap (tw) extensions is now deprecated and you will see warnings. See https://trailblazer.to/2.1/docs/activity.html#activity-taskwrap-extension
|
7
|
+
* Replace `TaskWrap::Extension()` with `TaskWrap::Extension.WrapStatic()` as a consistent interface for creating tW extensions at compile-time.
|
8
|
+
* Remove `Insert.find`.
|
9
|
+
* Rename `Activity::State::Config` to `Activity::Config`.
|
10
|
+
* Move `VariableMapping` to the `trailblazer-activity-dsl-linear` gem.
|
11
|
+
* Move `Pipeline.prepend` to the `trailblazer-activity-linear-dsl` gem.
|
12
|
+
* Add `Testing#assert_call` as a consistent test implementation. (0.14.0.beta2)
|
13
|
+
|
1
14
|
# 0.13.0
|
2
15
|
|
3
16
|
* Removed `TaskWrap::Inject::Defaults`. This is now implemented through `dsl`'s `:inject` option.
|
@@ -33,7 +46,7 @@
|
|
33
46
|
|
34
47
|
# 0.11.2
|
35
48
|
|
36
|
-
*
|
49
|
+
* Upgrading `trailblazer-context` version :drum:
|
37
50
|
|
38
51
|
# 0.11.1
|
39
52
|
|
@@ -0,0 +1,153 @@
|
|
1
|
+
module Trailblazer
|
2
|
+
class Activity
|
3
|
+
# The Adds interface are mechanics to alter sequences/pipelines.
|
4
|
+
# "one" ADDS structure: {row: ..., insert: [Insert, "id"]}
|
5
|
+
#
|
6
|
+
# To work with the instructions provided here, the pipeline structure
|
7
|
+
# needs to expose {#to_a}.
|
8
|
+
module Adds
|
9
|
+
module_function
|
10
|
+
# @returns Sequence/Pipeline New sequence instance
|
11
|
+
# @private
|
12
|
+
def insert_row(pipeline, row:, insert:)
|
13
|
+
insert_function, *args = insert
|
14
|
+
|
15
|
+
insert_function.(pipeline, row, *args)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Inserts one or more {Adds} into {pipeline}.
|
19
|
+
def apply_adds(pipeline, adds)
|
20
|
+
adds.each do |add|
|
21
|
+
pipeline = insert_row(pipeline, **add)
|
22
|
+
end
|
23
|
+
|
24
|
+
pipeline
|
25
|
+
end
|
26
|
+
|
27
|
+
# @param inserts Array of friendly interface insertions
|
28
|
+
# def call(pipeline, *inserts)
|
29
|
+
# adds = build_adds_for_friendly_interface(inserts)
|
30
|
+
|
31
|
+
# Adds.apply_adds(pipeline, adds)
|
32
|
+
# end
|
33
|
+
|
34
|
+
module FriendlyInterface
|
35
|
+
# @public
|
36
|
+
# @return Array of ADDS
|
37
|
+
#
|
38
|
+
# Translate a collection of friendly interface to ADDS.
|
39
|
+
# This is a mini-DSL, if you want.
|
40
|
+
def self.adds_for(inserts)
|
41
|
+
inserts.collect do |task, options|
|
42
|
+
build_adds(task, **options)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# @private
|
47
|
+
def self.build_adds(task, id:, prepend: "task_wrap.call_task", append: nil)
|
48
|
+
insert, insert_id = append ? [:Append, append] : [:Prepend, prepend]
|
49
|
+
|
50
|
+
{
|
51
|
+
insert: [Activity::Adds::Insert.method(insert), insert_id],
|
52
|
+
row: TaskWrap::Pipeline::Row(id, task)
|
53
|
+
}
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Functions to alter the Sequence/Pipeline by inserting, replacing, or deleting a row.
|
58
|
+
#
|
59
|
+
# they don't mutate the data structure but rebuild it, has to respond to {to_a}
|
60
|
+
#
|
61
|
+
# These methods are invoked via {Adds.apply_adds} and should never be called directly.
|
62
|
+
module Insert
|
63
|
+
module_function
|
64
|
+
|
65
|
+
# Append {new_row} after {insert_id}.
|
66
|
+
def Append(pipeline, new_row, insert_id=nil)
|
67
|
+
build_from_ary(pipeline, insert_id) do |ary, index|
|
68
|
+
index = ary.size if index.nil? # append to end of pipeline.
|
69
|
+
|
70
|
+
range_before_index(ary, index+1) + [new_row] + Array(ary[index+1..-1])
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Insert {new_row} before {insert_id}.
|
75
|
+
def Prepend(pipeline, new_row, insert_id=nil)
|
76
|
+
build_from_ary(pipeline, insert_id) do |ary, index|
|
77
|
+
index = 0 if index.nil? # Prepend to beginning of pipeline.
|
78
|
+
|
79
|
+
range_before_index(ary, index) + [new_row] + ary[index..-1]
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def Replace(pipeline, new_row, insert_id)
|
84
|
+
build_from_ary(pipeline, insert_id) do |ary, index|
|
85
|
+
range_before_index(ary, index) + [new_row] + ary[index+1..-1]
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def Delete(pipeline, _, insert_id)
|
90
|
+
build_from_ary(pipeline, insert_id) do |ary, index|
|
91
|
+
range_before_index(ary, index) + ary[index+1..-1]
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# @private
|
96
|
+
def build(sequence, rows)
|
97
|
+
sequence.class.new(rows)
|
98
|
+
end
|
99
|
+
|
100
|
+
# @private
|
101
|
+
def find_index(ary, insert_id)
|
102
|
+
ary.find_index { |row| row.id == insert_id }
|
103
|
+
end
|
104
|
+
|
105
|
+
# Converts the pipeline structure to an array,
|
106
|
+
# automatically finds the index for {insert_id},
|
107
|
+
# and calls the user block with the computed values.
|
108
|
+
#
|
109
|
+
# Single-entry point, could be named {#call}.
|
110
|
+
# @private
|
111
|
+
def apply_on_ary(pipeline, insert_id, raise_index_error: true, &block)
|
112
|
+
ary = pipeline.to_a
|
113
|
+
|
114
|
+
if insert_id.nil?
|
115
|
+
index = nil
|
116
|
+
else
|
117
|
+
index = find_index(ary, insert_id) # DISCUSS: this only makes sense if there are more than {Append} using this.
|
118
|
+
raise IndexError.new(pipeline, insert_id) if index.nil? && raise_index_error
|
119
|
+
end
|
120
|
+
|
121
|
+
_new_ary = yield(ary, index) # call the block.
|
122
|
+
end
|
123
|
+
|
124
|
+
def build_from_ary(pipeline, insert_id, &block)
|
125
|
+
new_ary = apply_on_ary(pipeline, insert_id, &block)
|
126
|
+
|
127
|
+
# Wrap the sequence/pipeline array into a concrete Sequence/Pipeline.
|
128
|
+
build(pipeline, new_ary)
|
129
|
+
end
|
130
|
+
|
131
|
+
# Always returns a valid, concat-able array for all indices
|
132
|
+
# before the {index}.
|
133
|
+
# @private
|
134
|
+
def range_before_index(ary, index)
|
135
|
+
return [] if index == 0
|
136
|
+
ary[0..index-1]
|
137
|
+
end
|
138
|
+
end # Insert
|
139
|
+
|
140
|
+
class IndexError < ::IndexError
|
141
|
+
def initialize(sequence, step_id)
|
142
|
+
valid_ids = sequence.to_a.collect{ |row| row.id.inspect }
|
143
|
+
|
144
|
+
message = "\n" \
|
145
|
+
"\e[31m#{step_id.inspect} is not a valid step ID. Did you mean any of these ?\e[0m\n" \
|
146
|
+
"\e[32m#{valid_ids.join("\n")}\e[0m"
|
147
|
+
|
148
|
+
super(message)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
@@ -76,13 +76,10 @@ module Trailblazer
|
|
76
76
|
outputs[signal]
|
77
77
|
end
|
78
78
|
|
79
|
-
# Common reasons to raise IllegalSignalError are
|
80
|
-
# *
|
81
|
-
# *
|
82
|
-
#
|
83
|
-
# Rest assured, it won't be raised in case of below scenarios where they can return any value,
|
84
|
-
# * Steps with instance method signature, for example, `step :load_user`
|
85
|
-
# * Steps with proc signature, for example `step ->(ctx, **){}`
|
79
|
+
# Common reasons to raise IllegalSignalError are when returning signals from
|
80
|
+
# * macros which are not registered
|
81
|
+
# * subprocesses where parent process have not registered that signal
|
82
|
+
# * ciruit interface steps, for example: `step task: method(:validate)`
|
86
83
|
class IllegalSignalError < RuntimeError
|
87
84
|
attr_reader :task, :signal
|
88
85
|
|
@@ -90,9 +87,9 @@ module Trailblazer
|
|
90
87
|
@task = task
|
91
88
|
@signal = signal
|
92
89
|
|
93
|
-
message = "#{exec_context.class}: \n
|
94
|
-
"\
|
95
|
-
"
|
90
|
+
message = "#{exec_context.class}: \n" \
|
91
|
+
"\e[31mUnrecognized Signal `#{signal.inspect}` returned from #{task.inspect}. Registered signals are, \e[0m\n" \
|
92
|
+
"\e[32m#{outputs.keys.map(&:inspect).join("\n")}\e[0m"
|
96
93
|
|
97
94
|
super(message)
|
98
95
|
end
|
@@ -1,33 +1,32 @@
|
|
1
1
|
module Trailblazer
|
2
|
-
|
3
|
-
#
|
4
|
-
#
|
5
|
-
# DISCUSS: we could replace parts with Hamster::Hash.
|
2
|
+
class Activity
|
3
|
+
# Config API allows you to read and write immutably to the activity's
|
4
|
+
# {:config} field. Most of the times, this only contains {:wrap_static}.
|
6
5
|
module Config
|
7
6
|
module_function
|
8
7
|
|
9
|
-
def set(
|
8
|
+
def set(config, *args)
|
10
9
|
if args.size == 2
|
11
10
|
key, value = *args
|
12
11
|
|
13
|
-
|
12
|
+
config = config.merge(key => value)
|
14
13
|
else
|
15
14
|
directive, key, value = *args
|
16
15
|
|
17
|
-
|
16
|
+
config = config.merge( directive => {}.freeze ) unless config.key?(directive)
|
18
17
|
|
19
|
-
directive_hash =
|
20
|
-
|
18
|
+
directive_hash = config[directive].merge(key => value)
|
19
|
+
config = config.merge( directive => directive_hash.freeze )
|
21
20
|
end
|
22
21
|
|
23
|
-
|
22
|
+
config
|
24
23
|
end
|
25
24
|
|
26
|
-
def get(
|
25
|
+
def get(config, *args)
|
27
26
|
directive, key = *args
|
28
27
|
|
29
|
-
return
|
30
|
-
return
|
28
|
+
return config[directive] if args.size == 1
|
29
|
+
return config[directive][key] if config.key?(directive)
|
31
30
|
|
32
31
|
nil
|
33
32
|
end
|
@@ -76,7 +76,8 @@ class Trailblazer::Activity
|
|
76
76
|
end.flatten(1)
|
77
77
|
end
|
78
78
|
|
79
|
-
# Invoke each task's extensions (usually coming from the DSL or some macro).
|
79
|
+
# Invoke each task's extensions (usually coming from the DSL user or some macro).
|
80
|
+
# We're expecting each {ext} to be a {TaskWrap::Extension::WrapStatic} instance.
|
80
81
|
def self.config(implementation, config:)
|
81
82
|
implementation.each do |id, task|
|
82
83
|
task.extensions.each { |ext| config = ext.(config: config, id: id, task: task) } # DISCUSS: ext must return new {Config}.
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module Trailblazer
|
2
|
+
class Activity
|
3
|
+
#
|
4
|
+
# Example with tracing:
|
5
|
+
#
|
6
|
+
# Call the task_wrap circuit:
|
7
|
+
# |-- Start
|
8
|
+
# |-- Trace.capture_args [optional]
|
9
|
+
# |-- Call (call actual task) id: "task_wrap.call_task"
|
10
|
+
# |-- Trace.capture_return [optional]
|
11
|
+
# |-- Wrap::End
|
12
|
+
module TaskWrap
|
13
|
+
# inserts must be
|
14
|
+
# An {Extension} can be used for {:wrap_runtime}. It expects a collection of
|
15
|
+
# "friendly interface" arrays.
|
16
|
+
#
|
17
|
+
# TaskWrap.Extension([ [task, id: "my_logger", append: "task_wrap.call_task"] ])
|
18
|
+
#
|
19
|
+
# If you want a {wrap_static} extension, wrap it using `Extension.WrapStatic.new`.
|
20
|
+
def self.Extension(*inserts, merge: nil)
|
21
|
+
if merge
|
22
|
+
return Extension::WrapStatic.new(extension: Extension.new(*merge))
|
23
|
+
# TODO: remove me once we drop the pre-friendly interface.
|
24
|
+
end
|
25
|
+
|
26
|
+
Extension.build(*inserts)
|
27
|
+
end
|
28
|
+
|
29
|
+
# An {Extension} is a collection of ADDS objects to be inserted into a taskWrap.
|
30
|
+
# It gets called either at
|
31
|
+
# * compile-time and adds its steps to the wrap_static (see Extension::WrapStatic)
|
32
|
+
# * run-time in {TaskWrap::Runner} and adds its steps dynamically at runtime to the
|
33
|
+
# step's taskWrap
|
34
|
+
class Extension
|
35
|
+
# Build a taskWrap extension from the "friendly interface" {[task, id:, ...]}
|
36
|
+
def self.build(*inserts)
|
37
|
+
# For performance reasons we're computing the ADDS here and not in {#call}.
|
38
|
+
extension_rows = Activity::Adds::FriendlyInterface.adds_for(inserts)
|
39
|
+
|
40
|
+
new(*extension_rows)
|
41
|
+
end
|
42
|
+
|
43
|
+
def initialize(*extension_rows)
|
44
|
+
extension_rows = deprecated_extension_for(extension_rows) # TODO: remove me soon!
|
45
|
+
|
46
|
+
@extension_rows = extension_rows # those rows are simple ADDS instructions.
|
47
|
+
end
|
48
|
+
|
49
|
+
# Merges {extension_rows} into the {Pipeline} instance.
|
50
|
+
# This is usually used in step extensions or at runtime for {wrap_runtime}.
|
51
|
+
def call(task_wrap_pipeline)
|
52
|
+
Adds.apply_adds(task_wrap_pipeline, @extension_rows)
|
53
|
+
end
|
54
|
+
|
55
|
+
# TODO: remove me once we drop the pre-friendly interface.
|
56
|
+
def deprecated_extension_for(extension_rows)
|
57
|
+
return extension_rows unless extension_rows.find { |ext| ext.is_a?(Array) }
|
58
|
+
|
59
|
+
warn "[Trailblazer] You are using the old API for taskWrap extensions.
|
60
|
+
Please update to the new TaskWrap.Extension() API: # FIXME !!!!!"
|
61
|
+
|
62
|
+
extension_rows.collect do |ary|
|
63
|
+
{
|
64
|
+
insert: ary[0..1],
|
65
|
+
row: Pipeline.Row(*ary[2])
|
66
|
+
}
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Extension are used at compile-time with {wrap_static}, mostly with the {dsl} gem.
|
71
|
+
# {WrapStatic} extensions are called for setup through {Intermediate.config} at compile-time.
|
72
|
+
# Each extension alters the activity's wrap_static taskWrap.
|
73
|
+
class WrapStatic
|
74
|
+
def initialize(extension:)
|
75
|
+
@extension = extension
|
76
|
+
end
|
77
|
+
|
78
|
+
def call(config:, task:, **)
|
79
|
+
# Add the extension's task(s) to the activity's {wrap_static} taskWrap
|
80
|
+
# by using the immutable {Config} interface.
|
81
|
+
before_pipe = Config.get(config, :wrap_static, task.circuit_task)
|
82
|
+
|
83
|
+
Config.set(config, :wrap_static, task.circuit_task, @extension.(before_pipe))
|
84
|
+
end
|
85
|
+
end # WrapStatic
|
86
|
+
|
87
|
+
end # Extension
|
88
|
+
end # TaskWrap
|
89
|
+
end
|
90
|
+
end
|
@@ -3,67 +3,62 @@ class Trailblazer::Activity
|
|
3
3
|
# This "circuit" is optimized for
|
4
4
|
# a) merging speed at run-time, since features like tracing will be applied here.
|
5
5
|
# b) execution speed. Every task in the real circuit is wrapped with one of us.
|
6
|
+
#
|
7
|
+
# It doesn't come with built-in insertion mechanics (except for {Pipeline.prepend}).
|
8
|
+
# Please add/remove steps using the {Activity::Adds} methods.
|
6
9
|
class Pipeline
|
7
10
|
def initialize(sequence)
|
8
11
|
@sequence = sequence # [[id, task], ..]
|
9
12
|
end
|
10
13
|
|
14
|
+
# Execute the pipeline and call all its steps, passing around the {wrap_ctx}.
|
11
15
|
def call(wrap_ctx, original_args)
|
12
16
|
@sequence.each { |(_id, task)| wrap_ctx, original_args = task.(wrap_ctx, original_args) }
|
13
17
|
|
14
18
|
return wrap_ctx, original_args
|
15
19
|
end
|
16
20
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
index = find_index(pipe, before_id)
|
21
|
-
|
22
|
-
seq = pipe.sequence.dup
|
23
|
-
|
24
|
-
Pipeline.new(seq.insert(index, insertion))
|
21
|
+
# Comply with the Adds interface.
|
22
|
+
def to_a
|
23
|
+
@sequence
|
25
24
|
end
|
26
25
|
|
27
|
-
|
28
|
-
|
26
|
+
# TODO: remove me when old tW extension API is deprecated.
|
27
|
+
def self.method(name)
|
28
|
+
new_name = {
|
29
|
+
insert_before: :Prepend,
|
30
|
+
insert_after: :Append,
|
31
|
+
append: :Append,
|
32
|
+
prepend: :Prepend,
|
33
|
+
}.fetch(name)
|
29
34
|
|
30
|
-
|
35
|
+
warn "[Trailblazer] Using `Trailblazer::Activity::TaskWrap::Pipeline.method(:#{name})` is deprecated.
|
36
|
+
Please use the new API: #FIXME!!!"
|
31
37
|
|
32
|
-
|
38
|
+
Trailblazer::Activity::Adds::Insert.method(new_name)
|
33
39
|
end
|
34
40
|
|
35
|
-
def self.
|
36
|
-
|
41
|
+
def self.Row(id, task)
|
42
|
+
Row[id, task]
|
37
43
|
end
|
38
44
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
Pipeline.new(pipe.sequence[0..index-1] + insertion.to_a + pipe.sequence[index+replace..-1])
|
45
|
-
end
|
46
|
-
|
47
|
-
# @private
|
48
|
-
def self.find_index(pipe, id)
|
49
|
-
index = pipe.sequence.find_index { |(seq_id, _)| seq_id == id }
|
45
|
+
class Row < Array
|
46
|
+
def id
|
47
|
+
self[0]
|
48
|
+
end
|
50
49
|
end
|
51
50
|
|
51
|
+
# TODO: remove {Merge} when old tW extension API is deprecated.
|
52
|
+
class Merge
|
53
|
+
def self.new(*inserts)
|
54
|
+
warn "[Trailblazer] Using `Trailblazer::Activity::TaskWrap::Pipeline::Merge.new` is deprecated.
|
55
|
+
Please use the new TaskWrap.Extension() API: #FIXME!!!"
|
52
56
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
# {Extension} API
|
57
|
-
class Merge # TODO: RENAME TO TaskWrap::Extension(::Merge)
|
58
|
-
def initialize(*extension_rows)
|
59
|
-
@extension_rows = extension_rows
|
57
|
+
# We can safely assume that users calling {Merge.new} are using the old tW extension API, not
|
58
|
+
# the "friendly API". That's why we don't go through {Extension.build}.
|
59
|
+
TaskWrap::Extension.new(*inserts)
|
60
60
|
end
|
61
|
-
|
62
|
-
def call(task_wrap_pipeline)
|
63
|
-
@extension_rows.collect { |(insert_function, target_id, row)| task_wrap_pipeline = insert_function.(task_wrap_pipeline, target_id, row) }
|
64
|
-
task_wrap_pipeline
|
65
|
-
end
|
66
|
-
end
|
61
|
+
end # Merge
|
67
62
|
end
|
68
63
|
end
|
69
64
|
end
|
@@ -33,28 +33,7 @@ module Trailblazer
|
|
33
33
|
# Gets executed in {Intermediate.call} which also provides {config}.
|
34
34
|
|
35
35
|
def initial_wrap_static(*)
|
36
|
-
|
37
|
-
TaskWrap::Pipeline.new([["task_wrap.call_task", TaskWrap.method(:call_task)]])
|
38
|
-
end
|
39
|
-
|
40
|
-
# Use this in your macros if you want to extend the {taskWrap}.
|
41
|
-
def Extension(merge:)
|
42
|
-
Extension.new(merge: Pipeline::Merge.new(*merge))
|
43
|
-
end
|
44
|
-
|
45
|
-
class Extension
|
46
|
-
def initialize(merge:)
|
47
|
-
@merge = merge
|
48
|
-
end
|
49
|
-
|
50
|
-
# Compile-time:
|
51
|
-
# Gets called via the {Normalizer} and represents an {:extensions} item.
|
52
|
-
# Adds/alters the activity's {wrap_static}.
|
53
|
-
def call(config:, task:, **)
|
54
|
-
before_pipe = State::Config.get(config, :wrap_static, task.circuit_task)
|
55
|
-
|
56
|
-
State::Config.set(config, :wrap_static, task.circuit_task, @merge.(before_pipe))
|
57
|
-
end
|
36
|
+
Pipeline.new([Pipeline.Row("task_wrap.call_task", TaskWrap.method(:call_task))])
|
58
37
|
end
|
59
38
|
end # TaskWrap
|
60
39
|
end
|
@@ -62,4 +41,4 @@ end
|
|
62
41
|
require "trailblazer/activity/task_wrap/pipeline"
|
63
42
|
require "trailblazer/activity/task_wrap/call_task"
|
64
43
|
require "trailblazer/activity/task_wrap/runner"
|
65
|
-
require "trailblazer/activity/task_wrap/
|
44
|
+
require "trailblazer/activity/task_wrap/extension"
|
@@ -54,6 +54,38 @@ module Trailblazer
|
|
54
54
|
Schema = Trailblazer::Activity::Schema
|
55
55
|
TaskWrap = Trailblazer::Activity::TaskWrap
|
56
56
|
|
57
|
+
# `:seq` is always passed into ctx.
|
58
|
+
# @param :seq String What the {:seq} variable in the result ctx looks like. (expected seq)
|
59
|
+
# @param :expected_ctx_variables Variables that are added during the call by the asserted activity.
|
60
|
+
def assert_call(activity, terminus: :success, seq: "[]", expected_ctx_variables: {}, **ctx_variables)
|
61
|
+
# Call without taskWrap!
|
62
|
+
signal, (ctx, _) = activity.([{seq: [], **ctx_variables}, _flow_options = {}]) # simply call the activity with the input you want to assert.
|
63
|
+
|
64
|
+
assert_call_for(signal, ctx, terminus: terminus, seq: seq, **expected_ctx_variables, **ctx_variables)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Use {TaskWrap.invoke} to call the activity.
|
68
|
+
def assert_invoke(activity, terminus: :success, seq: "[]", circuit_options: {}, expected_ctx_variables: {}, **ctx_variables) # DISCUSS: only for {activity} gem?
|
69
|
+
signal, (ctx, _flow_options) = TaskWrap.invoke(
|
70
|
+
activity,
|
71
|
+
[
|
72
|
+
{seq: [], **ctx_variables},
|
73
|
+
{} # flow_options
|
74
|
+
],
|
75
|
+
**circuit_options
|
76
|
+
)
|
77
|
+
|
78
|
+
assert_call_for(signal, ctx, terminus: terminus, seq: seq, **ctx_variables, **expected_ctx_variables) # DISCUSS: ordering of variables?
|
79
|
+
end
|
80
|
+
|
81
|
+
def assert_call_for(signal, ctx, terminus: :success, seq: "[]", **ctx_variables)
|
82
|
+
assert_equal signal.to_h[:semantic], terminus, "assert_call expected #{terminus} terminus, not #{signal}. Use assert_call(activity, terminus: #{signal.to_h[:semantic].inspect})"
|
83
|
+
|
84
|
+
assert_equal ctx.inspect, {seq: "%%%"}.merge(ctx_variables).inspect.sub('"%%%"', seq)
|
85
|
+
|
86
|
+
return ctx
|
87
|
+
end
|
88
|
+
|
57
89
|
module Implementing
|
58
90
|
extend Activity::Testing.def_tasks(:a, :b, :c, :d, :f, :g)
|
59
91
|
|
@@ -67,25 +99,27 @@ module Trailblazer
|
|
67
99
|
Implementing
|
68
100
|
end
|
69
101
|
|
70
|
-
def flat_activity
|
102
|
+
def flat_activity(implementing: Implementing)
|
71
103
|
return @_flat_activity if defined?(@_flat_activity)
|
72
104
|
|
73
105
|
intermediate = Inter.new(
|
74
106
|
{
|
75
107
|
Inter::TaskRef("Start.default") => [Inter::Out(:success, :B)],
|
76
|
-
Inter::TaskRef(:B, additional: true) => [Inter::Out(:success, :C)],
|
108
|
+
Inter::TaskRef(:B, additional: true) => [Inter::Out(:success, :C), Inter::Out(:failure, "End.failure")],
|
77
109
|
Inter::TaskRef(:C) => [Inter::Out(:success, "End.success")],
|
78
|
-
Inter::TaskRef("End.success", stop_event: true) => [Inter::Out(:success, nil)]
|
110
|
+
Inter::TaskRef("End.success", stop_event: true) => [Inter::Out(:success, nil)],
|
111
|
+
Inter::TaskRef("End.failure", stop_event: true) => [Inter::Out(:failure, nil)],
|
79
112
|
},
|
80
|
-
["End.success"],
|
113
|
+
["End.success", "End.failure"],
|
81
114
|
["Start.default"], # start
|
82
115
|
)
|
83
116
|
|
84
117
|
implementation = {
|
85
118
|
"Start.default" => Schema::Implementation::Task(st = Implementing::Start, [Activity::Output(Activity::Right, :success)], []),
|
86
|
-
:B => Schema::Implementation::Task(b =
|
87
|
-
:C => Schema::Implementation::Task(c =
|
119
|
+
:B => Schema::Implementation::Task(b = implementing.method(:b), [Activity::Output(Activity::Right, :success), Activity::Output(Activity::Left, :failure)], []),
|
120
|
+
:C => Schema::Implementation::Task(c = implementing.method(:c), [Activity::Output(Activity::Right, :success)], []),
|
88
121
|
"End.success" => Schema::Implementation::Task(_es = Implementing::Success, [Activity::Output(Implementing::Success, :success)], []), # DISCUSS: End has one Output, signal is itself?
|
122
|
+
"End.failure" => Schema::Implementation::Task(Implementing::Failure, [Activity::Output(Implementing::Failure, :failure)], []), # DISCUSS: End has one Output, signal is itself?
|
89
123
|
}
|
90
124
|
|
91
125
|
schema = Inter.(intermediate, implementation)
|
data/lib/trailblazer/activity.rb
CHANGED
@@ -14,12 +14,8 @@ module Trailblazer
|
|
14
14
|
)
|
15
15
|
end
|
16
16
|
|
17
|
-
#
|
18
|
-
#
|
19
|
-
# @state with a new, modified copy.
|
20
|
-
#
|
21
|
-
# Always use the accessors to avoid leaking state to other components
|
22
|
-
# due to mutable write operations.
|
17
|
+
# DISCUSS: we could remove this reader in the future
|
18
|
+
# and use {Activity.to_h[:config]}.
|
23
19
|
def [](*key)
|
24
20
|
@schema[:config][*key]
|
25
21
|
end
|
@@ -36,10 +32,13 @@ end
|
|
36
32
|
|
37
33
|
require "trailblazer/activity/structures"
|
38
34
|
require "trailblazer/activity/schema"
|
35
|
+
require "trailblazer/activity/schema/implementation"
|
36
|
+
require "trailblazer/activity/schema/intermediate"
|
39
37
|
require "trailblazer/activity/circuit"
|
40
38
|
require "trailblazer/activity/config"
|
41
39
|
require "trailblazer/activity/introspect"
|
42
40
|
require "trailblazer/activity/task_wrap"
|
41
|
+
require "trailblazer/activity/adds"
|
43
42
|
require "trailblazer/activity/task_builder"
|
44
43
|
|
45
44
|
require "trailblazer/option"
|
@@ -24,7 +24,7 @@ Gem::Specification.new do |spec|
|
|
24
24
|
spec.add_development_dependency "minitest", "~> 5.0"
|
25
25
|
spec.add_development_dependency "minitest-line"
|
26
26
|
spec.add_development_dependency "rake"
|
27
|
-
spec.add_development_dependency "trailblazer-developer", ">= 0.0.
|
27
|
+
spec.add_development_dependency "trailblazer-developer", ">= 0.0.23"
|
28
28
|
|
29
29
|
spec.required_ruby_version = '>= 2.1.0'
|
30
30
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: trailblazer-activity
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.14.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nick Sutterer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-08-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: trailblazer-context
|
@@ -100,14 +100,14 @@ dependencies:
|
|
100
100
|
requirements:
|
101
101
|
- - ">="
|
102
102
|
- !ruby/object:Gem::Version
|
103
|
-
version: 0.0.
|
103
|
+
version: 0.0.23
|
104
104
|
type: :development
|
105
105
|
prerelease: false
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
107
107
|
requirements:
|
108
108
|
- - ">="
|
109
109
|
- !ruby/object:Gem::Version
|
110
|
-
version: 0.0.
|
110
|
+
version: 0.0.23
|
111
111
|
description:
|
112
112
|
email:
|
113
113
|
- apotonick@gmail.com
|
@@ -115,6 +115,7 @@ executables: []
|
|
115
115
|
extensions: []
|
116
116
|
extra_rdoc_files: []
|
117
117
|
files:
|
118
|
+
- ".github/dependabot.yml"
|
118
119
|
- ".github/workflows/ci.yml"
|
119
120
|
- ".gitignore"
|
120
121
|
- CHANGES.md
|
@@ -125,6 +126,7 @@ files:
|
|
125
126
|
- Rakefile
|
126
127
|
- lib/trailblazer-activity.rb
|
127
128
|
- lib/trailblazer/activity.rb
|
129
|
+
- lib/trailblazer/activity/adds.rb
|
128
130
|
- lib/trailblazer/activity/circuit.rb
|
129
131
|
- lib/trailblazer/activity/config.rb
|
130
132
|
- lib/trailblazer/activity/introspect.rb
|
@@ -135,9 +137,9 @@ files:
|
|
135
137
|
- lib/trailblazer/activity/task_builder.rb
|
136
138
|
- lib/trailblazer/activity/task_wrap.rb
|
137
139
|
- lib/trailblazer/activity/task_wrap/call_task.rb
|
140
|
+
- lib/trailblazer/activity/task_wrap/extension.rb
|
138
141
|
- lib/trailblazer/activity/task_wrap/pipeline.rb
|
139
142
|
- lib/trailblazer/activity/task_wrap/runner.rb
|
140
|
-
- lib/trailblazer/activity/task_wrap/variable_mapping.rb
|
141
143
|
- lib/trailblazer/activity/testing.rb
|
142
144
|
- lib/trailblazer/activity/version.rb
|
143
145
|
- trailblazer-activity.gemspec
|
@@ -1,83 +0,0 @@
|
|
1
|
-
class Trailblazer::Activity
|
2
|
-
module TaskWrap
|
3
|
-
# Creates taskWrap steps to map variables before and after the actual step.
|
4
|
-
# We hook into the Normalizer, process `:input` and `:output` directives and
|
5
|
-
# translate them into a {DSL::Extension}.
|
6
|
-
#
|
7
|
-
# Note that the two options are not the only way to create filters, you can use the
|
8
|
-
# more low-level {Scoped()} from the `dsl` gem, too, and write your own filter logic.
|
9
|
-
module VariableMapping
|
10
|
-
# Places filters before/after the {call_task}.
|
11
|
-
# Note that {input} and {output} are automatically wrapped.
|
12
|
-
def self.merge_instructions_for(input, output, id:)
|
13
|
-
[
|
14
|
-
[TaskWrap::Pipeline.method(:insert_before), "task_wrap.call_task", ["task_wrap.input", TaskWrap::Input.new(input, id: id)]],
|
15
|
-
[TaskWrap::Pipeline.method(:insert_after), "task_wrap.call_task", ["task_wrap.output", TaskWrap::Output.new(output, id: id)]],
|
16
|
-
]
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
# TaskWrap step to compute the incoming {Context} for the wrapped task.
|
21
|
-
# This allows renaming, filtering, hiding, of the options passed into the wrapped task.
|
22
|
-
#
|
23
|
-
# Both Input and Output are typically to be added before and after task_wrap.call_task.
|
24
|
-
#
|
25
|
-
# @note Assumption: we always have :input _and_ :output, where :input produces a Context and :output decomposes it.
|
26
|
-
|
27
|
-
# Calls your {@filter} and replaces the original ctx with your returned one.
|
28
|
-
class Input
|
29
|
-
def initialize(filter, id:)
|
30
|
-
@filter = filter
|
31
|
-
@id = id
|
32
|
-
end
|
33
|
-
|
34
|
-
# {input.call()} is invoked in the taskWrap pipeline.
|
35
|
-
# {original_args} are the actual args passed to the wrapped task: [ [ctx, ..], circuit_options ]
|
36
|
-
# We now swap the ctx in {original_args} and our filtered one. The original "outside" ctx is keyed in
|
37
|
-
# {wrap_ctx} with the filter ID.
|
38
|
-
def call(wrap_ctx, original_args)
|
39
|
-
# let user compute new ctx for the wrapped task.
|
40
|
-
input_ctx = apply_filter(*original_args)
|
41
|
-
|
42
|
-
# decompose the original_args since we want to modify them.
|
43
|
-
(original_ctx, original_flow_options), original_circuit_options = original_args
|
44
|
-
|
45
|
-
wrap_ctx = wrap_ctx.merge(@id => original_ctx) # remember the original ctx by the key {@id}.
|
46
|
-
|
47
|
-
# instead of the original Context, pass on the filtered `input_ctx` in the wrap.
|
48
|
-
return wrap_ctx, [[input_ctx, original_flow_options], original_circuit_options]
|
49
|
-
end
|
50
|
-
|
51
|
-
private
|
52
|
-
|
53
|
-
# Invoke the @filter callable with the original circuit interface.
|
54
|
-
def apply_filter((ctx, original_flow_options), original_circuit_options)
|
55
|
-
@filter.([ctx, original_flow_options], **original_circuit_options) # returns {new_ctx}.
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
# TaskWrap step to compute the outgoing {Context} from the wrapped task.
|
60
|
-
# This allows renaming, filtering, hiding, of the options returned from the wrapped task.
|
61
|
-
class Output
|
62
|
-
def initialize(filter, id:)
|
63
|
-
@filter = filter
|
64
|
-
@id = id
|
65
|
-
end
|
66
|
-
|
67
|
-
# Runs your filter and replaces the ctx in `wrap_ctx[:return_args]` with the filtered one.
|
68
|
-
def call(wrap_ctx, original_args)
|
69
|
-
(original_ctx, _original_flow_options), original_circuit_options = original_args
|
70
|
-
|
71
|
-
returned_ctx, returned_flow_options = wrap_ctx[:return_args] # this is the Context returned from {call}ing the wrapped user task.
|
72
|
-
original_ctx = wrap_ctx[@id] # grab the original ctx from before which was set in the {:input} filter.
|
73
|
-
# let user compute the output.
|
74
|
-
output_ctx = @filter.(returned_ctx, [original_ctx, returned_flow_options], **original_circuit_options)
|
75
|
-
|
76
|
-
wrap_ctx = wrap_ctx.merge( return_args: [output_ctx, returned_flow_options] )
|
77
|
-
|
78
|
-
# and then pass on the "new" context.
|
79
|
-
return wrap_ctx, original_args
|
80
|
-
end
|
81
|
-
end
|
82
|
-
end # Wrap
|
83
|
-
end
|