trailblazer-activity-dsl-linear 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +2 -3
- data/CHANGES.md +100 -0
- data/Gemfile +7 -4
- data/Rakefile +1 -1
- data/lib/trailblazer/activity/dsl/linear/feature/merge.rb +2 -2
- data/lib/trailblazer/activity/dsl/linear/feature/patch.rb +9 -5
- data/lib/trailblazer/activity/dsl/linear/feature/variable_mapping/dsl.rb +241 -156
- data/lib/trailblazer/activity/dsl/linear/feature/variable_mapping/runtime.rb +276 -0
- data/lib/trailblazer/activity/dsl/linear/feature/variable_mapping.rb +70 -226
- data/lib/trailblazer/activity/dsl/linear/helper/path.rb +37 -18
- data/lib/trailblazer/activity/dsl/linear/helper.rb +38 -17
- data/lib/trailblazer/activity/dsl/linear/normalizer/extensions.rb +63 -0
- data/lib/trailblazer/activity/dsl/linear/normalizer/inherit.rb +90 -0
- data/lib/trailblazer/activity/dsl/linear/normalizer/output_tuples.rb +160 -0
- data/lib/trailblazer/activity/dsl/linear/normalizer/terminus.rb +26 -29
- data/lib/trailblazer/activity/dsl/linear/normalizer.rb +99 -160
- data/lib/trailblazer/activity/dsl/linear/sequence/builder.rb +3 -2
- data/lib/trailblazer/activity/dsl/linear/sequence/compiler.rb +21 -17
- data/lib/trailblazer/activity/dsl/linear/sequence/search.rb +2 -8
- data/lib/trailblazer/activity/dsl/linear/strategy.rb +56 -17
- data/lib/trailblazer/activity/dsl/linear/version.rb +1 -1
- data/lib/trailblazer/activity/dsl/linear.rb +13 -1
- data/lib/trailblazer/activity/fast_track.rb +96 -67
- data/lib/trailblazer/activity/path.rb +35 -53
- data/lib/trailblazer/activity/railway.rb +63 -65
- data/trailblazer-activity-dsl-linear.gemspec +8 -8
- metadata +27 -18
- data/lib/trailblazer/activity/dsl/linear/feature/variable_mapping/inherit.rb +0 -38
@@ -9,12 +9,15 @@ module Trailblazer
|
|
9
9
|
#
|
10
10
|
# They're usually invoked from {Strategy#invoke_normalizer_for!}, which is called from {Path#step},
|
11
11
|
# {Railway#pass}, etc.
|
12
|
+
#
|
13
|
+
# Most parts of Normalizer are documented: https://trailblazer.to/2.1/docs/internals.html#internals-dsl-normalizer
|
12
14
|
module Normalizer
|
13
15
|
# Container for all final normalizers of a specific Strategy.
|
14
16
|
class Normalizers
|
15
17
|
def initialize(**options)
|
16
18
|
@normalizers = options
|
17
19
|
end
|
20
|
+
|
18
21
|
# Execute the specific normalizer (step, fail, pass) for a particular option set provided
|
19
22
|
# by the DSL user. Usually invoked when you call {#step}.
|
20
23
|
def call(name, ctx)
|
@@ -57,21 +60,11 @@ module Trailblazer
|
|
57
60
|
[name, new_normalizer]
|
58
61
|
end.to_h
|
59
62
|
|
60
|
-
|
61
63
|
Normalizers.new(**hsh.merge(new_normalizers))
|
62
64
|
end
|
63
65
|
end
|
64
66
|
|
65
|
-
#
|
66
|
-
class Task < TaskBuilder::Task
|
67
|
-
def call(wrap_ctx, flow_options={})
|
68
|
-
_result = call_option(@task, [wrap_ctx, flow_options]) # DISCUSS: this mutates {ctx}.
|
69
|
-
|
70
|
-
return wrap_ctx, flow_options
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
# Wrap user's normalizer task with {Trailblazer::Option} and thus execute it with
|
67
|
+
# Wrap user's normalizer task in a {Pipeline::TaskAdapter} so it executes with
|
75
68
|
# convenient kw args.
|
76
69
|
#
|
77
70
|
# Example
|
@@ -79,87 +72,130 @@ module Trailblazer
|
|
79
72
|
#
|
80
73
|
# # will call {normalizer_task} and pass ctx variables as kwargs, as follows
|
81
74
|
# def normalize_id(ctx, id: false, task:, **)
|
82
|
-
def Task(
|
83
|
-
|
75
|
+
def Task(user_step)
|
76
|
+
Activity::TaskWrap::Pipeline::TaskAdapter.for_step(user_step, option: false) # we don't need Option as we don't have ciruit_options here, and no {:exec_context}
|
84
77
|
end
|
85
78
|
|
86
79
|
# The generic normalizer not tied to `step` or friends.
|
87
|
-
def Normalizer
|
88
|
-
pipeline
|
80
|
+
def Normalizer(prepend_to_default_outputs: [])
|
81
|
+
# Adding steps to the output pipeline means they are only called when there
|
82
|
+
# are no :outputs set already.
|
83
|
+
outputs_pipeline = TaskWrap::Pipeline.new([])
|
84
|
+
prepend_to_default_outputs.each do |hsh|
|
85
|
+
outputs_pipeline = Linear::Normalizer.prepend_to(outputs_pipeline, nil, hsh) # DISCUSS: does it matter if we prepend FastTrack to Railway, etc?
|
86
|
+
end
|
87
|
+
|
88
|
+
# Call the prepend_to_outputs pipeline only if {:outputs} is not set (by Subprocess).
|
89
|
+
# too bad we don't have nesting here, yet.
|
90
|
+
defaults_for_outputs = ->(ctx, args) do
|
91
|
+
return [ctx, args] if ctx.key?(:outputs)
|
92
|
+
|
93
|
+
outputs_pipeline.(ctx, args)
|
94
|
+
end
|
95
|
+
|
96
|
+
TaskWrap::Pipeline.new(
|
89
97
|
{
|
90
|
-
"activity.normalize_step_interface" => Normalizer.Task(method(:normalize_step_interface)),
|
98
|
+
"activity.normalize_step_interface" => Normalizer.Task(method(:normalize_step_interface)), # Makes sure {:options} is always a hash.
|
99
|
+
"activity.macro_options_with_symbol_task" => Normalizer.Task(method(:macro_options_with_symbol_task)), # DISCUSS: we might deprecate {task: :instance_method}
|
100
|
+
|
91
101
|
"activity.merge_library_options" => Normalizer.Task(method(:merge_library_options)), # Merge "macro"/user options over library options.
|
92
102
|
"activity.normalize_for_macro" => Normalizer.Task(method(:merge_user_options)), # Merge user_options over "macro" options.
|
93
103
|
"activity.normalize_normalizer_options" => Normalizer.Task(method(:merge_normalizer_options)), # Merge user_options over normalizer_options.
|
94
104
|
"activity.normalize_non_symbol_options" => Normalizer.Task(method(:normalize_non_symbol_options)),
|
95
105
|
"activity.path_helper.forward_block" => Normalizer.Task(Helper::Path::Normalizer.method(:forward_block_for_path_branch)), # forward the "global" block
|
96
106
|
"activity.normalize_context" => method(:normalize_context),
|
107
|
+
"activity.id_with_inherit_and_replace" => Normalizer.Task(method(:id_with_inherit_and_replace)),
|
97
108
|
"activity.normalize_id" => Normalizer.Task(method(:normalize_id)),
|
98
|
-
"activity.normalize_override" => Normalizer.Task(method(:normalize_override)),
|
109
|
+
"activity.normalize_override" => Normalizer.Task(method(:normalize_override)), # TODO: remove!
|
99
110
|
"activity.wrap_task_with_step_interface" => Normalizer.Task(method(:wrap_task_with_step_interface)),
|
100
111
|
|
101
|
-
|
112
|
+
# Nested pipeline:
|
113
|
+
"activity.default_outputs" => defaults_for_outputs, # only {if :outputs.nil?}
|
114
|
+
|
115
|
+
"extensions.convert_extensions_option_to_tuples" => Normalizer.Task(Extensions.method(:convert_extensions_option_to_tuples)),
|
116
|
+
|
117
|
+
"inherit.recall_recorded_options" => Normalizer.Task(Inherit.method(:recall_recorded_options)),
|
102
118
|
"activity.sequence_insert" => Normalizer.Task(method(:normalize_sequence_insert)),
|
103
119
|
"activity.normalize_duplications" => Normalizer.Task(method(:normalize_duplications)),
|
104
120
|
|
105
|
-
"activity.path_helper.path_to_track"
|
106
|
-
|
107
|
-
|
121
|
+
"activity.path_helper.path_to_track" => Normalizer.Task(Helper::Path::Normalizer.method(:convert_paths_to_tracks)),
|
122
|
+
|
123
|
+
|
124
|
+
"output_tuples.normalize_output_tuples" => Normalizer.Task(OutputTuples.method(:normalize_output_tuples)), # Output(Signal, :semantic) => Id()
|
125
|
+
"output_tuples.remember_custom_output_tuples" => Normalizer.Task(OutputTuples.method(:remember_custom_output_tuples)), # Output(Signal, :semantic) => Id()
|
126
|
+
"output_tuples.register_additional_outputs" => Normalizer.Task(OutputTuples.method(:register_additional_outputs)), # Output(Signal, :semantic) => Id()
|
127
|
+
"output_tuples.filter_inherited_output_tuples" => Normalizer.Task(OutputTuples.method(:filter_inherited_output_tuples)),
|
128
|
+
|
129
|
+
"activity.wirings" => Normalizer.Task(OutputTuples::Connections.method(:compile_wirings)),
|
108
130
|
|
109
|
-
|
131
|
+
|
132
|
+
"extensions.compile_extensions" => Normalizer.Task(Extensions.method(:compile_extensions)),
|
133
|
+
"extensions.compile_recorded_extensions" => Normalizer.Task(Extensions.method(:compile_recorded_extensions)),
|
134
|
+
|
135
|
+
# DISCUSS: make this configurable? maybe lots of folks don't want {:inherit}?
|
136
|
+
"inherit.compile_recorded_options" => Normalizer.Task(Inherit.method(:compile_recorded_options)),
|
110
137
|
|
111
138
|
# TODO: make this a "Subprocess":
|
112
139
|
"activity.compile_data" => Normalizer.Task(method(:compile_data)),
|
113
140
|
"activity.create_row" => Normalizer.Task(method(:create_row)),
|
114
141
|
"activity.create_add" => Normalizer.Task(method(:create_add)),
|
115
142
|
"activity.create_adds" => Normalizer.Task(method(:create_adds)),
|
116
|
-
}
|
117
|
-
collect { |id, task| TaskWrap::Pipeline.Row(id, task) }
|
143
|
+
}
|
144
|
+
.collect { |id, task| TaskWrap::Pipeline.Row(id, task) }
|
118
145
|
)
|
146
|
+
end
|
147
|
+
|
148
|
+
# DISCUSS: should we remove this special case?
|
149
|
+
# This handles
|
150
|
+
# step task: :instance_method_exposing_circuit_interface
|
151
|
+
def macro_options_with_symbol_task(ctx, options:, **)
|
152
|
+
return if options[:wrap_task]
|
153
|
+
return unless options[:task].is_a?(Symbol)
|
119
154
|
|
120
|
-
|
155
|
+
ctx[:options] = {
|
156
|
+
**options,
|
157
|
+
wrap_task: true,
|
158
|
+
step_interface_builder: ->(task) { Trailblazer::Option(task) } # only wrap in Option, not {TaskAdapter}.
|
159
|
+
}
|
121
160
|
end
|
122
161
|
|
162
|
+
# @param {:options} The first argument passed to {#step}
|
163
|
+
# After this step, options is always a hash.
|
164
|
+
#
|
123
165
|
# Specific to the "step DSL": if the first argument is a callable, wrap it in a {step_interface_builder}
|
124
166
|
# since its interface expects the step interface, but the circuit will call it with circuit interface.
|
125
167
|
def normalize_step_interface(ctx, options:, **)
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
else
|
136
|
-
# step task: Callable, ... (Subprocess, Proc, macros etc)
|
137
|
-
options # NOOP
|
138
|
-
end
|
139
|
-
else
|
140
|
-
# Step Interface
|
141
|
-
# step :find, ...
|
142
|
-
# step Callable, ... (Method, Proc etc)
|
143
|
-
{ task: options, wrap_task: true }
|
144
|
-
end
|
145
|
-
|
146
|
-
ctx[:options] = options
|
168
|
+
return if options.is_a?(Hash)
|
169
|
+
|
170
|
+
# Step Interface
|
171
|
+
# step :find, ...
|
172
|
+
# step Callable, ... (Method, Proc etc)
|
173
|
+
ctx[:options] = {
|
174
|
+
task: options,
|
175
|
+
wrap_task: true # task exposes step interface.
|
176
|
+
}
|
147
177
|
end
|
148
178
|
|
149
|
-
|
179
|
+
# @param :wrap_task If true, the {:task} is wrapped using the step_interface_builder, meaning the
|
180
|
+
# task is expecting the step interface.
|
181
|
+
def wrap_task_with_step_interface(ctx, step_interface_builder:, task:, wrap_task: false, **)
|
150
182
|
return unless wrap_task
|
151
183
|
|
152
184
|
ctx[:task] = step_interface_builder.(task)
|
153
185
|
end
|
154
186
|
|
155
|
-
def normalize_id(ctx, id: false,
|
187
|
+
def normalize_id(ctx, task:, id: false, **)
|
156
188
|
ctx[:id] = id || task
|
157
189
|
end
|
158
190
|
|
191
|
+
# TODO: remove {#normalize_override} in 1.2.0 (Used in macro-contract tests).
|
159
192
|
# {:override} really only makes sense for {step Macro(), {override: true}} where the {user_options}
|
160
193
|
# dictate the overriding.
|
161
194
|
def normalize_override(ctx, id:, override: false, **)
|
162
195
|
return unless override
|
196
|
+
|
197
|
+
Activity::Deprecate.warn Linear::Deprecate.dsl_caller_location, "The :override option is deprecated and will be removed. Please use :replace instead."
|
198
|
+
|
163
199
|
ctx[:replace] = (id || raise)
|
164
200
|
end
|
165
201
|
|
@@ -185,20 +221,6 @@ module Trailblazer
|
|
185
221
|
return ctx, flow_options
|
186
222
|
end
|
187
223
|
|
188
|
-
# Compile the actual {Seq::Row}'s {wiring}.
|
189
|
-
# This combines {:connections} and {:outputs}
|
190
|
-
def compile_wirings(ctx, connections:, outputs:, id:, **)
|
191
|
-
ctx[:wirings] =
|
192
|
-
connections.collect do |semantic, (search_strategy_builder, *search_args)|
|
193
|
-
output = outputs[semantic] || raise("No `#{semantic}` output found for #{id.inspect} and outputs #{outputs.inspect}")
|
194
|
-
|
195
|
-
search_strategy_builder.( # return proc to be called when compiling Seq, e.g. {ById(output, :id)}
|
196
|
-
output,
|
197
|
-
*search_args
|
198
|
-
)
|
199
|
-
end
|
200
|
-
end
|
201
|
-
|
202
224
|
# Processes {:before,:after,:replace,:delete} options and
|
203
225
|
# defaults to {before: "End.success"} which, yeah.
|
204
226
|
def normalize_sequence_insert(ctx, end_id:, **)
|
@@ -214,10 +236,10 @@ module Trailblazer
|
|
214
236
|
# @private
|
215
237
|
def sequence_insert_options
|
216
238
|
{
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
239
|
+
before: :Prepend,
|
240
|
+
after: :Append,
|
241
|
+
replace: :Replace,
|
242
|
+
delete: :Delete
|
221
243
|
}
|
222
244
|
end
|
223
245
|
|
@@ -240,7 +262,6 @@ module Trailblazer
|
|
240
262
|
ctx[:task] = task.clone if sequence.find { |row| row[1] == task }
|
241
263
|
end
|
242
264
|
|
243
|
-
|
244
265
|
# Move DSL user options such as {Output(:success) => Track(:found)} to
|
245
266
|
# a new key {options[:non_symbol_options]}.
|
246
267
|
# This allows using {options} as a {**ctx}-able hash in Ruby 2.6 and 3.0.
|
@@ -252,97 +273,14 @@ module Trailblazer
|
|
252
273
|
ctx[:options] = symbol_options.merge(non_symbol_options: non_symbol_options)
|
253
274
|
end
|
254
275
|
|
255
|
-
#
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
return unless
|
260
|
-
|
261
|
-
# DISCUSS: how could we add another magnetic_to to an end?
|
262
|
-
output_configs.each do |output, cfg|
|
263
|
-
new_connections, add =
|
264
|
-
if cfg.is_a?(Linear::Track)
|
265
|
-
[output_to_track(ctx, output, cfg), cfg.adds] # FIXME: why does Track have a {adds} field? we don't use it anywhere.
|
266
|
-
elsif cfg.is_a?(Linear::Id)
|
267
|
-
[output_to_id(ctx, output, cfg.value), []]
|
268
|
-
elsif cfg.is_a?(Activity::End)
|
269
|
-
end_id = Activity::Railway.end_id(**cfg.to_h)
|
270
|
-
end_exists = Activity::Adds::Insert.find_index(ctx[:sequence], end_id)
|
271
|
-
|
272
|
-
_adds = end_exists ? [] : add_terminus(cfg, id: end_id, sequence: sequence, normalizers: normalizers)
|
273
|
-
|
274
|
-
[output_to_id(ctx, output, end_id), _adds]
|
275
|
-
else
|
276
|
-
raise cfg.inspect
|
277
|
-
end
|
278
|
-
|
279
|
-
connections = connections.merge(new_connections)
|
280
|
-
adds += add
|
281
|
-
end
|
282
|
-
|
283
|
-
ctx[:connections] = connections
|
284
|
-
ctx[:adds] = adds
|
285
|
-
end
|
286
|
-
|
287
|
-
def output_to_track(ctx, output, track)
|
288
|
-
search_strategy = track.options[:wrap_around] ? :WrapAround : :Forward
|
289
|
-
|
290
|
-
{output.value => [Linear::Sequence::Search.method(search_strategy), track.color]}
|
291
|
-
end
|
276
|
+
# Whenever {:replace} and {:inherit} are passed, automatically assign {:id}.
|
277
|
+
# DISCUSS: this step could be nested in {inherit_option}.
|
278
|
+
def id_with_inherit_and_replace(ctx, id: nil, replace: nil, inherit: nil, **)
|
279
|
+
return if id
|
280
|
+
return unless inherit # inherit: true and inherit: [] both work.
|
281
|
+
return unless replace
|
292
282
|
|
293
|
-
|
294
|
-
{output.value => [Linear::Sequence::Search.method(:ById), target]}
|
295
|
-
end
|
296
|
-
|
297
|
-
# Returns ADDS for the new terminus.
|
298
|
-
def add_terminus(end_event, id:, sequence:, normalizers:)
|
299
|
-
step_options = Linear::Sequence::Builder.invoke_normalizer_for(:terminus, end_event, {id: id}, sequence: sequence, normalizer_options: {}, normalizers: normalizers)
|
300
|
-
|
301
|
-
step_options[:adds]
|
302
|
-
end
|
303
|
-
|
304
|
-
# Output(Signal, :semantic) => Id()
|
305
|
-
def normalize_outputs_from_dsl(ctx, non_symbol_options:, outputs:, **)
|
306
|
-
output_configs = non_symbol_options.find_all{ |k,v| k.kind_of?(Activity::Output) }
|
307
|
-
return unless output_configs.any?
|
308
|
-
|
309
|
-
dsl_options = {}
|
310
|
-
|
311
|
-
output_configs.collect do |output, cfg| # {cfg} = Track(:success)
|
312
|
-
outputs = outputs.merge(output.semantic => output)
|
313
|
-
dsl_options = dsl_options.merge(Linear::Strategy.Output(output.semantic) => cfg)
|
314
|
-
end
|
315
|
-
|
316
|
-
ctx[:outputs] = outputs
|
317
|
-
ctx[:non_symbol_options] = non_symbol_options.merge(dsl_options)
|
318
|
-
end
|
319
|
-
|
320
|
-
# Currently, the {:inherit} option copies over {:connections} from the original step
|
321
|
-
# and merges them with the (prolly) connections passed from the user.
|
322
|
-
def inherit_option(ctx, inherit: false, sequence:, id:, extensions: [], **)
|
323
|
-
return unless inherit === true
|
324
|
-
|
325
|
-
row = InheritOption.find_row(sequence, id) # from this row we're inheriting options.
|
326
|
-
|
327
|
-
inherited_connections = row.data[:connections]
|
328
|
-
inherited_extensions = row.data[:extensions]
|
329
|
-
|
330
|
-
ctx[:connections] = get_inheritable_connections(ctx, inherited_connections)
|
331
|
-
ctx[:extensions] = Array(inherited_extensions) + Array(extensions)
|
332
|
-
end
|
333
|
-
|
334
|
-
module InheritOption # TODO: move all inherit methods in here!
|
335
|
-
def self.find_row(sequence, id)
|
336
|
-
index = Activity::Adds::Insert.find_index(sequence, id)
|
337
|
-
sequence[index]
|
338
|
-
end
|
339
|
-
end
|
340
|
-
|
341
|
-
# return connections from {parent} step which are supported by current step
|
342
|
-
private def get_inheritable_connections(ctx, parent_connections)
|
343
|
-
return parent_connections unless ctx[:outputs]
|
344
|
-
|
345
|
-
parent_connections.slice(*ctx[:outputs].keys)
|
283
|
+
ctx[:id] = replace
|
346
284
|
end
|
347
285
|
|
348
286
|
def create_row(ctx, task:, wirings:, magnetic_to:, data:, **)
|
@@ -359,13 +297,14 @@ module Trailblazer
|
|
359
297
|
|
360
298
|
# TODO: document DataVariable() => :name
|
361
299
|
# Compile data that goes into the sequence row.
|
362
|
-
def compile_data(ctx, default_variables_for_data: [:id, :dsl_track, :
|
363
|
-
variables_for_data = non_symbol_options
|
300
|
+
def compile_data(ctx, non_symbol_options:, default_variables_for_data: [:id, :dsl_track, :extensions], **)
|
301
|
+
variables_for_data = non_symbol_options
|
302
|
+
.find_all { |k, v| k.instance_of?(Linear::DataVariableName) }
|
303
|
+
.flat_map { |k, v| Array(v) }
|
364
304
|
|
365
305
|
ctx[:data] = (default_variables_for_data + variables_for_data).collect { |key| [key, ctx[key]] }.to_h
|
366
306
|
end
|
367
307
|
end
|
368
|
-
|
369
308
|
end # Normalizer
|
370
309
|
end
|
371
310
|
end
|
@@ -13,7 +13,7 @@ module Trailblazer
|
|
13
13
|
# @private
|
14
14
|
# Run a specific normalizer (e.g. for `#step`), apply the adds to the sequence and return the latter.
|
15
15
|
# DISCUSS: where does this method belong? Sequence + Normalizers?
|
16
|
-
def self.update_sequence_for(type, task, options={}, sequence:, **kws, &block)
|
16
|
+
def self.update_sequence_for(type, task, options = {}, sequence:, **kws, &block)
|
17
17
|
step_options = invoke_normalizer_for(type, task, options, sequence: sequence, **kws, &block)
|
18
18
|
|
19
19
|
_sequence = Activity::Adds.apply_adds(sequence, step_options[:adds])
|
@@ -32,7 +32,8 @@ module Trailblazer
|
|
32
32
|
sequence: sequence,
|
33
33
|
}
|
34
34
|
|
35
|
-
_step_options = normalizers.(
|
35
|
+
_step_options = normalizers.(
|
36
|
+
type,
|
36
37
|
normalizer_options: normalizer_options, # class-level Strategy configuration, such as :step_interface_builder
|
37
38
|
options: task, # macro-options
|
38
39
|
user_options: options, # user-specified options from the DSL method
|
@@ -8,16 +8,19 @@ module Trailblazer
|
|
8
8
|
module_function
|
9
9
|
|
10
10
|
# Default strategy to find out what's a stop event is to inspect the TaskRef's {data[:stop_event]}.
|
11
|
-
def
|
12
|
-
intermediate_wiring
|
11
|
+
def find_termini(intermediate_wiring)
|
12
|
+
intermediate_wiring
|
13
|
+
.find_all { |task_ref, _| task_ref.data[:stop_event] }
|
14
|
+
.collect { |task_ref, _| [task_ref.id, task_ref.data.fetch(:semantic)] }
|
15
|
+
.to_h
|
13
16
|
end
|
14
17
|
|
15
18
|
# The first task in the wiring is the default start task.
|
16
|
-
def
|
17
|
-
|
19
|
+
def find_start_task_id(intermediate_wiring) # FIXME: shouldn't we use sequence here? and Row#id?
|
20
|
+
intermediate_wiring.first.first.id
|
18
21
|
end
|
19
22
|
|
20
|
-
def call(sequence, find_stops: method(:
|
23
|
+
def call(sequence, find_stops: method(:find_termini), find_start: method(:find_start_task_id))
|
21
24
|
_implementations, intermediate_wiring =
|
22
25
|
sequence.inject([[], []]) do |(implementations, intermediates), seq_row|
|
23
26
|
_magnetic_to, task, connections, data = seq_row
|
@@ -27,7 +30,7 @@ module Trailblazer
|
|
27
30
|
connections = find_connections(seq_row, connections, sequence)
|
28
31
|
|
29
32
|
# FIXME: {:extensions} should be initialized
|
30
|
-
implementations += [[id, Schema::Implementation::Task(task, connections.collect { |output, _| output }, data[:extensions] || [])
|
33
|
+
implementations += [[id, Schema::Implementation::Task(task, connections.collect { |output, _| output }, data[:extensions] || [])]]
|
31
34
|
|
32
35
|
intermediates += [
|
33
36
|
[
|
@@ -40,29 +43,30 @@ module Trailblazer
|
|
40
43
|
[implementations, intermediates]
|
41
44
|
end
|
42
45
|
|
43
|
-
|
44
|
-
|
46
|
+
start_task_id = find_start.(intermediate_wiring)
|
47
|
+
terminus_to_semantic = find_stops.(intermediate_wiring)
|
45
48
|
|
46
|
-
intermediate = Schema::Intermediate.new(
|
47
|
-
implementation =
|
49
|
+
intermediate = Schema::Intermediate.new(intermediate_wiring.to_h, terminus_to_semantic, start_task_id)
|
50
|
+
implementation = _implementations.to_h
|
48
51
|
|
49
|
-
Schema::Intermediate.(intermediate, implementation) # implemented in the generic {trailblazer-activity} gem.
|
52
|
+
Schema::Intermediate::Compiler.(intermediate, implementation) # implemented in the generic {trailblazer-activity} gem.
|
50
53
|
end
|
51
54
|
|
52
55
|
# private
|
53
56
|
|
54
|
-
|
55
|
-
|
57
|
+
# Execute all search strategies for a row, retrieve outputs and
|
58
|
+
# their respective target IDs.
|
59
|
+
def find_connections(seq_row, searches, sequence)
|
60
|
+
searches.collect do |search|
|
56
61
|
output, target_seq_row = search.(sequence, seq_row) # invoke the node's "connection search" strategy.
|
57
62
|
|
58
63
|
target_seq_row = sequence[-1] if target_seq_row.nil? # connect to an End if target unknown. # DISCUSS: make this configurable, maybe?
|
59
64
|
|
60
65
|
[
|
61
|
-
output,
|
62
|
-
target_seq_row
|
63
|
-
target_seq_row # DISCUSS: needed?
|
66
|
+
output,
|
67
|
+
target_seq_row.id
|
64
68
|
]
|
65
|
-
end
|
69
|
+
end
|
66
70
|
end
|
67
71
|
end # Compiler
|
68
72
|
end # Sequence
|
@@ -11,7 +11,7 @@ class Trailblazer::Activity
|
|
11
11
|
# Note that we only go forward, no back-references are done here.
|
12
12
|
def Forward(output, target_color)
|
13
13
|
->(sequence, me) do
|
14
|
-
target_seq_row = find_in_range(sequence[sequence.index(me)+1..-1], target_color)
|
14
|
+
target_seq_row = find_in_range(sequence[sequence.index(me) + 1..-1], target_color)
|
15
15
|
|
16
16
|
return output, target_seq_row
|
17
17
|
end
|
@@ -23,7 +23,7 @@ class Trailblazer::Activity
|
|
23
23
|
->(sequence, me) do
|
24
24
|
my_index = sequence.index(me)
|
25
25
|
# First, try all elements after me, then go through the elements preceding myself.
|
26
|
-
wrapped_range = sequence[my_index+1..-1] + sequence[0..my_index-1]
|
26
|
+
wrapped_range = sequence[my_index + 1..-1] + sequence[0..my_index - 1]
|
27
27
|
|
28
28
|
target_seq_row = find_in_range(wrapped_range, target_color)
|
29
29
|
|
@@ -31,12 +31,6 @@ class Trailblazer::Activity
|
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
|
-
def Noop(output)
|
35
|
-
->(sequence, me) do
|
36
|
-
return output, [nil,nil,nil,{}] # FIXME
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
34
|
# Find the seq_row with {id} and connect the current node to it.
|
41
35
|
def ById(output, id)
|
42
36
|
->(sequence, me) do
|
@@ -28,10 +28,15 @@ module Trailblazer
|
|
28
28
|
end
|
29
29
|
|
30
30
|
# @public
|
31
|
-
|
32
|
-
|
33
|
-
def step(*args, &block)
|
34
|
-
|
31
|
+
# We forward `step` to the Dsl (State) object.
|
32
|
+
# Recompiling the activity/sequence is a matter specific to Strategy (Railway etc).
|
33
|
+
def step(*args, &block)
|
34
|
+
recompile_activity_for(:step, *args, &block)
|
35
|
+
end
|
36
|
+
|
37
|
+
def terminus(*args)
|
38
|
+
recompile_activity_for(:terminus, *args)
|
39
|
+
end
|
35
40
|
|
36
41
|
private def recompile_activity_for(type, *args, &block)
|
37
42
|
sequence = apply_step_on_sequence_builder(type, *args, &block)
|
@@ -41,16 +46,14 @@ module Trailblazer
|
|
41
46
|
|
42
47
|
# TODO: make {rescue} optional, only in dev mode.
|
43
48
|
# @return Sequence
|
44
|
-
private def apply_step_on_sequence_builder(type, arg, options={}, &block)
|
45
|
-
|
49
|
+
private def apply_step_on_sequence_builder(type, arg, options = {}, &block)
|
50
|
+
Sequence::Builder.(type, arg, options,
|
46
51
|
sequence: @state.get(:sequence),
|
47
52
|
normalizers: @state.get(:normalizers),
|
48
53
|
|
49
54
|
normalizer_options: @state.get(:normalizer_options),
|
50
55
|
|
51
|
-
|
52
|
-
)
|
53
|
-
|
56
|
+
&block)
|
54
57
|
rescue Activity::Adds::IndexError
|
55
58
|
# re-raise this exception with activity class prepended
|
56
59
|
# to the message this time.
|
@@ -71,8 +74,8 @@ module Trailblazer
|
|
71
74
|
end
|
72
75
|
|
73
76
|
# Used only once per strategy class body.
|
74
|
-
def compile_strategy!(
|
75
|
-
options =
|
77
|
+
def compile_strategy!(strategy_dsl, **options)
|
78
|
+
options = DSL.OptionsForSequenceBuilder(strategy_dsl, **options)
|
76
79
|
|
77
80
|
compile_strategy_for!(**options)
|
78
81
|
end
|
@@ -90,7 +93,7 @@ module Trailblazer
|
|
90
93
|
|
91
94
|
activity.to_h.to_h.merge(
|
92
95
|
activity: activity,
|
93
|
-
sequence: @state.get(:sequence)
|
96
|
+
sequence: @state.get(:sequence)
|
94
97
|
)
|
95
98
|
end
|
96
99
|
|
@@ -105,8 +108,8 @@ module Trailblazer
|
|
105
108
|
)
|
106
109
|
end
|
107
110
|
|
108
|
-
def invoke(*args)
|
109
|
-
TaskWrap.invoke(self, *args)
|
111
|
+
def invoke(*args, **kws)
|
112
|
+
TaskWrap.invoke(self, *args, **kws)
|
110
113
|
end
|
111
114
|
end # class << self
|
112
115
|
# FIXME: do we want class << self?!
|
@@ -124,9 +127,47 @@ module Trailblazer
|
|
124
127
|
Class.new(strategy) do
|
125
128
|
compile_strategy!(strategy::DSL, normalizers: @state.get(:normalizers), **options)
|
126
129
|
|
127
|
-
class_exec(&block) if
|
130
|
+
class_exec(&block) if block
|
128
131
|
end
|
129
132
|
end
|
133
|
+
|
134
|
+
def OptionsForSequenceBuilder(strategy_dsl, termini: nil, **user_options)
|
135
|
+
# DISCUSS: instead of calling a separate {initial_sequence} method we could make DSL strategies
|
136
|
+
# use the actual DSL to build up the initial_sequence, somewhere outside? Maybe using {:adds}?
|
137
|
+
strategy_options, strategy_termini = strategy_dsl.options_for_sequence_build(**user_options) # call Path.options_for_sequence_builder
|
138
|
+
|
139
|
+
# DISCUSS: passing on Normalizers here is a service, not sure I like it.
|
140
|
+
initial_sequence = process_termini(strategy_options[:sequence], termini || strategy_termini, normalizers: strategy_dsl::Normalizers)
|
141
|
+
|
142
|
+
{
|
143
|
+
step_interface_builder: method(:build_circuit_task_for_step),
|
144
|
+
adds: [], # DISCUSS: needed?
|
145
|
+
**user_options,
|
146
|
+
**strategy_options, # this might (and should!) override :track_name etc.
|
147
|
+
sequence: initial_sequence,
|
148
|
+
}
|
149
|
+
# no {:termini} left in options
|
150
|
+
end
|
151
|
+
|
152
|
+
# If no {:termini} were provided by the Strategy user, we use the default
|
153
|
+
# {strategy_termini}.
|
154
|
+
def process_termini(sequence, termini, **options_for_append_terminus)
|
155
|
+
termini.each do |task, terminus_options|
|
156
|
+
sequence = append_terminus(sequence, task, **options_for_append_terminus, **terminus_options)
|
157
|
+
end
|
158
|
+
|
159
|
+
sequence
|
160
|
+
end
|
161
|
+
|
162
|
+
def append_terminus(sequence, task, normalizers:, **options)
|
163
|
+
# DISCUSS: why are we requiring {:normalizers} here? only for invoking Normalizer.terminus
|
164
|
+
_sequence = Linear::Sequence::Builder.update_sequence_for(:terminus, task, options, normalizers: normalizers, sequence: sequence, normalizer_options: {})
|
165
|
+
end
|
166
|
+
|
167
|
+
# Wraps {user_step} into a circuit-interface compatible callable, a.k.a. "task".
|
168
|
+
def build_circuit_task_for_step(user_step)
|
169
|
+
Activity::Circuit::TaskAdapter.for_step(user_step, option: true)
|
170
|
+
end
|
130
171
|
end # DSL
|
131
172
|
|
132
173
|
# FIXME: move to State#dup
|
@@ -156,5 +197,3 @@ module Trailblazer
|
|
156
197
|
end
|
157
198
|
end
|
158
199
|
end
|
159
|
-
|
160
|
-
|