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.
Files changed (29) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +2 -3
  3. data/CHANGES.md +100 -0
  4. data/Gemfile +7 -4
  5. data/Rakefile +1 -1
  6. data/lib/trailblazer/activity/dsl/linear/feature/merge.rb +2 -2
  7. data/lib/trailblazer/activity/dsl/linear/feature/patch.rb +9 -5
  8. data/lib/trailblazer/activity/dsl/linear/feature/variable_mapping/dsl.rb +241 -156
  9. data/lib/trailblazer/activity/dsl/linear/feature/variable_mapping/runtime.rb +276 -0
  10. data/lib/trailblazer/activity/dsl/linear/feature/variable_mapping.rb +70 -226
  11. data/lib/trailblazer/activity/dsl/linear/helper/path.rb +37 -18
  12. data/lib/trailblazer/activity/dsl/linear/helper.rb +38 -17
  13. data/lib/trailblazer/activity/dsl/linear/normalizer/extensions.rb +63 -0
  14. data/lib/trailblazer/activity/dsl/linear/normalizer/inherit.rb +90 -0
  15. data/lib/trailblazer/activity/dsl/linear/normalizer/output_tuples.rb +160 -0
  16. data/lib/trailblazer/activity/dsl/linear/normalizer/terminus.rb +26 -29
  17. data/lib/trailblazer/activity/dsl/linear/normalizer.rb +99 -160
  18. data/lib/trailblazer/activity/dsl/linear/sequence/builder.rb +3 -2
  19. data/lib/trailblazer/activity/dsl/linear/sequence/compiler.rb +21 -17
  20. data/lib/trailblazer/activity/dsl/linear/sequence/search.rb +2 -8
  21. data/lib/trailblazer/activity/dsl/linear/strategy.rb +56 -17
  22. data/lib/trailblazer/activity/dsl/linear/version.rb +1 -1
  23. data/lib/trailblazer/activity/dsl/linear.rb +13 -1
  24. data/lib/trailblazer/activity/fast_track.rb +96 -67
  25. data/lib/trailblazer/activity/path.rb +35 -53
  26. data/lib/trailblazer/activity/railway.rb +63 -65
  27. data/trailblazer-activity-dsl-linear.gemspec +8 -8
  28. metadata +27 -18
  29. 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
- # @private
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(user_proc)
83
- Normalizer::Task.new(Trailblazer::Option(user_proc), user_proc)
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 = TaskWrap::Pipeline.new(
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)), # Makes sure {:options} is always a hash.
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
- "activity.inherit_option" => Normalizer.Task(method(:inherit_option)),
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" => Normalizer.Task(Helper::Path::Normalizer.method(:convert_paths_to_tracks)),
106
- "activity.normalize_outputs_from_dsl" => Normalizer.Task(method(:normalize_outputs_from_dsl)), # Output(Signal, :semantic) => Id()
107
- "activity.normalize_connections_from_dsl" => Normalizer.Task(method(:normalize_connections_from_dsl)),
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
- "activity.wirings" => Normalizer.Task(method(:compile_wirings)),
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
- pipeline
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
- options = case options
127
- when Hash
128
- # Circuit Interface
129
- task = options.fetch(:task)
130
- id = options[:id]
131
-
132
- if task.is_a?(Symbol)
133
- # step task: :find, id: :load
134
- { **options, id: (id || task), task: Trailblazer::Option(task) }
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
- def wrap_task_with_step_interface(ctx, wrap_task: false, step_interface_builder:, task:, **)
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, task:, **)
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
- :before => :Prepend,
218
- :after => :Append,
219
- :replace => :Replace,
220
- :delete => :Delete,
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
- # Process {Output(:semantic) => target} and make them {:connections}.
256
- def normalize_connections_from_dsl(ctx, connections:, adds:, non_symbol_options:, sequence:, normalizers:, **)
257
- # Find all {Output() => Track()/Id()/End()}
258
- output_configs = non_symbol_options.find_all{ |k,v| k.kind_of?(Linear::OutputSemantic) }
259
- return unless output_configs.any?
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
- def output_to_id(ctx, output, target)
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, :connections, :extensions, :stop_event], non_symbol_options:, **)
363
- variables_for_data = non_symbol_options.find_all { |k,v| k.instance_of?(Linear::DataVariableName) }.collect { |k,v| Array(v) }.flatten
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.(type,
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 find_stop_task_ids(intermediate_wiring)
12
- intermediate_wiring.collect { |task_ref, outs| task_ref.data[:stop_event] ? task_ref.id : nil }.compact
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 find_start_task_ids(intermediate_wiring)
17
- [intermediate_wiring.first.first.id]
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(:find_stop_task_ids), find_start: method(:find_start_task_ids))
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
- start_task_ids = find_start.(intermediate_wiring)
44
- stop_task_refs = find_stops.(intermediate_wiring)
46
+ start_task_id = find_start.(intermediate_wiring)
47
+ terminus_to_semantic = find_stops.(intermediate_wiring)
45
48
 
46
- intermediate = Schema::Intermediate.new(Hash[intermediate_wiring], stop_task_refs, start_task_ids)
47
- implementation = Hash[_implementations]
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
- def find_connections(seq_row, strategies, sequence)
55
- strategies.collect do |search|
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, # implementation
62
- target_seq_row[3][:id], # intermediate # FIXME. this sucks.
63
- target_seq_row # DISCUSS: needed?
66
+ output,
67
+ target_seq_row.id
64
68
  ]
65
- end.compact
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
- # 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); recompile_activity_for(:step, *args, &block); end
34
- def terminus(*args); recompile_activity_for(:terminus, *args); end
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
- return Sequence::Builder.(type, arg, options,
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
- &block
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!(strategy, **options)
75
- options = strategy.OptionsForSequenceBuilder(**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 block_given?
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
-
@@ -3,7 +3,7 @@ module Trailblazer
3
3
  module Activity
4
4
  module DSL
5
5
  module Linear
6
- VERSION = "1.0.0"
6
+ VERSION = "1.2.0"
7
7
  end
8
8
  end
9
9
  end