trailblazer-activity-dsl-linear 0.4.3 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|