trailblazer-activity 0.12.2 → 0.14.0.beta2
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 +22 -1
- data/README.md +7 -7
- 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_builder.rb +11 -3
- data/lib/trailblazer/activity/task_wrap/extension.rb +90 -0
- data/lib/trailblazer/activity/task_wrap/pipeline.rb +34 -25
- data/lib/trailblazer/activity/task_wrap.rb +5 -26
- 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 +14 -13
- data/lib/trailblazer/activity/task_wrap/inject.rb +0 -39
- data/lib/trailblazer/activity/task_wrap/variable_mapping.rb +0 -90
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5ea76d00d1fbeedcd5160134fff2223300697bd777fb58475fc8de2311c1a5ae
|
|
4
|
+
data.tar.gz: 833e2c3b9fae46af07f07a4422405e1b66606460425d39a8777b84cfed8a0518
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ff4c7f7bcd260b495348ba7286e18a60cc104d83ff176d5e82c3c80c22e23bc69cd73cb7976f3637e64b9067932651478b0538b9eaf6731ca793a31ac70e5535
|
|
7
|
+
data.tar.gz: 34c21e990a083a2a08f203f21304b0ad33ab3616450b285f7ddc4c90861c160c5c548e1ea3af92e4a4828df2f789324d21a0deb00aa1e67c07c8084654430c1f
|
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,24 @@
|
|
|
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
|
+
|
|
14
|
+
# 0.13.0
|
|
15
|
+
|
|
16
|
+
* Removed `TaskWrap::Inject::Defaults`. This is now implemented through `dsl`'s `:inject` option.
|
|
17
|
+
* Removed `TaskWrap::VariableMapping.Extension`.
|
|
18
|
+
* Renamed private `TaskWrap::VariableMapping.merge_for` to `.merge_instructions_for` as there's no {Merge} instance, yet.
|
|
19
|
+
* Extract invocation logic in `TaskBuilder::Task` into `Task#call_option`.
|
|
20
|
+
* Add `TaskWrap::Pipeline::prepend`.
|
|
21
|
+
|
|
1
22
|
# 0.12.2
|
|
2
23
|
|
|
3
24
|
* Use extracted `trailblazer-option`.
|
|
@@ -25,7 +46,7 @@
|
|
|
25
46
|
|
|
26
47
|
# 0.11.2
|
|
27
48
|
|
|
28
|
-
*
|
|
49
|
+
* Upgrading `trailblazer-context` version :drum:
|
|
29
50
|
|
|
30
51
|
# 0.11.1
|
|
31
52
|
|
data/README.md
CHANGED
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
Implements Intermediate, Implementation and compiler
|
|
4
4
|
|
|
5
|
-
The `activity` gem
|
|
5
|
+
The `activity` gem implements the runtime logic to invoke a new abstraction called "activities". Ideally, activities are defined using the [`dsl-linear` DSL gem](https://github.com/trailblazer/trailblazer-activity-dsl-linear).
|
|
6
6
|
|
|
7
7
|
A process is a set of arbitrary pieces of logic you define, chained together and put into a meaningful context by an activity. Activity lets you focus on the implementation of steps while Trailblazer takes care of the control flow.
|
|
8
8
|
|
|
9
|
-
Please find the [full documentation on the Trailblazer website](https://
|
|
9
|
+
Please find the [full documentation on the Trailblazer website](https://trailblazer.to/2.1/docs/activity.html).
|
|
10
10
|
|
|
11
11
|
## Example
|
|
12
12
|
|
|
13
|
-
In conjunction with [`dsl-linear`](https://github.com/trailblazer/trailblazer-activity-dsl-linear), the `activity` gem provides three default patterns to model processes: `Path`, `Railway` and `FastTrack`. Here's an example of what a railway activity could look like, along with some more complex connections (you can read more about Railway strategy in the [docs](https://
|
|
13
|
+
In conjunction with [`dsl-linear`](https://github.com/trailblazer/trailblazer-activity-dsl-linear), the `activity` gem provides three default patterns to model processes: `Path`, `Railway` and `FastTrack`. Here's an example of what a railway activity could look like, along with some more complex connections (you can read more about Railway strategy in the [docs](https://trailblazer.to/2.1/docs/activity.html#activity-strategy-railway)).
|
|
14
14
|
|
|
15
15
|
```ruby
|
|
16
16
|
require "trailblazer-activity"
|
|
@@ -78,15 +78,15 @@ With Activity, modeling business processes turns out to be ridiculously simple:
|
|
|
78
78
|
|
|
79
79
|
## Operation
|
|
80
80
|
|
|
81
|
-
Trailblazer's [`Operation`](https://
|
|
81
|
+
Trailblazer's [`Operation`](https://trailblazer.to/2.1/docs/operation.html#operation-overview) internally uses an activity to model the processes.
|
|
82
82
|
|
|
83
83
|
## Workflow
|
|
84
|
-
Activities can be formed into bigger compounds and using workflow, you can build long-running processes such as a moderated blog post or a parcel delivery. Also, you don't have to use the DSL but can use the [`editor`](https://
|
|
84
|
+
Activities can be formed into bigger compounds and using workflow, you can build long-running processes such as a moderated blog post or a parcel delivery. Also, you don't have to use the DSL but can use the [`editor`](https://trailblazer.to/2.1/docs/pro.html#pro-editor)instead(cool for more complex, long-running flows). Here comes a sample screenshot.
|
|
85
85
|
|
|
86
|
-
<img src="http://
|
|
86
|
+
<img src="http://trailblazer.to/2.1/dist/img/flow.png">
|
|
87
87
|
|
|
88
88
|
## License
|
|
89
89
|
|
|
90
90
|
© Copyright 2018, Trailblazer GmbH
|
|
91
91
|
|
|
92
|
-
Licensed under the LGPLv3 license. We also offer a commercial-friendly [license](https://
|
|
92
|
+
Licensed under the LGPLv3 license. We also offer a commercial-friendly [license](https://trailblazer.to/2.1/docs/pro.html#pro-license).
|
|
@@ -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}.
|
|
@@ -18,17 +18,20 @@ module Trailblazer
|
|
|
18
18
|
end
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
+
# Wraps a {task} (that usually expects the task interface) into a circuit interface
|
|
22
|
+
# that can be used directly in a {Circuit}.
|
|
23
|
+
# We expect {task} to be exposing an {Option()} interface when calling it.
|
|
21
24
|
class Task
|
|
22
25
|
def initialize(task, user_proc)
|
|
23
26
|
@task = task
|
|
24
27
|
@user_proc = user_proc
|
|
25
|
-
|
|
26
28
|
freeze
|
|
27
29
|
end
|
|
28
30
|
|
|
29
|
-
def call(
|
|
31
|
+
def call((ctx, flow_options), **circuit_options)
|
|
30
32
|
# Execute the user step with TRB's kw args.
|
|
31
|
-
|
|
33
|
+
# {@task} is/implements {Trailblazer::Option} interface.
|
|
34
|
+
result = call_option(@task, [ctx, flow_options], **circuit_options)
|
|
32
35
|
|
|
33
36
|
# Return an appropriate signal which direction to go next.
|
|
34
37
|
signal = Activity::TaskBuilder.binary_signal_for(result, Activity::Right, Activity::Left)
|
|
@@ -36,6 +39,11 @@ module Trailblazer
|
|
|
36
39
|
return signal, [ctx, flow_options]
|
|
37
40
|
end
|
|
38
41
|
|
|
42
|
+
# Invoke the original {user_proc} that is wrapped in an {Option()}.
|
|
43
|
+
private def call_option(task_with_option_interface, (ctx, _flow_options), **circuit_options)
|
|
44
|
+
task_with_option_interface.(ctx, keyword_arguments: ctx.to_hash, **circuit_options) # circuit_options contains :exec_context.
|
|
45
|
+
end
|
|
46
|
+
|
|
39
47
|
def inspect # TODO: make me private!
|
|
40
48
|
%{#<Trailblazer::Activity::TaskBuilder::Task user_proc=#{Trailblazer::Activity::Introspect.render_task(@user_proc)}>}
|
|
41
49
|
end
|
|
@@ -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,53 +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 = pipe.sequence.find_index { |(id, _)| id == 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
|
-
# {Extension} API
|
|
43
|
-
class Merge # TODO: RENAME TO TaskWrap::Extension(::Merge)
|
|
44
|
-
def initialize(*extension_rows)
|
|
45
|
-
@extension_rows = extension_rows
|
|
45
|
+
class Row < Array
|
|
46
|
+
def id
|
|
47
|
+
self[0]
|
|
46
48
|
end
|
|
49
|
+
end
|
|
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!!!"
|
|
47
56
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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)
|
|
51
60
|
end
|
|
52
|
-
end
|
|
61
|
+
end # Merge
|
|
53
62
|
end
|
|
54
63
|
end
|
|
55
64
|
end
|
|
@@ -14,7 +14,8 @@ module Trailblazer
|
|
|
14
14
|
|
|
15
15
|
# Compute runtime arguments necessary to execute a taskWrap per task of the activity.
|
|
16
16
|
# This method is the top-level entry, called only once for the entire activity graph.
|
|
17
|
-
|
|
17
|
+
# [:wrap_static] The taskWrap used for the topmost activity/operation.
|
|
18
|
+
def invoke(activity, args, wrap_runtime: {}, wrap_static: initial_wrap_static, **circuit_options) # FIXME: why do we need this method?
|
|
18
19
|
circuit_options = circuit_options.merge(
|
|
19
20
|
runner: TaskWrap::Runner,
|
|
20
21
|
wrap_runtime: wrap_runtime,
|
|
@@ -24,7 +25,7 @@ module Trailblazer
|
|
|
24
25
|
)
|
|
25
26
|
|
|
26
27
|
# signal, (ctx, flow), circuit_options =
|
|
27
|
-
Runner.(activity, args, **circuit_options)
|
|
28
|
+
TaskWrap::Runner.(activity, args, **circuit_options)
|
|
28
29
|
end
|
|
29
30
|
|
|
30
31
|
# {:extension} API
|
|
@@ -32,28 +33,7 @@ module Trailblazer
|
|
|
32
33
|
# Gets executed in {Intermediate.call} which also provides {config}.
|
|
33
34
|
|
|
34
35
|
def initial_wrap_static(*)
|
|
35
|
-
|
|
36
|
-
TaskWrap::Pipeline.new([["task_wrap.call_task", TaskWrap.method(:call_task)]])
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
# Use this in your macros if you want to extend the {taskWrap}.
|
|
40
|
-
def Extension(merge:)
|
|
41
|
-
Extension.new(merge: Pipeline::Merge.new(*merge))
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
class Extension
|
|
45
|
-
def initialize(merge:)
|
|
46
|
-
@merge = merge
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
# Compile-time:
|
|
50
|
-
# Gets called via the {Normalizer} and represents an {:extensions} item.
|
|
51
|
-
# Adds/alters the activity's {wrap_static}.
|
|
52
|
-
def call(config:, task:, **)
|
|
53
|
-
before_pipe = State::Config.get(config, :wrap_static, task.circuit_task)
|
|
54
|
-
|
|
55
|
-
State::Config.set(config, :wrap_static, task.circuit_task, @merge.(before_pipe))
|
|
56
|
-
end
|
|
36
|
+
Pipeline.new([Pipeline.Row("task_wrap.call_task", TaskWrap.method(:call_task))])
|
|
57
37
|
end
|
|
58
38
|
end # TaskWrap
|
|
59
39
|
end
|
|
@@ -61,5 +41,4 @@ end
|
|
|
61
41
|
require "trailblazer/activity/task_wrap/pipeline"
|
|
62
42
|
require "trailblazer/activity/task_wrap/call_task"
|
|
63
43
|
require "trailblazer/activity/task_wrap/runner"
|
|
64
|
-
require "trailblazer/activity/task_wrap/
|
|
65
|
-
require "trailblazer/activity/task_wrap/inject"
|
|
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.beta2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Nick Sutterer
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2022-08-02 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: trailblazer-context
|
|
@@ -100,21 +100,22 @@ 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.
|
|
111
|
-
description:
|
|
110
|
+
version: 0.0.23
|
|
111
|
+
description:
|
|
112
112
|
email:
|
|
113
113
|
- apotonick@gmail.com
|
|
114
114
|
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,10 +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
|
|
138
|
-
- lib/trailblazer/activity/task_wrap/
|
|
140
|
+
- lib/trailblazer/activity/task_wrap/extension.rb
|
|
139
141
|
- lib/trailblazer/activity/task_wrap/pipeline.rb
|
|
140
142
|
- lib/trailblazer/activity/task_wrap/runner.rb
|
|
141
|
-
- lib/trailblazer/activity/task_wrap/variable_mapping.rb
|
|
142
143
|
- lib/trailblazer/activity/testing.rb
|
|
143
144
|
- lib/trailblazer/activity/version.rb
|
|
144
145
|
- trailblazer-activity.gemspec
|
|
@@ -146,7 +147,7 @@ homepage: http://trailblazer.to
|
|
|
146
147
|
licenses:
|
|
147
148
|
- LGPL-3.0
|
|
148
149
|
metadata: {}
|
|
149
|
-
post_install_message:
|
|
150
|
+
post_install_message:
|
|
150
151
|
rdoc_options: []
|
|
151
152
|
require_paths:
|
|
152
153
|
- lib
|
|
@@ -157,12 +158,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
157
158
|
version: 2.1.0
|
|
158
159
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
159
160
|
requirements:
|
|
160
|
-
- - "
|
|
161
|
+
- - ">"
|
|
161
162
|
- !ruby/object:Gem::Version
|
|
162
|
-
version:
|
|
163
|
+
version: 1.3.1
|
|
163
164
|
requirements: []
|
|
164
|
-
rubygems_version: 3.
|
|
165
|
-
signing_key:
|
|
165
|
+
rubygems_version: 3.2.3
|
|
166
|
+
signing_key:
|
|
166
167
|
specification_version: 4
|
|
167
168
|
summary: Runtime code for Trailblazer activities.
|
|
168
169
|
test_files: []
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
class Trailblazer::Activity
|
|
2
|
-
module TaskWrap
|
|
3
|
-
# Allows to inject attributes for a task and defaults them if not.
|
|
4
|
-
# Per default, the defaulting is scoped, meaning only the task will see it.
|
|
5
|
-
module Inject
|
|
6
|
-
module Defaults
|
|
7
|
-
module_function
|
|
8
|
-
|
|
9
|
-
def Extension(defaults)
|
|
10
|
-
# Returns new ctx.
|
|
11
|
-
input = ->((original_ctx, flow_options), circuit_options) do
|
|
12
|
-
defaulted_options = defaults_for(defaults, original_ctx)
|
|
13
|
-
|
|
14
|
-
ctx = original_ctx.merge(defaulted_options)
|
|
15
|
-
|
|
16
|
-
Trailblazer::Context(ctx, {}, flow_options[:context_options])
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
output = ->(new_ctx, (original_ctx, _flow_options), _circuit_options) { # FIXME: use Unscope
|
|
20
|
-
_, mutable_data = new_ctx.decompose
|
|
21
|
-
|
|
22
|
-
# we are only interested in the {mutable_data} part since the disposed part
|
|
23
|
-
# represents the injected/defaulted data.
|
|
24
|
-
original_ctx.merge(mutable_data)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
VariableMapping::Extension(input, output, id: input)
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
# go through all defaultable options and default them if appropriate.
|
|
31
|
-
def defaults_for(defaults, original_ctx)
|
|
32
|
-
Hash[
|
|
33
|
-
defaults.collect { |k, v| [k, original_ctx[k] || v] } # FIXME: doesn't allow {false/nil} currently.
|
|
34
|
-
]
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
end # Inject
|
|
38
|
-
end
|
|
39
|
-
end
|
|
@@ -1,90 +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()} etc., too, and write your own filter logic.
|
|
9
|
-
module VariableMapping
|
|
10
|
-
# The taskWrap extension that's included into the static taskWrap for a task.
|
|
11
|
-
def self.Extension(input, output, id: input.object_id)
|
|
12
|
-
Trailblazer::Activity::TaskWrap::Extension(
|
|
13
|
-
merge: merge_for(input, output, id: id),
|
|
14
|
-
)
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
# DISCUSS: do we want the automatic wrapping of {input} and {output}?
|
|
18
|
-
def self.merge_for(input, output, id:) # TODO: rename
|
|
19
|
-
[
|
|
20
|
-
[TaskWrap::Pipeline.method(:insert_before), "task_wrap.call_task", ["task_wrap.input", TaskWrap::Input.new(input, id: id)]],
|
|
21
|
-
[TaskWrap::Pipeline.method(:insert_after), "task_wrap.call_task", ["task_wrap.output", TaskWrap::Output.new(output, id: id)]],
|
|
22
|
-
]
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
# TaskWrap step to compute the incoming {Context} for the wrapped task.
|
|
27
|
-
# This allows renaming, filtering, hiding, of the options passed into the wrapped task.
|
|
28
|
-
#
|
|
29
|
-
# Both Input and Output are typically to be added before and after task_wrap.call_task.
|
|
30
|
-
#
|
|
31
|
-
# @note Assumption: we always have :input _and_ :output, where :input produces a Context and :output decomposes it.
|
|
32
|
-
|
|
33
|
-
# Calls your {@filter} and replaces the original ctx with your returned one.
|
|
34
|
-
class Input
|
|
35
|
-
def initialize(filter, id:)
|
|
36
|
-
@filter = filter
|
|
37
|
-
@id = id
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
# {input.call()} is invoked in the circuit.
|
|
41
|
-
# `original_args` are the actual args passed to the wrapped task: [ [ctx, ..], circuit_options ]
|
|
42
|
-
#
|
|
43
|
-
def call(wrap_ctx, original_args)
|
|
44
|
-
# let user compute new ctx for the wrapped task.
|
|
45
|
-
input_ctx = apply_filter(*original_args)
|
|
46
|
-
|
|
47
|
-
# decompose the original_args since we want to modify them.
|
|
48
|
-
(original_ctx, original_flow_options), original_circuit_options = original_args
|
|
49
|
-
|
|
50
|
-
wrap_ctx = wrap_ctx.merge(@id => original_ctx) # remember the original ctx by the key {@id}.
|
|
51
|
-
|
|
52
|
-
# instead of the original Context, pass on the filtered `input_ctx` in the wrap.
|
|
53
|
-
return wrap_ctx, [[input_ctx, original_flow_options], original_circuit_options]
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
private
|
|
57
|
-
|
|
58
|
-
# Invoke the @filter callable with the original circuit interface.
|
|
59
|
-
def apply_filter((ctx, original_flow_options), original_circuit_options)
|
|
60
|
-
@filter.([ctx, original_flow_options], **original_circuit_options) # returns {new_ctx}.
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
# TaskWrap step to compute the outgoing {Context} from the wrapped task.
|
|
65
|
-
# This allows renaming, filtering, hiding, of the options returned from the wrapped task.
|
|
66
|
-
class Output
|
|
67
|
-
def initialize(filter, id:)
|
|
68
|
-
@filter = filter
|
|
69
|
-
@id = id
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
# Runs your filter and replaces the ctx in `wrap_ctx[:return_args]` with the filtered one.
|
|
73
|
-
def call(wrap_ctx, original_args)
|
|
74
|
-
(original_ctx, original_flow_options), original_circuit_options = original_args
|
|
75
|
-
|
|
76
|
-
return_args = wrap_ctx[:return_args]
|
|
77
|
-
|
|
78
|
-
returned_ctx, returned_flow_options = wrap_ctx[:return_args] # this is the Context returned from {call}ing the wrapped user task.
|
|
79
|
-
original_ctx = wrap_ctx[@id] # grab the original ctx from before which was set in the {:input} filter.
|
|
80
|
-
# let user compute the output.
|
|
81
|
-
output_ctx = @filter.(returned_ctx, [original_ctx, returned_flow_options], **original_circuit_options)
|
|
82
|
-
|
|
83
|
-
wrap_ctx = wrap_ctx.merge( return_args: [output_ctx, returned_flow_options] )
|
|
84
|
-
|
|
85
|
-
# and then pass on the "new" context.
|
|
86
|
-
return wrap_ctx, original_args
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
end # Wrap
|
|
90
|
-
end
|