trailblazer-activity-dsl-linear 0.4.3 → 0.5.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/CHANGES.md +6 -0
- data/Gemfile +1 -0
- data/README.md +9 -9
- data/lib/trailblazer/activity/dsl/linear/normalizer.rb +98 -119
- data/lib/trailblazer/activity/dsl/linear/state.rb +4 -11
- data/lib/trailblazer/activity/dsl/linear/variable_mapping.rb +154 -38
- data/lib/trailblazer/activity/dsl/linear/version.rb +1 -1
- data/lib/trailblazer/activity/fast_track.rb +54 -63
- data/lib/trailblazer/activity/path.rb +34 -58
- data/lib/trailblazer/activity/railway.rb +41 -63
- data/trailblazer-activity-dsl-linear.gemspec +1 -1
- metadata +6 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6185e8d95445bb10e611433a6885be07ec85524cdb5d45e1aed6449610391dd1
|
4
|
+
data.tar.gz: e0af2bf5eba1bbb381c35675c3352ba99b5a3598964e9deffeec75d8e6b3be58
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3ae89d32dddf81784df0abb2d103c3672125c80f6637e5cae76a3a460addb75e7707352a43643d8359273cb7c2e6c5e96636e19c714adebe5da657cd74d5c73e
|
7
|
+
data.tar.gz: c9f56bcb32fd1e1ba4adb7e8b39af43093d96a0eadb9ff21cbe35a2207a4c7b70728ee1eda7de00af1bf0972afbb51744143ff6f3d05595c6457c209f31b38c4
|
data/CHANGES.md
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
# 0.5.0
|
2
|
+
|
3
|
+
* Introduce `:inject` option to pass-through injected variables and to default input variables.
|
4
|
+
* Remove `VariableMapping::Input::Scoped` as we're now using a separate `Pipeline` for input filtering.
|
5
|
+
* Massively simplify (and accelerate!) the `Normalizer` layer by using `TaskWrap::Pipeline` instead of `Activity::Path`. Note that you can alter a normalizer by using the `TaskWrap::Pipeline` API now.
|
6
|
+
|
1
7
|
# 0.4.3
|
2
8
|
|
3
9
|
* Limit `trailblazer-activity` dependency to `< 0.13.0`.
|
data/Gemfile
CHANGED
@@ -9,4 +9,5 @@ gem "rubocop", require: false
|
|
9
9
|
|
10
10
|
# gem "trailblazer-developer", path: "../trailblazer-developer"
|
11
11
|
# gem "trailblazer-activity", path: "../trailblazer-activity"
|
12
|
+
gem "trailblazer-activity", github: "trailblazer/trailblazer-activity"
|
12
13
|
# gem "trailblazer-activity", path: "../circuit"
|
data/README.md
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
# Activity-DSL-Linear
|
2
2
|
|
3
3
|
The `activity-dsl-linear` gem brings:
|
4
|
-
- [Path](https://
|
5
|
-
- [Railway](https://
|
6
|
-
- [Fasttrack](https://
|
4
|
+
- [Path](https://trailblazer.to/2.1/docs/activity.html#activity-strategy-path)
|
5
|
+
- [Railway](https://trailblazer.to/2.1/docs/activity.html#activity-strategy-railway)
|
6
|
+
- [Fasttrack](https://trailblazer.to/2.1/docs/activity.html#activity-strategy-fasttrack)
|
7
7
|
|
8
8
|
DSLs strategies for buildig activities. It is build around [`activity`](https://github.com/trailblazer/trailblazer-activity) gem.
|
9
9
|
|
10
|
-
Please find the [full documentation on the Trailblazer website](https://
|
10
|
+
Please find the [full documentation on the Trailblazer website](https://trailblazer.to/2.1/docs/activity.html#activity-strategy).
|
11
11
|
|
12
12
|
## Example
|
13
13
|
|
14
|
-
The `activity-dsl-linear` 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://
|
14
|
+
The `activity-dsl-linear` 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)).
|
15
15
|
|
16
16
|
```ruby
|
17
17
|
require "trailblazer-activity"
|
@@ -79,15 +79,15 @@ With Activity, modeling business processes turns out to be ridiculously simple:
|
|
79
79
|
|
80
80
|
## Operation
|
81
81
|
|
82
|
-
Trailblazer's [`Operation`](https://
|
82
|
+
Trailblazer's [`Operation`](https://trailblazer.to/2.1/docs/operation.html#operation-overview) internally uses an activity to model the processes.
|
83
83
|
|
84
84
|
## Workflow
|
85
|
-
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://
|
85
|
+
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.
|
86
86
|
|
87
|
-
<img src="http://
|
87
|
+
<img src="http://trailblazer.to/2.1/dist/img/flow.png">
|
88
88
|
|
89
89
|
## License
|
90
90
|
|
91
91
|
© Copyright 2018, Trailblazer GmbH
|
92
92
|
|
93
|
-
Licensed under the LGPLv3 license. We also offer a commercial-friendly [license](https://
|
93
|
+
Licensed under the LGPLv3 license. We also offer a commercial-friendly [license](https://trailblazer.to/2.1/docs/pro.html#pro-license).
|
@@ -7,154 +7,149 @@ module Trailblazer
|
|
7
7
|
module Normalizer
|
8
8
|
module_function
|
9
9
|
|
10
|
-
#
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
{
|
16
|
-
"activity.normalize_step_interface" => method(:normalize_step_interface), # first
|
17
|
-
"activity.normalize_for_macro" => method(:merge_user_options),
|
18
|
-
"activity.normalize_normalizer_options" => method(:merge_normalizer_options),
|
19
|
-
"activity.normalize_context" => method(:normalize_context),
|
20
|
-
"activity.normalize_id" => method(:normalize_id),
|
21
|
-
"activity.normalize_override" => method(:normalize_override),
|
22
|
-
"activity.wrap_task_with_step_interface" => method(:wrap_task_with_step_interface), # last
|
23
|
-
"activity.inherit_option" => method(:inherit_option),
|
24
|
-
},
|
10
|
+
# Wrap {task} with {Trailblazer::Option} and execute it with kw args in {#call}.
|
11
|
+
# Note that this instance always return {Right}.
|
12
|
+
class Task < TaskBuilder::Task
|
13
|
+
def call(wrap_ctx, flow_options={})
|
14
|
+
result = call_option(@task, [wrap_ctx, flow_options]) # DISCUSS: this mutates {ctx}.
|
25
15
|
|
26
|
-
|
27
|
-
|
16
|
+
return wrap_ctx, flow_options
|
17
|
+
end
|
18
|
+
end
|
28
19
|
|
29
|
-
|
30
|
-
|
20
|
+
def Task(user_proc)
|
21
|
+
Normalizer::Task.new(Trailblazer::Option(user_proc), user_proc)
|
22
|
+
end
|
31
23
|
|
24
|
+
# activity_normalizer.([{options:, user_options:, normalizer_options: }])
|
25
|
+
def activity_normalizer(pipeline)
|
26
|
+
pipeline = TaskWrap::Pipeline.prepend(
|
27
|
+
pipeline,
|
28
|
+
nil, # this means, put it to the beginning.
|
32
29
|
{
|
33
|
-
"activity.
|
34
|
-
"activity.
|
35
|
-
"activity.
|
30
|
+
"activity.normalize_step_interface" => Normalizer.Task(method(:normalize_step_interface)), # first
|
31
|
+
"activity.normalize_for_macro" => Normalizer.Task(method(:merge_user_options)),
|
32
|
+
"activity.normalize_normalizer_options" => Normalizer.Task(method(:merge_normalizer_options)),
|
33
|
+
"activity.normalize_non_symbol_options" => Normalizer.Task(method(:normalize_non_symbol_options)),
|
34
|
+
"activity.normalize_context" => method(:normalize_context),
|
35
|
+
"activity.normalize_id" => Normalizer.Task(method(:normalize_id)),
|
36
|
+
"activity.normalize_override" => Normalizer.Task(method(:normalize_override)),
|
37
|
+
"activity.wrap_task_with_step_interface" => Normalizer.Task(method(:wrap_task_with_step_interface)), # last
|
38
|
+
"activity.inherit_option" => Normalizer.Task(method(:inherit_option)),
|
36
39
|
},
|
37
|
-
|
38
|
-
Linear::Insert.method(:Prepend), "path.wirings"
|
39
40
|
)
|
40
41
|
|
41
|
-
|
42
|
-
|
43
|
-
|
42
|
+
pipeline = TaskWrap::Pipeline.prepend(
|
43
|
+
pipeline,
|
44
|
+
"path.wirings",
|
44
45
|
{
|
45
|
-
"activity.
|
46
|
+
"activity.normalize_outputs_from_dsl" => Normalizer.Task(method(:normalize_outputs_from_dsl)), # Output(Signal, :semantic) => Id()
|
47
|
+
"activity.normalize_connections_from_dsl" => Normalizer.Task(method(:normalize_connections_from_dsl)),
|
48
|
+
"activity.input_output_dsl" => Normalizer.Task(method(:input_output_dsl)),
|
46
49
|
},
|
50
|
+
)
|
47
51
|
|
48
|
-
|
52
|
+
pipeline = TaskWrap::Pipeline.append(
|
53
|
+
pipeline,
|
54
|
+
nil,
|
55
|
+
["activity.cleanup_options", method(:cleanup_options)]
|
49
56
|
)
|
50
|
-
|
51
|
-
|
57
|
+
|
58
|
+
pipeline
|
52
59
|
end
|
53
60
|
|
54
61
|
# Specific to the "step DSL": if the first argument is a callable, wrap it in a {step_interface_builder}
|
55
62
|
# since its interface expects the step interface, but the circuit will call it with circuit interface.
|
56
|
-
def normalize_step_interface(
|
57
|
-
options = case
|
63
|
+
def normalize_step_interface(ctx, options:, **)
|
64
|
+
options = case options
|
58
65
|
when Hash
|
59
66
|
# Circuit Interface
|
60
|
-
task =
|
61
|
-
id =
|
67
|
+
task = options.fetch(:task)
|
68
|
+
id = options[:id]
|
62
69
|
|
63
70
|
if task.is_a?(Symbol)
|
64
71
|
# step task: :find, id: :load
|
65
|
-
{ **
|
72
|
+
{ **options, id: (id || task), task: Trailblazer::Option(task) }
|
66
73
|
else
|
67
74
|
# step task: Callable, ... (Subprocess, Proc, macros etc)
|
68
|
-
|
75
|
+
options # NOOP
|
69
76
|
end
|
70
77
|
else
|
71
78
|
# Step Interface
|
72
79
|
# step :find, ...
|
73
80
|
# step Callable, ... (Method, Proc etc)
|
74
|
-
{ task:
|
81
|
+
{ task: options, wrap_task: true }
|
75
82
|
end
|
76
83
|
|
77
|
-
|
84
|
+
ctx[:options] = options
|
78
85
|
end
|
79
86
|
|
80
|
-
def wrap_task_with_step_interface(
|
81
|
-
return
|
82
|
-
|
83
|
-
step_interface_builder = ctx[:step_interface_builder] # FIXME: use kw!
|
84
|
-
task = ctx[:task] # FIXME: use kw!
|
87
|
+
def wrap_task_with_step_interface(ctx, wrap_task: false, step_interface_builder:, task:, **)
|
88
|
+
return unless wrap_task
|
85
89
|
|
86
|
-
|
87
|
-
|
88
|
-
return Trailblazer::Activity::Right, [ctx.merge(task: wrapped_task), flow_options]
|
90
|
+
ctx[:task] = step_interface_builder.(task)
|
89
91
|
end
|
90
92
|
|
91
|
-
def normalize_id(
|
92
|
-
|
93
|
-
|
94
|
-
return Trailblazer::Activity::Right, [ctx.merge(id: id), flow_options]
|
93
|
+
def normalize_id(ctx, id: false, task:, **)
|
94
|
+
ctx[:id] = id || task
|
95
95
|
end
|
96
96
|
|
97
97
|
# {:override} really only makes sense for {step Macro(), {override: true}} where the {user_options}
|
98
98
|
# dictate the overriding.
|
99
|
-
def normalize_override(
|
100
|
-
|
101
|
-
|
102
|
-
return Trailblazer::Activity::Right, [ctx, flow_options]
|
99
|
+
def normalize_override(ctx, id:, override: false, **)
|
100
|
+
return unless override
|
101
|
+
ctx[:replace] = (id || raise)
|
103
102
|
end
|
104
103
|
|
105
104
|
# make ctx[:options] the actual ctx
|
106
|
-
def merge_user_options(
|
107
|
-
|
108
|
-
|
109
|
-
ctx = ctx.merge(options: options.merge(ctx[:user_options])) # Note that the user options are merged over the macro options.
|
110
|
-
|
111
|
-
return Trailblazer::Activity::Right, [ctx, flow_options]
|
105
|
+
def merge_user_options(ctx, options:, **)
|
106
|
+
# {options} are either a <#task> or {} from macro
|
107
|
+
ctx[:options] = options.merge(ctx[:user_options]) # Note that the user options are merged over the macro options.
|
112
108
|
end
|
113
109
|
|
114
110
|
# {:normalizer_options} such as {:track_name} get overridden by user/macro.
|
115
|
-
def merge_normalizer_options(
|
116
|
-
|
117
|
-
|
118
|
-
ctx = ctx.merge(options: normalizer_options.merge(ctx[:options])) #
|
119
|
-
|
120
|
-
return Trailblazer::Activity::Right, [ctx, flow_options]
|
111
|
+
def merge_normalizer_options(ctx, normalizer_options:, options:, **)
|
112
|
+
ctx[:options] = normalizer_options.merge(options)
|
121
113
|
end
|
122
114
|
|
123
|
-
def normalize_context(
|
115
|
+
def normalize_context(ctx, flow_options)
|
124
116
|
ctx = ctx[:options]
|
125
117
|
|
126
|
-
return
|
118
|
+
return ctx, flow_options
|
127
119
|
end
|
128
120
|
|
129
121
|
# Compile the actual {Seq::Row}'s {wiring}.
|
130
122
|
# This combines {:connections} and {:outputs}
|
131
|
-
def compile_wirings(
|
132
|
-
connections = ctx[:connections] || raise # FIXME
|
133
|
-
outputs = ctx[:outputs] || raise # FIXME
|
134
|
-
|
123
|
+
def compile_wirings(ctx, connections:, outputs:, id:, **)
|
135
124
|
ctx[:wirings] =
|
136
125
|
connections.collect do |semantic, (search_strategy_builder, *search_args)|
|
137
|
-
output = outputs[semantic] || raise("No `#{semantic}` output found for #{
|
126
|
+
output = outputs[semantic] || raise("No `#{semantic}` output found for #{id.inspect} and outputs #{outputs.inspect}")
|
138
127
|
|
139
128
|
search_strategy_builder.( # return proc to be called when compiling Seq, e.g. {ById(output, :id)}
|
140
129
|
output,
|
141
130
|
*search_args
|
142
131
|
)
|
143
132
|
end
|
144
|
-
|
145
|
-
return Trailblazer::Activity::Right, [ctx, flow_options]
|
146
133
|
end
|
147
134
|
|
148
|
-
#
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
135
|
+
# Move DSL user options such as {Output(:success) => Track(:found)} to
|
136
|
+
# a new key {options[:non_symbol_options]}.
|
137
|
+
# This allows using {options} as a {**ctx}-able hash in Ruby 2.6 and 3.0.
|
138
|
+
def normalize_non_symbol_options(ctx, options:, **)
|
139
|
+
symbol_options = options.find_all { |k, v| k.is_a?(Symbol) }.to_h
|
140
|
+
non_symbol_options = options.slice(*(options.keys - symbol_options.keys))
|
141
|
+
# raise unless (symbol_options.size+non_symbol_options.size) == options.size
|
142
|
+
|
143
|
+
ctx[:options] = symbol_options.merge(non_symbol_options: non_symbol_options)
|
144
|
+
end
|
153
145
|
|
146
|
+
# Process {Output(:semantic) => target} and make them {:connections}.
|
147
|
+
def normalize_connections_from_dsl(ctx, connections:, adds:, non_symbol_options:, **)
|
154
148
|
# Find all {Output() => Track()/Id()/End()}
|
155
|
-
|
156
|
-
|
149
|
+
output_configs = non_symbol_options.find_all{ |k,v| k.kind_of?(Activity::DSL::Linear::OutputSemantic) }
|
150
|
+
return unless output_configs.any?
|
157
151
|
|
152
|
+
output_configs.each do |output, cfg|
|
158
153
|
new_connections, add =
|
159
154
|
if cfg.is_a?(Activity::DSL::Linear::Track)
|
160
155
|
[output_to_track(ctx, output, cfg), cfg.adds]
|
@@ -177,9 +172,8 @@ module Trailblazer
|
|
177
172
|
adds += add
|
178
173
|
end
|
179
174
|
|
180
|
-
|
181
|
-
|
182
|
-
return Trailblazer::Activity::Right, [new_ctx, flow_options]
|
175
|
+
ctx[:connections] = connections
|
176
|
+
ctx[:adds] = adds
|
183
177
|
end
|
184
178
|
|
185
179
|
def output_to_track(ctx, output, track)
|
@@ -205,53 +199,38 @@ module Trailblazer
|
|
205
199
|
end
|
206
200
|
|
207
201
|
# Output(Signal, :semantic) => Id()
|
208
|
-
def normalize_outputs_from_dsl(
|
209
|
-
|
202
|
+
def normalize_outputs_from_dsl(ctx, non_symbol_options:, outputs:, **)
|
203
|
+
output_configs = non_symbol_options.find_all{ |k,v| k.kind_of?(Activity::Output) }
|
204
|
+
return unless output_configs.any?
|
210
205
|
|
211
|
-
outputs = ctx[:outputs]
|
212
206
|
dsl_options = {}
|
213
207
|
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
outputs = outputs.merge(output.semantic => output)
|
208
|
+
output_configs.collect do |output, cfg| # {cfg} = Track(:success)
|
209
|
+
outputs = outputs.merge(output.semantic => output)
|
218
210
|
dsl_options = dsl_options.merge(Linear.Output(output.semantic) => cfg)
|
219
211
|
end
|
220
212
|
|
221
|
-
|
222
|
-
|
223
|
-
return Trailblazer::Activity::Right, [new_ctx, flow_options]
|
213
|
+
ctx[:outputs] = outputs
|
214
|
+
ctx[:non_symbol_options] = non_symbol_options.merge(dsl_options)
|
224
215
|
end
|
225
216
|
|
226
|
-
def input_output_dsl(
|
227
|
-
config = ctx.select { |k,v| [:input, :output, :output_with_outer_ctx].include?(k) } # TODO: optimize this, we don't have to go through the entire hash.
|
228
|
-
|
229
|
-
return Trailblazer::Activity::Right, [ctx, flow_options] if config.size == 0 # no :input/:output passed.
|
217
|
+
def input_output_dsl(ctx, extensions: [], **)
|
218
|
+
config = ctx.select { |k,v| [:input, :output, :output_with_outer_ctx, :inject].include?(k) } # TODO: optimize this, we don't have to go through the entire hash.
|
219
|
+
return unless config.any? # no :input/:output/:inject passed.
|
230
220
|
|
231
|
-
|
232
|
-
new_ctx[:extensions] = ctx[:extensions] || [] # merge DSL extensions with I/O.
|
233
|
-
new_ctx[:extensions] += [Linear.VariableMapping(**config)]
|
234
|
-
|
235
|
-
return Trailblazer::Activity::Right, [ctx.merge(new_ctx), flow_options]
|
221
|
+
ctx[:extensions] = extensions + [Linear.VariableMapping(**config)]
|
236
222
|
end
|
237
223
|
|
238
224
|
# Currently, the {:inherit} option copies over {:connections} from the original step
|
239
225
|
# and merges them with the (prolly) connections passed from the user.
|
240
|
-
def inherit_option(
|
241
|
-
return
|
242
|
-
|
243
|
-
sequence = ctx[:sequence]
|
244
|
-
id = ctx[:id]
|
226
|
+
def inherit_option(ctx, inherit: false, sequence:, id:, extensions: [], **)
|
227
|
+
return unless inherit
|
245
228
|
|
246
229
|
index = Linear::Insert.find_index(sequence, id)
|
247
230
|
row = sequence[index] # from this row we're inheriting options.
|
248
231
|
|
249
|
-
connections = get_inheritable_connections(ctx, row[3][:connections])
|
250
|
-
extensions = Array(row[3][:extensions]) + Array(
|
251
|
-
|
252
|
-
ctx = ctx.merge(connections: connections, extensions: extensions) # "inherit"
|
253
|
-
|
254
|
-
return Trailblazer::Activity::Right, [ctx, flow_options]
|
232
|
+
ctx[:connections] = get_inheritable_connections(ctx, row[3][:connections])
|
233
|
+
ctx[:extensions] = Array(row[3][:extensions]) + Array(extensions)
|
255
234
|
end
|
256
235
|
|
257
236
|
# return connections from {parent} step which are supported by current step
|
@@ -262,11 +241,11 @@ module Trailblazer
|
|
262
241
|
end
|
263
242
|
|
264
243
|
# TODO: make this extendable!
|
265
|
-
def cleanup_options(
|
244
|
+
def cleanup_options(ctx, flow_options)
|
266
245
|
# new_ctx = ctx.reject { |k, v| [:connections, :outputs, :end_id, :step_interface_builder, :failure_end, :track_name, :sequence].include?(k) }
|
267
|
-
new_ctx = ctx.reject { |k, v| [:outputs, :end_id, :step_interface_builder, :failure_end, :track_name, :sequence].include?(k) }
|
246
|
+
new_ctx = ctx.reject { |k, v| [:outputs, :end_id, :step_interface_builder, :failure_end, :track_name, :sequence, :non_symbol_options].include?(k) }
|
268
247
|
|
269
|
-
return
|
248
|
+
return new_ctx, flow_options
|
270
249
|
end
|
271
250
|
end
|
272
251
|
|
@@ -39,27 +39,20 @@ module Trailblazer
|
|
39
39
|
|
40
40
|
# Compiles and maintains all final normalizers for a specific DSL.
|
41
41
|
class Normalizer
|
42
|
-
def compile_normalizer(normalizer_sequence)
|
43
|
-
process = Trailblazer::Activity::DSL::Linear::Compiler.(normalizer_sequence)
|
44
|
-
process.to_h[:circuit]
|
45
|
-
end
|
46
|
-
|
47
42
|
# [gets instantiated at compile time.]
|
48
43
|
#
|
49
44
|
# We simply compile the activities that represent the normalizers for #step, #pass, etc.
|
50
45
|
# This can happen at compile-time, as normalizers are stateless.
|
51
46
|
def initialize(normalizer_sequences)
|
52
|
-
@normalizers =
|
53
|
-
normalizer_sequences.collect { |name, seq| [name, compile_normalizer(seq)] }
|
54
|
-
]
|
47
|
+
@normalizers = normalizer_sequences
|
55
48
|
end
|
56
49
|
|
57
50
|
# Execute the specific normalizer (step, fail, pass) for a particular option set provided
|
58
51
|
# by the DSL user. This is usually when you call Operation::step.
|
59
|
-
def call(name,
|
52
|
+
def call(name, ctx)
|
60
53
|
normalizer = @normalizers.fetch(name)
|
61
|
-
|
62
|
-
|
54
|
+
wrap_ctx, _ = normalizer.(ctx, nil)
|
55
|
+
wrap_ctx
|
63
56
|
end
|
64
57
|
end
|
65
58
|
end # State
|
@@ -4,28 +4,164 @@ module Trailblazer
|
|
4
4
|
module Linear
|
5
5
|
# Normalizer-steps to implement {:input} and {:output}
|
6
6
|
# Returns an Extension instance to be thrown into the `step` DSL arguments.
|
7
|
-
def self.VariableMapping(input:
|
8
|
-
input
|
9
|
-
VariableMapping::Input::Scoped.new(
|
10
|
-
Trailblazer::Option(VariableMapping::filter_for(input))
|
11
|
-
)
|
12
|
-
|
13
|
-
unscope_class = output_with_outer_ctx ? VariableMapping::Output::Unscoped::WithOuterContext : VariableMapping::Output::Unscoped
|
14
|
-
|
15
|
-
output =
|
16
|
-
unscope_class.new(
|
17
|
-
Trailblazer::Option(VariableMapping::filter_for(output))
|
18
|
-
)
|
7
|
+
def self.VariableMapping(input: nil, output: VariableMapping.default_output, output_with_outer_ctx: false, inject: [])
|
8
|
+
merge_instructions = VariableMapping.merge_instructions_from_dsl(input: input, output: output, output_with_outer_ctx: output_with_outer_ctx, inject: inject)
|
19
9
|
|
20
|
-
TaskWrap::Extension(
|
21
|
-
merge: TaskWrap::VariableMapping.merge_for(input, output, id: input.object_id), # wraps filters: {Input(input), Output(output)}
|
22
|
-
)
|
10
|
+
TaskWrap::Extension(merge: merge_instructions)
|
23
11
|
end
|
24
12
|
|
25
13
|
module VariableMapping
|
26
14
|
module_function
|
27
15
|
|
16
|
+
# For the input filter we
|
17
|
+
# 1. create a separate {Pipeline} instance {pipe}. Depending on the user's options, this might have up to four steps.
|
18
|
+
# 2. The {pipe} is run in a lamdba {input}, the lambda returns the pipe's ctx[:input_ctx].
|
19
|
+
# 3. The {input} filter in turn is wrapped into an {Activity::TaskWrap::Input} object via {#merge_instructions_for}.
|
20
|
+
# 4. The {TaskWrap::Input} instance is then finally placed into the taskWrap as {"task_wrap.input"}.
|
21
|
+
#
|
28
22
|
# @private
|
23
|
+
def merge_instructions_from_dsl(input:, output:, output_with_outer_ctx:, inject:)
|
24
|
+
# FIXME: this could (should?) be in Normalizer?
|
25
|
+
inject_passthrough = inject.find_all { |name| name.is_a?(Symbol) }
|
26
|
+
inject_with_default = inject.find { |name| name.is_a?(Hash) } # FIXME: we only support one default hash in the DSL so far.
|
27
|
+
|
28
|
+
input_steps = [
|
29
|
+
["input.init_hash", VariableMapping.method(:initial_input_hash)],
|
30
|
+
]
|
31
|
+
|
32
|
+
# With only injections defined, we do not filter out anything, we use the original ctx
|
33
|
+
# and _add_ defaulting for injected variables.
|
34
|
+
if !input # only injections defined
|
35
|
+
input_steps << ["input.default_input", VariableMapping.method(:default_input_ctx)]
|
36
|
+
end
|
37
|
+
|
38
|
+
if input # :input or :input/:inject
|
39
|
+
input_steps << ["input.add_variables", VariableMapping.method(:add_variables)]
|
40
|
+
|
41
|
+
input_filter = Trailblazer::Option(VariableMapping::filter_for(input))
|
42
|
+
end
|
43
|
+
|
44
|
+
if inject_passthrough || inject_with_default
|
45
|
+
input_steps << ["input.add_injections", VariableMapping.method(:add_injections)] # we now allow one filter per injected variable.
|
46
|
+
end
|
47
|
+
|
48
|
+
if inject_passthrough || inject_with_default
|
49
|
+
injections = inject.collect do |name|
|
50
|
+
if name.is_a?(Symbol)
|
51
|
+
[[name, Trailblazer::Option(->(*) { [false, name] })]] # we don't want defaulting, this return value signalizes "please pass-through, only".
|
52
|
+
else # we automatically assume this is a hash of callables
|
53
|
+
name.collect do |_name, filter|
|
54
|
+
[_name, Trailblazer::Option(->(ctx, **kws) { [true, _name, filter.(ctx, **kws)] })] # filter will compute the default value
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end.flatten(1).to_h
|
58
|
+
end
|
59
|
+
|
60
|
+
input_steps << ["input.scope", VariableMapping.method(:scope)]
|
61
|
+
|
62
|
+
|
63
|
+
pipe = Activity::TaskWrap::Pipeline.new(input_steps)
|
64
|
+
|
65
|
+
# gets wrapped by {VariableMapping::Input} and called there.
|
66
|
+
# API: @filter.([ctx, original_flow_options], **original_circuit_options)
|
67
|
+
# input = Trailblazer::Option(->(original_ctx, **) { })
|
68
|
+
input = ->((ctx, flow_options), **circuit_options) do # This filter is called by {TaskWrap::Input#call} in the {activity} gem.
|
69
|
+
wrap_ctx, _ = pipe.({injections: injections, input_filter: input_filter}, [[ctx, flow_options], circuit_options])
|
70
|
+
|
71
|
+
wrap_ctx[:input_ctx]
|
72
|
+
end
|
73
|
+
|
74
|
+
# 1. {} empty input hash
|
75
|
+
# 1. input # dynamic => hash
|
76
|
+
# 2. input_map => hash
|
77
|
+
# 3. inject => hash
|
78
|
+
# 4. Input::Scoped()
|
79
|
+
|
80
|
+
unscope_class = output_with_outer_ctx ? VariableMapping::Output::Unscoped::WithOuterContext : VariableMapping::Output::Unscoped
|
81
|
+
|
82
|
+
output =
|
83
|
+
unscope_class.new(
|
84
|
+
Trailblazer::Option(VariableMapping::filter_for(output))
|
85
|
+
)
|
86
|
+
|
87
|
+
TaskWrap::VariableMapping.merge_instructions_for(input, output, id: input.object_id) # wraps filters: {Input(input), Output(output)}
|
88
|
+
end
|
89
|
+
|
90
|
+
# DISCUSS: improvable sections such as merge vs hash[]=
|
91
|
+
def initial_input_hash(wrap_ctx, original_args)
|
92
|
+
wrap_ctx = wrap_ctx.merge(input_hash: {})
|
93
|
+
|
94
|
+
return wrap_ctx, original_args
|
95
|
+
end
|
96
|
+
|
97
|
+
# Merge all original ctx variables into the new input_ctx.
|
98
|
+
# This happens when no {:input} is provided.
|
99
|
+
def default_input_ctx(wrap_ctx, original_args)
|
100
|
+
((original_ctx, _), _) = original_args
|
101
|
+
|
102
|
+
MergeVariables(original_ctx, wrap_ctx, original_args)
|
103
|
+
end
|
104
|
+
|
105
|
+
# TODO: test {nil} default
|
106
|
+
# FIXME: what if you don't want inject but always the value from the config?
|
107
|
+
# Add injected variables if they're present on
|
108
|
+
# the original, incoming ctx.
|
109
|
+
def add_injections(wrap_ctx, original_args)
|
110
|
+
name2filter = wrap_ctx[:injections]
|
111
|
+
((original_ctx, _), circuit_options) = original_args
|
112
|
+
|
113
|
+
injections =
|
114
|
+
name2filter.collect do |name, filter|
|
115
|
+
# DISCUSS: should we remove {is_defaulted} and infer type from {filter} or the return value?
|
116
|
+
is_defaulted, new_name, default_value = filter.(original_ctx, keyword_arguments: original_ctx.to_hash, **circuit_options) # FIXME: interface? # {filter} exposes {Option} interface
|
117
|
+
|
118
|
+
original_ctx.key?(name) ?
|
119
|
+
[new_name, original_ctx[name]] : (
|
120
|
+
is_defaulted ? [new_name, default_value] : nil
|
121
|
+
)
|
122
|
+
end.compact.to_h # FIXME: are we <2.6 safe here?
|
123
|
+
|
124
|
+
MergeVariables(injections, wrap_ctx, original_args)
|
125
|
+
end
|
126
|
+
|
127
|
+
# Implements {:input}.
|
128
|
+
def add_variables(wrap_ctx, original_args)
|
129
|
+
filter = wrap_ctx[:input_filter]
|
130
|
+
((original_ctx, _), circuit_options) = original_args
|
131
|
+
|
132
|
+
# this is the actual logic.
|
133
|
+
variables = filter.(original_ctx, keyword_arguments: original_ctx.to_hash, **circuit_options)
|
134
|
+
|
135
|
+
MergeVariables(variables, wrap_ctx, original_args)
|
136
|
+
end
|
137
|
+
|
138
|
+
# Finally, create a new input ctx from all the
|
139
|
+
# collected input variables.
|
140
|
+
# This goes into the step/nested OP.
|
141
|
+
def scope(wrap_ctx, original_args)
|
142
|
+
((_, flow_options), _) = original_args
|
143
|
+
|
144
|
+
# this is the actual context passed into the step.
|
145
|
+
wrap_ctx[:input_ctx] = Trailblazer::Context(
|
146
|
+
wrap_ctx[:input_hash],
|
147
|
+
{}, # mutable variables
|
148
|
+
flow_options[:context_options]
|
149
|
+
)
|
150
|
+
|
151
|
+
return wrap_ctx, original_args
|
152
|
+
end
|
153
|
+
|
154
|
+
# Last call in every step. Currently replaces {:input_ctx} by adding variables using {#merge}.
|
155
|
+
# DISCUSS: improve here?
|
156
|
+
def MergeVariables(variables, wrap_ctx, original_args)
|
157
|
+
wrap_ctx[:input_hash] = wrap_ctx[:input_hash].merge(variables)
|
158
|
+
|
159
|
+
return wrap_ctx, original_args
|
160
|
+
end
|
161
|
+
|
162
|
+
# @private
|
163
|
+
# The default {:output} filter only returns the "mutable" part of the inner ctx.
|
164
|
+
# This means only variables added using {inner_ctx[..]=} are merged on the outside.
|
29
165
|
def default_output
|
30
166
|
->(scoped, **) do
|
31
167
|
_wrapped, mutable = scoped.decompose # `_wrapped` is what the `:input` filter returned, `mutable` is what the task wrote to `scoped`.
|
@@ -33,11 +169,6 @@ module Trailblazer
|
|
33
169
|
end
|
34
170
|
end
|
35
171
|
|
36
|
-
# @private
|
37
|
-
def default_input
|
38
|
-
->(ctx, **) { ctx }
|
39
|
-
end
|
40
|
-
|
41
172
|
# Returns a filter proc to be called in an Option.
|
42
173
|
# @private
|
43
174
|
def filter_for(filter)
|
@@ -74,22 +205,6 @@ module Trailblazer
|
|
74
205
|
end
|
75
206
|
end
|
76
207
|
|
77
|
-
module Input
|
78
|
-
class Scoped
|
79
|
-
def initialize(filter)
|
80
|
-
@filter = filter
|
81
|
-
end
|
82
|
-
|
83
|
-
def call((original_ctx, flow_options), **circuit_options)
|
84
|
-
Trailblazer::Context(
|
85
|
-
@filter.(original_ctx, keyword_arguments: original_ctx.to_hash, **circuit_options),
|
86
|
-
{},
|
87
|
-
flow_options[:context_options]
|
88
|
-
)
|
89
|
-
end
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
208
|
module Output
|
94
209
|
# Merge the resulting {@filter.()} hash back into the original ctx.
|
95
210
|
# DISCUSS: do we need the original_ctx as a filter argument?
|
@@ -98,19 +213,20 @@ module Trailblazer
|
|
98
213
|
@filter = filter
|
99
214
|
end
|
100
215
|
|
216
|
+
# The returned hash from {@filter} is merged with the original ctx.
|
101
217
|
def call(new_ctx, (original_ctx, flow_options), **circuit_options)
|
102
218
|
original_ctx.merge(
|
103
219
|
call_filter(new_ctx, [original_ctx, flow_options], **circuit_options)
|
104
220
|
)
|
105
221
|
end
|
106
222
|
|
107
|
-
def call_filter(new_ctx, (
|
223
|
+
def call_filter(new_ctx, (_original_ctx, _flow_options), **circuit_options)
|
108
224
|
# Pass {inner_ctx, **inner_ctx}
|
109
225
|
@filter.(new_ctx, keyword_arguments: new_ctx.to_hash, **circuit_options)
|
110
226
|
end
|
111
227
|
|
112
228
|
class WithOuterContext < Unscoped
|
113
|
-
def call_filter(new_ctx, (original_ctx,
|
229
|
+
def call_filter(new_ctx, (original_ctx, _flow_options), **circuit_options)
|
114
230
|
# Pass {inner_ctx, outer_ctx, **inner_ctx}
|
115
231
|
@filter.(new_ctx, original_ctx, keyword_arguments: new_ctx.to_hash, **circuit_options)
|
116
232
|
end
|
@@ -6,7 +6,7 @@ module Trailblazer
|
|
6
6
|
end
|
7
7
|
end
|
8
8
|
|
9
|
-
# Implementation module that can be passed to `Activity
|
9
|
+
# Implementation module that can be passed to `Activity()`.
|
10
10
|
class FastTrack
|
11
11
|
Linear = Activity::DSL::Linear
|
12
12
|
|
@@ -22,105 +22,97 @@ module Trailblazer
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def normalizer_for_fail
|
25
|
-
|
25
|
+
pipeline = step_options(Trailblazer::Activity::Railway::DSL.normalizer_for_fail)
|
26
26
|
|
27
|
-
|
28
|
-
|
27
|
+
TaskWrap::Pipeline.prepend(
|
28
|
+
pipeline,
|
29
|
+
"path.wirings",
|
29
30
|
|
30
31
|
{
|
31
|
-
"fast_track.fail_fast_option_for_fail" => method(:fail_fast_option_for_fail),
|
32
|
-
}
|
33
|
-
Linear::Insert.method(:Prepend), "path.wirings"
|
32
|
+
"fast_track.fail_fast_option_for_fail" => Linear::Normalizer.Task(method(:fail_fast_option_for_fail)),
|
33
|
+
}
|
34
34
|
)
|
35
35
|
end
|
36
36
|
|
37
37
|
def normalizer_for_pass
|
38
|
-
|
38
|
+
pipeline = step_options(Trailblazer::Activity::Railway::DSL.normalizer_for_pass)
|
39
39
|
|
40
|
-
|
41
|
-
|
40
|
+
TaskWrap::Pipeline.prepend(
|
41
|
+
pipeline,
|
42
|
+
"path.wirings",
|
42
43
|
|
43
44
|
{
|
44
|
-
"fast_track.pass_fast_option_for_pass" => method(:pass_fast_option_for_pass),
|
45
|
-
}
|
46
|
-
Linear::Insert.method(:Prepend), "path.wirings"
|
45
|
+
"fast_track.pass_fast_option_for_pass" => Linear::Normalizer.Task(method(:pass_fast_option_for_pass)),
|
46
|
+
}
|
47
47
|
)
|
48
48
|
end
|
49
49
|
|
50
|
-
def step_options(
|
51
|
-
|
52
|
-
|
50
|
+
def step_options(pipeline)
|
51
|
+
TaskWrap::Pipeline.prepend(
|
52
|
+
pipeline,
|
53
|
+
"path.wirings",
|
53
54
|
|
54
55
|
{
|
55
|
-
"fast_track.pass_fast_option" => method(:pass_fast_option),
|
56
|
-
"fast_track.fail_fast_option" => method(:fail_fast_option),
|
57
|
-
"fast_track.fast_track_option" => method(:fast_track_option),
|
58
|
-
}
|
59
|
-
Linear::Insert.method(:Prepend), "path.wirings"
|
56
|
+
"fast_track.pass_fast_option" => Linear::Normalizer.Task(method(:pass_fast_option)),
|
57
|
+
"fast_track.fail_fast_option" => Linear::Normalizer.Task(method(:fail_fast_option)),
|
58
|
+
"fast_track.fast_track_option" => Linear::Normalizer.Task(method(:fast_track_option)),
|
59
|
+
}
|
60
60
|
)
|
61
61
|
end
|
62
62
|
|
63
|
-
def pass_fast_option(
|
64
|
-
ctx = merge_connections_for(ctx,
|
63
|
+
def pass_fast_option(ctx, **)
|
64
|
+
ctx = merge_connections_for!(ctx, :pass_fast, :success, **ctx)
|
65
65
|
|
66
|
-
ctx = merge_connections_for(ctx,
|
67
|
-
ctx = merge_outputs_for(ctx,
|
68
|
-
pass_fast: Activity.Output(Activity::FastTrack::PassFast, :pass_fast),
|
66
|
+
ctx = merge_connections_for!(ctx, :pass_fast, :pass_fast, :pass_fast, **ctx)
|
67
|
+
ctx = merge_outputs_for!(ctx,
|
68
|
+
{pass_fast: Activity.Output(Activity::FastTrack::PassFast, :pass_fast)},
|
69
|
+
**ctx
|
69
70
|
)
|
70
|
-
|
71
|
-
return Right, [ctx, flow_options]
|
72
71
|
end
|
73
72
|
|
74
|
-
def pass_fast_option_for_pass(
|
75
|
-
ctx = merge_connections_for(ctx,
|
76
|
-
ctx = merge_connections_for(ctx,
|
77
|
-
|
78
|
-
return Right, [ctx, flow_options]
|
73
|
+
def pass_fast_option_for_pass(ctx, **)
|
74
|
+
ctx = merge_connections_for!(ctx, :pass_fast, :failure, **ctx)
|
75
|
+
ctx = merge_connections_for!(ctx, :pass_fast, :success, **ctx)
|
79
76
|
end
|
80
77
|
|
81
|
-
def fail_fast_option(
|
82
|
-
ctx = merge_connections_for(ctx,
|
78
|
+
def fail_fast_option(ctx, **)
|
79
|
+
ctx = merge_connections_for!(ctx, :fail_fast, :failure, **ctx)
|
83
80
|
|
84
|
-
ctx = merge_connections_for(ctx,
|
85
|
-
ctx = merge_outputs_for(ctx,
|
86
|
-
fail_fast: Activity.Output(Activity::FastTrack::FailFast, :fail_fast),
|
81
|
+
ctx = merge_connections_for!(ctx, :fail_fast, :fail_fast, :fail_fast, **ctx)
|
82
|
+
ctx = merge_outputs_for!(ctx,
|
83
|
+
{fail_fast: Activity.Output(Activity::FastTrack::FailFast, :fail_fast)},
|
84
|
+
**ctx
|
87
85
|
)
|
88
|
-
|
89
|
-
return Right, [ctx, flow_options]
|
90
86
|
end
|
91
87
|
|
92
|
-
def fail_fast_option_for_fail(
|
93
|
-
ctx = merge_connections_for(ctx,
|
94
|
-
ctx = merge_connections_for(ctx,
|
95
|
-
|
96
|
-
return Right, [ctx, flow_options]
|
88
|
+
def fail_fast_option_for_fail(ctx, **)
|
89
|
+
ctx = merge_connections_for!(ctx, :fail_fast, :failure, **ctx)
|
90
|
+
ctx = merge_connections_for!(ctx, :fail_fast, :success, **ctx)
|
97
91
|
end
|
98
92
|
|
99
|
-
def fast_track_option(
|
100
|
-
return
|
93
|
+
def fast_track_option(ctx, fast_track: false, **)
|
94
|
+
return unless fast_track
|
101
95
|
|
102
|
-
ctx = merge_connections_for(ctx,
|
103
|
-
ctx = merge_connections_for(ctx,
|
96
|
+
ctx = merge_connections_for!(ctx, :fast_track, :fail_fast, :fail_fast, **ctx)
|
97
|
+
ctx = merge_connections_for!(ctx, :fast_track, :pass_fast, :pass_fast, **ctx)
|
104
98
|
|
105
|
-
ctx = merge_outputs_for(ctx,
|
106
|
-
pass_fast: Activity.Output(Activity::FastTrack::PassFast, :pass_fast),
|
107
|
-
|
99
|
+
ctx = merge_outputs_for!(ctx,
|
100
|
+
{pass_fast: Activity.Output(Activity::FastTrack::PassFast, :pass_fast),
|
101
|
+
fail_fast: Activity.Output(Activity::FastTrack::FailFast, :fail_fast)},
|
102
|
+
**ctx
|
108
103
|
)
|
109
|
-
|
110
|
-
return Right, [ctx, flow_options]
|
111
104
|
end
|
112
105
|
|
113
|
-
def merge_connections_for(ctx,
|
114
|
-
return ctx unless
|
106
|
+
def merge_connections_for!(ctx, option_name, semantic, magnetic_to=option_name, connections:, **)
|
107
|
+
return ctx unless ctx[option_name]
|
115
108
|
|
116
|
-
|
117
|
-
ctx
|
109
|
+
ctx[:connections] = connections.merge(semantic => [Linear::Search.method(:Forward), magnetic_to])
|
110
|
+
ctx
|
118
111
|
end
|
119
112
|
|
120
|
-
def merge_outputs_for(ctx, outputs)
|
121
|
-
ctx =
|
122
|
-
|
123
|
-
)
|
113
|
+
def merge_outputs_for!(ctx, new_outputs, outputs:, **)
|
114
|
+
ctx[:outputs] = new_outputs.merge(outputs)
|
115
|
+
ctx
|
124
116
|
end
|
125
117
|
|
126
118
|
def initial_sequence(initial_sequence:, fail_fast_end: Activity::End.new(semantic: :fail_fast), pass_fast_end: Activity::End.new(semantic: :pass_fast), **_o)
|
@@ -166,7 +158,6 @@ module Trailblazer
|
|
166
158
|
extend Activity::DSL::Linear::Strategy
|
167
159
|
|
168
160
|
initialize!(Railway::DSL::State.new(**DSL.OptionsForState()))
|
169
|
-
|
170
161
|
end # FastTrack
|
171
162
|
end
|
172
163
|
end
|
@@ -9,20 +9,30 @@ module Trailblazer
|
|
9
9
|
module_function
|
10
10
|
|
11
11
|
def normalizer
|
12
|
-
|
13
|
-
|
14
|
-
|
12
|
+
TaskWrap::Pipeline.new(normalizer_steps.to_a)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Return {Path::Normalizer} sequence.
|
16
|
+
private def normalizer_steps
|
17
|
+
{
|
18
|
+
"path.outputs" => Linear::Normalizer.Task(method(:merge_path_outputs)),
|
19
|
+
"path.connections" => Linear::Normalizer.Task(method(:merge_path_connections)),
|
20
|
+
"path.sequence_insert" => Linear::Normalizer.Task(method(:normalize_sequence_insert)),
|
21
|
+
"path.normalize_duplications" => Linear::Normalizer.Task(method(:normalize_duplications)),
|
22
|
+
"path.magnetic_to" => Linear::Normalizer.Task(method(:normalize_magnetic_to)),
|
23
|
+
"path.wirings" => Linear::Normalizer.Task(Linear::Normalizer.method(:compile_wirings)),
|
24
|
+
}
|
15
25
|
end
|
16
26
|
|
17
27
|
def start_sequence(track_name:)
|
18
28
|
start_default = Activity::Start.new(semantic: :default)
|
19
29
|
start_event = Linear::Sequence.create_row(task: start_default, id: "Start.default", magnetic_to: nil, wirings: [Linear::Search::Forward(unary_outputs[:success], track_name)])
|
20
|
-
_sequence
|
30
|
+
_sequence = Linear::Sequence[start_event]
|
21
31
|
end
|
22
32
|
|
23
33
|
# DISCUSS: still not sure this should sit here.
|
24
34
|
# Pseudo-DSL that prepends {steps} to {sequence}.
|
25
|
-
def prepend_to_path(sequence, steps, insertion_method=Linear::Insert.method(:Prepend), insert_id="End.success")
|
35
|
+
def prepend_to_path(sequence, steps, insertion_method=Linear::Insert.method(:Prepend), insert_id="End.success") # FIXME: where do we need you?
|
26
36
|
new_rows = steps.collect do |id, task|
|
27
37
|
Linear::Sequence.create_row(
|
28
38
|
task: task,
|
@@ -43,32 +53,24 @@ module Trailblazer
|
|
43
53
|
{success: [Linear::Search.method(:Forward), track_name]}
|
44
54
|
end
|
45
55
|
|
46
|
-
def merge_path_outputs(
|
47
|
-
ctx =
|
48
|
-
|
49
|
-
return Right, [ctx, flow_options]
|
56
|
+
def merge_path_outputs(ctx, outputs: nil, **)
|
57
|
+
ctx[:outputs] = outputs || unary_outputs
|
50
58
|
end
|
51
59
|
|
52
|
-
def merge_path_connections(
|
53
|
-
|
54
|
-
ctx = {connections: unary_connections(track_name: track_name)}.merge(ctx)
|
55
|
-
|
56
|
-
return Right, [ctx, flow_options]
|
60
|
+
def merge_path_connections(ctx, track_name:, connections: nil, **)
|
61
|
+
ctx[:connections] = connections || unary_connections(track_name: track_name)
|
57
62
|
end
|
58
63
|
|
59
64
|
# Processes {:before,:after,:replace,:delete} options and
|
60
65
|
# defaults to {before: "End.success"} which, yeah.
|
61
|
-
def normalize_sequence_insert(
|
66
|
+
def normalize_sequence_insert(ctx, end_id:, **)
|
62
67
|
insertion = ctx.keys & sequence_insert_options.keys
|
63
68
|
insertion = insertion[0] || :before
|
64
|
-
|
65
|
-
target = ctx[insertion] || ctx[:end_id]
|
69
|
+
target = ctx[insertion] || end_id
|
66
70
|
|
67
71
|
insertion_method = sequence_insert_options[insertion]
|
68
72
|
|
69
|
-
ctx =
|
70
|
-
|
71
|
-
return Right, [ctx, flow_options]
|
73
|
+
ctx[:sequence_insert] = [Linear::Insert.method(insertion_method), target]
|
72
74
|
end
|
73
75
|
|
74
76
|
# @private
|
@@ -81,51 +83,25 @@ module Trailblazer
|
|
81
83
|
}
|
82
84
|
end
|
83
85
|
|
84
|
-
def normalize_duplications(
|
85
|
-
return
|
86
|
-
|
87
|
-
signal, (ctx, flow_options) = raise_on_duplicate_id([ctx, flow_options])
|
88
|
-
signal, (ctx, flow_options) = clone_duplicate_activity([ctx, flow_options])
|
86
|
+
def normalize_duplications(ctx, replace: false, **)
|
87
|
+
return if replace
|
89
88
|
|
90
|
-
|
89
|
+
raise_on_duplicate_id(ctx, **ctx)
|
90
|
+
clone_duplicate_activity(ctx, **ctx) # DISCUSS: mutates {ctx}.
|
91
91
|
end
|
92
92
|
|
93
|
-
def raise_on_duplicate_id(
|
94
|
-
id, sequence = ctx[:id], ctx[:sequence]
|
93
|
+
def raise_on_duplicate_id(ctx, id:, sequence:, **)
|
95
94
|
raise "ID #{id} is already taken. Please specify an `:id`." if sequence.find { |row| row[3][:id] == id }
|
96
|
-
|
97
|
-
return Right, [ctx, flow_options]
|
98
|
-
end
|
99
|
-
|
100
|
-
def clone_duplicate_activity((ctx, flow_options), *)
|
101
|
-
return Right, [ctx, flow_options] unless ctx[:task].is_a?(Class)
|
102
|
-
|
103
|
-
task, sequence = ctx[:task], ctx[:sequence]
|
104
|
-
ctx = ctx.merge(task: task.clone) if sequence.find { |row| row[1] == task }
|
105
|
-
|
106
|
-
return Right, [ctx, flow_options]
|
107
95
|
end
|
108
96
|
|
109
|
-
def
|
110
|
-
|
111
|
-
|
112
|
-
ctx = {magnetic_to: track_name}.merge(ctx)
|
97
|
+
def clone_duplicate_activity(ctx, task:, sequence:, **)
|
98
|
+
return unless task.is_a?(Class)
|
113
99
|
|
114
|
-
|
100
|
+
ctx[:task] = task.clone if sequence.find { |row| row[1] == task }
|
115
101
|
end
|
116
102
|
|
117
|
-
#
|
118
|
-
|
119
|
-
prepend_to_path(
|
120
|
-
sequence,
|
121
|
-
|
122
|
-
"path.outputs" => method(:merge_path_outputs),
|
123
|
-
"path.connections" => method(:merge_path_connections),
|
124
|
-
"path.sequence_insert" => method(:normalize_sequence_insert),
|
125
|
-
"path.normalize_duplications" => method(:normalize_duplications),
|
126
|
-
"path.magnetic_to" => method(:normalize_magnetic_to),
|
127
|
-
"path.wirings" => Linear::Normalizer.method(:compile_wirings),
|
128
|
-
)
|
103
|
+
def normalize_magnetic_to(ctx, track_name:, **) # TODO: merge with Railway.merge_magnetic_to
|
104
|
+
ctx[:magnetic_to] = ctx.key?(:magnetic_to) ? ctx[:magnetic_to] : track_name # FIXME: can we be magnetic_to {nil}?
|
129
105
|
end
|
130
106
|
|
131
107
|
# Returns an initial two-step sequence with {Start.default > End.success}.
|
@@ -161,7 +137,7 @@ module Trailblazer
|
|
161
137
|
# DISCUSS: maybe make this a function?
|
162
138
|
# These are the normalizers for an {Activity}, to be injected into a State.
|
163
139
|
Normalizers = Linear::State::Normalizer.new(
|
164
|
-
step: Linear::Normalizer.activity_normalizer(
|
140
|
+
step: Linear::Normalizer.activity_normalizer(Path::DSL.normalizer), # here, we extend the generic FastTrack::step_normalizer with the Activity-specific DSL
|
165
141
|
)
|
166
142
|
|
167
143
|
# pp Normalizers
|
@@ -198,7 +174,7 @@ module Trailblazer
|
|
198
174
|
initialize!(Path::DSL::State.new(**DSL.OptionsForState()))
|
199
175
|
end # Path
|
200
176
|
|
201
|
-
def self.Path(options)
|
177
|
+
def self.Path(**options)
|
202
178
|
Class.new(Path) do
|
203
179
|
initialize!(Path::DSL::State.new(**Path::DSL.OptionsForState(**options)))
|
204
180
|
end
|
@@ -15,95 +15,74 @@ module Trailblazer
|
|
15
15
|
# TODO: make this easier, even at this step.
|
16
16
|
|
17
17
|
def normalizer_for_fail
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
task: task,
|
35
|
-
magnetic_to: :success, id: id,
|
36
|
-
wirings: [Linear::Search.Forward(Path::DSL.unary_outputs[:success], :success)],
|
37
|
-
sequence_insert: [Linear::Insert.method(:Replace), "path.connections"])
|
18
|
+
pipeline = TaskWrap::Pipeline.prepend(
|
19
|
+
normalizer,
|
20
|
+
"path.wirings",
|
21
|
+
{
|
22
|
+
"railway.magnetic_to.fail" => Linear::Normalizer.Task(Fail.method(:merge_magnetic_to)),
|
23
|
+
}
|
24
|
+
)
|
25
|
+
|
26
|
+
pipeline = TaskWrap::Pipeline.prepend(
|
27
|
+
pipeline,
|
28
|
+
"path.connections",
|
29
|
+
{
|
30
|
+
"railway.connections.fail.success_to_failure" => Linear::Normalizer.Task(Fail.method(:connect_success_to_failure)),
|
31
|
+
},
|
32
|
+
replace: 1 # replace {"path.connections"}
|
33
|
+
)
|
38
34
|
end
|
39
35
|
|
40
36
|
def normalizer_for_pass
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
sequence = Linear::DSL.insert_task(sequence,
|
47
|
-
task: task,
|
48
|
-
magnetic_to: :success, id: id,
|
49
|
-
wirings: [Linear::Search.Forward(Path::DSL.unary_outputs[:success], :success)],
|
50
|
-
sequence_insert: [Linear::Insert.method(:Append), "path.connections"])
|
37
|
+
pipeline = TaskWrap::Pipeline.insert_after(
|
38
|
+
normalizer,
|
39
|
+
"path.connections",
|
40
|
+
["railway.connections.pass.failure_to_success", Linear::Normalizer.Task(Pass.method(:connect_failure_to_success))],
|
41
|
+
)
|
51
42
|
end
|
52
43
|
|
53
44
|
module Fail
|
54
45
|
module_function
|
55
46
|
|
56
|
-
def merge_magnetic_to(
|
57
|
-
ctx =
|
58
|
-
|
59
|
-
return Right, [ctx, flow_options]
|
47
|
+
def merge_magnetic_to(ctx, **)
|
48
|
+
ctx[:magnetic_to] = :failure
|
60
49
|
end
|
61
50
|
|
62
|
-
def connect_success_to_failure(
|
63
|
-
ctx =
|
64
|
-
|
65
|
-
return Right, [ctx, flow_options]
|
51
|
+
def connect_success_to_failure(ctx, connections: nil, **)
|
52
|
+
ctx[:connections] = connections || {success: [Linear::Search.method(:Forward), :failure]}
|
66
53
|
end
|
67
54
|
end
|
68
55
|
|
69
56
|
module Pass
|
70
57
|
module_function
|
71
58
|
|
72
|
-
def connect_failure_to_success(
|
73
|
-
connections =
|
74
|
-
|
75
|
-
return Right, [ctx.merge(connections: connections), flow_options]
|
59
|
+
def connect_failure_to_success(ctx, connections:, **)
|
60
|
+
ctx[:connections] = connections.merge({failure: [Linear::Search.method(:Forward), :success]})
|
76
61
|
end
|
77
62
|
end
|
78
63
|
|
79
64
|
# Add {Railway} steps to normalizer path.
|
80
|
-
def step_options(
|
81
|
-
|
82
|
-
|
83
|
-
|
65
|
+
def step_options(pipeline)
|
66
|
+
TaskWrap::Pipeline.prepend(
|
67
|
+
pipeline,
|
68
|
+
"path.wirings",
|
84
69
|
{
|
85
|
-
"railway.outputs" => method(:normalize_path_outputs),
|
86
|
-
"railway.connections" => method(:normalize_path_connections),
|
70
|
+
"railway.outputs" => Linear::Normalizer.Task(method(:normalize_path_outputs)),
|
71
|
+
"railway.connections" => Linear::Normalizer.Task(method(:normalize_path_connections)),
|
87
72
|
},
|
88
|
-
|
89
|
-
Linear::Insert.method(:Prepend), "path.wirings" # override where it's added.
|
90
73
|
)
|
91
74
|
end
|
92
75
|
|
93
76
|
# Add {:failure} output to {:outputs}.
|
94
77
|
# TODO: assert that failure_outputs doesn't override existing {:outputs}
|
95
|
-
def normalize_path_outputs(
|
96
|
-
outputs = failure_outputs.merge(
|
97
|
-
ctx = ctx.merge(outputs: outputs)
|
78
|
+
def normalize_path_outputs(ctx, outputs:, **)
|
79
|
+
outputs = failure_outputs.merge(outputs)
|
98
80
|
|
99
|
-
|
81
|
+
ctx[:outputs] = outputs
|
100
82
|
end
|
101
83
|
|
102
|
-
def normalize_path_connections(
|
103
|
-
connections = failure_connections.merge(
|
104
|
-
ctx = ctx.merge(connections: connections)
|
105
|
-
|
106
|
-
return Right, [ctx, flow_options]
|
84
|
+
def normalize_path_connections(ctx, connections:, **)
|
85
|
+
ctx[:connections] = failure_connections.merge(connections)
|
107
86
|
end
|
108
87
|
|
109
88
|
def failure_outputs
|
@@ -115,16 +94,16 @@ module Trailblazer
|
|
115
94
|
|
116
95
|
def initial_sequence(failure_end:, initial_sequence:, **path_options)
|
117
96
|
# TODO: this could be an Activity itself but maybe a bit too much for now.
|
118
|
-
|
97
|
+
_seq = Path::DSL.append_end(initial_sequence, task: failure_end, magnetic_to: :failure, id: "End.failure")
|
119
98
|
end
|
120
99
|
|
121
100
|
class State < Path::DSL::State
|
122
101
|
def fail(*args)
|
123
|
-
|
102
|
+
_seq = Linear::Strategy.task_for!(self, :fail, *args) # mutate @state
|
124
103
|
end
|
125
104
|
|
126
105
|
def pass(*args)
|
127
|
-
|
106
|
+
_seq = Linear::Strategy.task_for!(self, :pass, *args) # mutate @state
|
128
107
|
end
|
129
108
|
end # Instance
|
130
109
|
|
@@ -162,7 +141,6 @@ module Trailblazer
|
|
162
141
|
extend DSL::Linear::Strategy
|
163
142
|
|
164
143
|
initialize!(Railway::DSL::State.new(**DSL.OptionsForState()))
|
165
|
-
|
166
144
|
end # Railway
|
167
145
|
|
168
146
|
def self.Railway(options)
|
@@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
|
|
19
19
|
end
|
20
20
|
spec.require_paths = ["lib"]
|
21
21
|
|
22
|
-
spec.add_dependency "trailblazer-activity", ">= 0.
|
22
|
+
spec.add_dependency "trailblazer-activity", ">= 0.13.0", "< 1.0.0"
|
23
23
|
|
24
24
|
spec.add_development_dependency "bundler"
|
25
25
|
spec.add_development_dependency "minitest", "~> 5.0"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: trailblazer-activity-dsl-linear
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.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: 2021-
|
11
|
+
date: 2021-12-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: trailblazer-activity
|
@@ -16,20 +16,20 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0.
|
19
|
+
version: 0.13.0
|
20
20
|
- - "<"
|
21
21
|
- !ruby/object:Gem::Version
|
22
|
-
version: 0.
|
22
|
+
version: 1.0.0
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
25
|
version_requirements: !ruby/object:Gem::Requirement
|
26
26
|
requirements:
|
27
27
|
- - ">="
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
version: 0.
|
29
|
+
version: 0.13.0
|
30
30
|
- - "<"
|
31
31
|
- !ruby/object:Gem::Version
|
32
|
-
version: 0.
|
32
|
+
version: 1.0.0
|
33
33
|
- !ruby/object:Gem::Dependency
|
34
34
|
name: bundler
|
35
35
|
requirement: !ruby/object:Gem::Requirement
|