trailblazer-activity-dsl-linear 0.5.0 → 1.0.0.beta1
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 +1 -1
- data/CHANGES.md +52 -0
- data/Gemfile +3 -1
- data/README.md +9 -17
- data/lib/trailblazer/activity/dsl/linear/feature/merge.rb +36 -0
- data/lib/trailblazer/activity/dsl/linear/feature/patch.rb +40 -0
- data/lib/trailblazer/activity/dsl/linear/feature/variable_mapping/dsl.rb +281 -0
- data/lib/trailblazer/activity/dsl/linear/feature/variable_mapping/inherit.rb +38 -0
- data/lib/trailblazer/activity/dsl/linear/feature/variable_mapping.rb +298 -0
- data/lib/trailblazer/activity/dsl/linear/helper/path.rb +106 -0
- data/lib/trailblazer/activity/dsl/linear/helper.rb +54 -128
- data/lib/trailblazer/activity/dsl/linear/normalizer/terminus.rb +92 -0
- data/lib/trailblazer/activity/dsl/linear/normalizer.rb +194 -77
- data/lib/trailblazer/activity/dsl/linear/sequence/builder.rb +47 -0
- data/lib/trailblazer/activity/dsl/linear/sequence/compiler.rb +72 -0
- data/lib/trailblazer/activity/dsl/linear/sequence/search.rb +58 -0
- data/lib/trailblazer/activity/dsl/linear/sequence.rb +34 -0
- data/lib/trailblazer/activity/dsl/linear/strategy.rb +116 -71
- data/lib/trailblazer/activity/dsl/linear/version.rb +1 -1
- data/lib/trailblazer/activity/dsl/linear.rb +21 -177
- data/lib/trailblazer/activity/fast_track.rb +42 -53
- data/lib/trailblazer/activity/path.rb +52 -127
- data/lib/trailblazer/activity/railway.rb +48 -68
- data/trailblazer-activity-dsl-linear.gemspec +3 -2
- metadata +41 -13
- data/lib/trailblazer/activity/dsl/linear/compiler.rb +0 -70
- data/lib/trailblazer/activity/dsl/linear/state.rb +0 -63
- data/lib/trailblazer/activity/dsl/linear/variable_mapping.rb +0 -240
@@ -0,0 +1,92 @@
|
|
1
|
+
module Trailblazer
|
2
|
+
class Activity
|
3
|
+
module DSL
|
4
|
+
module Linear
|
5
|
+
module Normalizer
|
6
|
+
# Normalizer pipeline for the {terminus} DSL method.
|
7
|
+
module Terminus
|
8
|
+
module_function
|
9
|
+
|
10
|
+
def Normalizer
|
11
|
+
normalizer_steps =
|
12
|
+
{
|
13
|
+
"activity.normalize_step_interface" => Normalizer.Task(Normalizer.method(:normalize_step_interface)), # first
|
14
|
+
"activity.merge_library_options" => Normalizer.Task(Normalizer.method(:merge_library_options)), # Merge "macro"/user options over library options.
|
15
|
+
"activity.normalize_for_macro" => Normalizer.Task(Normalizer.method(:merge_user_options)),
|
16
|
+
"activity.normalize_normalizer_options" => Normalizer.Task(Normalizer.method(:merge_normalizer_options)),
|
17
|
+
"activity.normalize_non_symbol_options" => Normalizer.Task(Normalizer.method(:normalize_non_symbol_options)),
|
18
|
+
"activity.normalize_context" => Normalizer.method(:normalize_context),
|
19
|
+
"terminus.normalize_task" => Normalizer.Task(Terminus.method(:normalize_task)),
|
20
|
+
"terminus.normalize_id" => Normalizer.Task(method(:normalize_id)),
|
21
|
+
"terminus.normalize_magnetic_to" => Normalizer.Task(Terminus.method(:normalize_magnetic_to)),
|
22
|
+
"terminus.append_end" => Normalizer.Task(Terminus.method(:append_end)),
|
23
|
+
|
24
|
+
"activity.compile_data" => Normalizer.Task(Normalizer.method(:compile_data)), # FIXME
|
25
|
+
"activity.create_row" => Normalizer.Task(Normalizer.method(:create_row)),
|
26
|
+
"activity.create_add" => Normalizer.Task(Normalizer.method(:create_add)),
|
27
|
+
"activity.create_adds" => Normalizer.Task(Normalizer.method(:create_adds)),
|
28
|
+
}
|
29
|
+
|
30
|
+
TaskWrap::Pipeline.new(normalizer_steps.to_a)
|
31
|
+
end
|
32
|
+
|
33
|
+
# @private
|
34
|
+
def normalize_id(ctx, id: nil, semantic:, **)
|
35
|
+
ctx.merge!(
|
36
|
+
id: id || Strategy.end_id(semantic: semantic)
|
37
|
+
)
|
38
|
+
end
|
39
|
+
|
40
|
+
# @private
|
41
|
+
# Set {:task} and {:semantic}.
|
42
|
+
def normalize_task(ctx, task:, **)
|
43
|
+
if task.kind_of?(Activity::End) # DISCUSS: do we want this check?
|
44
|
+
ctx = _normalize_task_for_end_event(ctx, **ctx)
|
45
|
+
else
|
46
|
+
# When used such as {terminus :found}, create the end event automatically.
|
47
|
+
ctx = _normalize_task_for_symbol(ctx, **ctx)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def _normalize_task_for_end_event(ctx, task:, **) # you cannot override using {:semantic}
|
52
|
+
ctx.merge!(
|
53
|
+
semantic: task.to_h[:semantic]
|
54
|
+
)
|
55
|
+
end
|
56
|
+
|
57
|
+
def _normalize_task_for_symbol(ctx, task:, semantic: task, **)
|
58
|
+
ctx.merge!(
|
59
|
+
task: Strategy.End(semantic),
|
60
|
+
semantic: semantic,
|
61
|
+
)
|
62
|
+
end
|
63
|
+
|
64
|
+
# @private
|
65
|
+
def normalize_magnetic_to(ctx, magnetic_to: nil, semantic:, **)
|
66
|
+
return if magnetic_to
|
67
|
+
ctx.merge!(magnetic_to: semantic)
|
68
|
+
end
|
69
|
+
|
70
|
+
# @private
|
71
|
+
def append_end(ctx, task:, semantic:, append_to: "End.success", **)
|
72
|
+
terminus_args = {
|
73
|
+
sequence_insert: [Activity::Adds::Insert.method(:Append), append_to],
|
74
|
+
stop_event: true
|
75
|
+
}
|
76
|
+
|
77
|
+
ctx.merge!(
|
78
|
+
wirings: [
|
79
|
+
Linear::Sequence::Search::Noop(
|
80
|
+
Activity::Output.new(task, semantic), # DISCUSS: do we really want to transport the semantic "in" the object?
|
81
|
+
)
|
82
|
+
],
|
83
|
+
adds: [],
|
84
|
+
**terminus_args
|
85
|
+
)
|
86
|
+
end
|
87
|
+
end # Terminus
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -2,57 +2,119 @@ module Trailblazer
|
|
2
2
|
class Activity
|
3
3
|
module DSL
|
4
4
|
module Linear
|
5
|
-
# Normalizers are linear activities that process and normalize the options from a DSL call
|
6
|
-
#
|
5
|
+
# Normalizers are linear activities that process and normalize the options from a specific DSL call,
|
6
|
+
# such as `#step` or `#pass`. All defaulting should happen through the normalizer. An invoked
|
7
|
+
# normalizer produces an options hash that has to contain an [:adds] key with a ADDS structure usable
|
8
|
+
# for {Sequence.apply_adds}.
|
9
|
+
#
|
10
|
+
# They're usually invoked from {Strategy#invoke_normalizer_for!}, which is called from {Path#step},
|
11
|
+
# {Railway#pass}, etc.
|
7
12
|
module Normalizer
|
13
|
+
# Container for all final normalizers of a specific Strategy.
|
14
|
+
class Normalizers
|
15
|
+
def initialize(**options)
|
16
|
+
@normalizers = options
|
17
|
+
end
|
18
|
+
# Execute the specific normalizer (step, fail, pass) for a particular option set provided
|
19
|
+
# by the DSL user. Usually invoked when you call {#step}.
|
20
|
+
def call(name, ctx)
|
21
|
+
normalizer = @normalizers.fetch(name)
|
22
|
+
wrap_ctx, _ = normalizer.(ctx, nil)
|
23
|
+
wrap_ctx
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
8
27
|
module_function
|
9
28
|
|
10
|
-
#
|
11
|
-
#
|
29
|
+
# Helper for normalizers.
|
30
|
+
# To be applied on {Pipeline} instances.
|
31
|
+
def self.prepend_to(pipe, insertion_id, insertion)
|
32
|
+
adds =
|
33
|
+
insertion.collect do |id, task|
|
34
|
+
{insert: [Adds::Insert.method(:Prepend), insertion_id], row: Activity::TaskWrap::Pipeline.Row(id, task)}
|
35
|
+
end
|
36
|
+
|
37
|
+
Adds.apply_adds(pipe, insertion_id ? adds : adds.reverse)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Helper for normalizers.
|
41
|
+
def self.replace(pipe, insertion_id, (id, task))
|
42
|
+
Adds.apply_adds(
|
43
|
+
pipe,
|
44
|
+
[{insert: [Adds::Insert.method(:Replace), insertion_id], row: Activity::TaskWrap::Pipeline.Row(id, task)}]
|
45
|
+
)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Extend a particular normalizer with new steps and save it on the activity.
|
49
|
+
def self.extend!(activity_class, *step_methods)
|
50
|
+
activity_class.instance_variable_get(:@state).update!(:normalizers) do |normalizers|
|
51
|
+
hsh = normalizers.instance_variable_get(:@normalizers) # TODO: introduce {#to_h}.
|
52
|
+
|
53
|
+
new_normalizers = # {step: #<..>, pass: #<..>}
|
54
|
+
step_methods.collect do |name|
|
55
|
+
extended_normalizer = hsh.fetch(name) # grab existing normalizer.
|
56
|
+
new_normalizer = yield(extended_normalizer) # and let the user block change it.
|
57
|
+
[name, new_normalizer]
|
58
|
+
end.to_h
|
59
|
+
|
60
|
+
|
61
|
+
Normalizers.new(**hsh.merge(new_normalizers))
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# @private
|
12
66
|
class Task < TaskBuilder::Task
|
13
67
|
def call(wrap_ctx, flow_options={})
|
14
|
-
|
68
|
+
_result = call_option(@task, [wrap_ctx, flow_options]) # DISCUSS: this mutates {ctx}.
|
15
69
|
|
16
70
|
return wrap_ctx, flow_options
|
17
71
|
end
|
18
72
|
end
|
19
73
|
|
74
|
+
# Wrap user's normalizer task with {Trailblazer::Option} and thus execute it with
|
75
|
+
# convenient kw args.
|
76
|
+
#
|
77
|
+
# Example
|
78
|
+
# normalizer_task = Normalizer.Task(method(:normalize_id))
|
79
|
+
#
|
80
|
+
# # will call {normalizer_task} and pass ctx variables as kwargs, as follows
|
81
|
+
# def normalize_id(ctx, id: false, task:, **)
|
20
82
|
def Task(user_proc)
|
21
83
|
Normalizer::Task.new(Trailblazer::Option(user_proc), user_proc)
|
22
84
|
end
|
23
85
|
|
24
|
-
#
|
25
|
-
def
|
26
|
-
pipeline = TaskWrap::Pipeline.
|
27
|
-
pipeline,
|
28
|
-
nil, # this means, put it to the beginning.
|
86
|
+
# The generic normalizer not tied to `step` or friends.
|
87
|
+
def Normalizer
|
88
|
+
pipeline = TaskWrap::Pipeline.new(
|
29
89
|
{
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
90
|
+
"activity.normalize_step_interface" => Normalizer.Task(method(:normalize_step_interface)), # Makes sure {:options} is always a hash.
|
91
|
+
"activity.merge_library_options" => Normalizer.Task(method(:merge_library_options)), # Merge "macro"/user options over library options.
|
92
|
+
"activity.normalize_for_macro" => Normalizer.Task(method(:merge_user_options)), # Merge user_options over "macro" options.
|
93
|
+
"activity.normalize_normalizer_options" => Normalizer.Task(method(:merge_normalizer_options)), # Merge user_options over normalizer_options.
|
94
|
+
"activity.normalize_non_symbol_options" => Normalizer.Task(method(:normalize_non_symbol_options)),
|
95
|
+
"activity.path_helper.forward_block" => Normalizer.Task(Helper::Path::Normalizer.method(:forward_block_for_path_branch)), # forward the "global" block
|
96
|
+
"activity.normalize_context" => method(:normalize_context),
|
97
|
+
"activity.normalize_id" => Normalizer.Task(method(:normalize_id)),
|
98
|
+
"activity.normalize_override" => Normalizer.Task(method(:normalize_override)),
|
99
|
+
"activity.wrap_task_with_step_interface" => Normalizer.Task(method(:wrap_task_with_step_interface)),
|
100
|
+
|
101
|
+
"activity.inherit_option" => Normalizer.Task(method(:inherit_option)),
|
102
|
+
"activity.sequence_insert" => Normalizer.Task(method(:normalize_sequence_insert)),
|
103
|
+
"activity.normalize_duplications" => Normalizer.Task(method(:normalize_duplications)),
|
104
|
+
|
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)),
|
108
|
+
|
109
|
+
"activity.wirings" => Normalizer.Task(method(:compile_wirings)),
|
110
|
+
|
111
|
+
# TODO: make this a "Subprocess":
|
112
|
+
"activity.compile_data" => Normalizer.Task(method(:compile_data)),
|
113
|
+
"activity.create_row" => Normalizer.Task(method(:create_row)),
|
114
|
+
"activity.create_add" => Normalizer.Task(method(:create_add)),
|
115
|
+
"activity.create_adds" => Normalizer.Task(method(:create_adds)),
|
116
|
+
}.
|
117
|
+
collect { |id, task| TaskWrap::Pipeline.Row(id, task) }
|
56
118
|
)
|
57
119
|
|
58
120
|
pipeline
|
@@ -101,10 +163,15 @@ module Trailblazer
|
|
101
163
|
ctx[:replace] = (id || raise)
|
102
164
|
end
|
103
165
|
|
166
|
+
# {:library_options} such as :sequence, :dsl_track, etc.
|
167
|
+
def merge_library_options(ctx, options:, library_options:, **)
|
168
|
+
ctx[:options] = library_options.merge(options)
|
169
|
+
end
|
170
|
+
|
104
171
|
# make ctx[:options] the actual ctx
|
105
|
-
def merge_user_options(ctx, options:, **)
|
172
|
+
def merge_user_options(ctx, options:, user_options:, **)
|
106
173
|
# {options} are either a <#task> or {} from macro
|
107
|
-
ctx[:options] = options.merge(
|
174
|
+
ctx[:options] = options.merge(user_options) # Note that the user options are merged over the macro options.
|
108
175
|
end
|
109
176
|
|
110
177
|
# {:normalizer_options} such as {:track_name} get overridden by user/macro.
|
@@ -132,6 +199,48 @@ module Trailblazer
|
|
132
199
|
end
|
133
200
|
end
|
134
201
|
|
202
|
+
# Processes {:before,:after,:replace,:delete} options and
|
203
|
+
# defaults to {before: "End.success"} which, yeah.
|
204
|
+
def normalize_sequence_insert(ctx, end_id:, **)
|
205
|
+
insertion = ctx.keys & sequence_insert_options.keys
|
206
|
+
insertion = insertion[0] || :before
|
207
|
+
target = ctx[insertion] || end_id
|
208
|
+
|
209
|
+
insertion_method = sequence_insert_options[insertion]
|
210
|
+
|
211
|
+
ctx[:sequence_insert] = [Activity::Adds::Insert.method(insertion_method), target]
|
212
|
+
end
|
213
|
+
|
214
|
+
# @private
|
215
|
+
def sequence_insert_options
|
216
|
+
{
|
217
|
+
:before => :Prepend,
|
218
|
+
:after => :Append,
|
219
|
+
:replace => :Replace,
|
220
|
+
:delete => :Delete,
|
221
|
+
}
|
222
|
+
end
|
223
|
+
|
224
|
+
def normalize_duplications(ctx, replace: false, **)
|
225
|
+
return if replace
|
226
|
+
|
227
|
+
raise_on_duplicate_id(ctx, **ctx)
|
228
|
+
clone_duplicate_activity(ctx, **ctx) # DISCUSS: mutates {ctx}.
|
229
|
+
end
|
230
|
+
|
231
|
+
# @private
|
232
|
+
def raise_on_duplicate_id(ctx, id:, sequence:, **)
|
233
|
+
raise "ID #{id} is already taken. Please specify an `:id`." if sequence.find { |row| row.id == id }
|
234
|
+
end
|
235
|
+
|
236
|
+
# @private
|
237
|
+
def clone_duplicate_activity(ctx, task:, sequence:, **)
|
238
|
+
return unless task.is_a?(Class)
|
239
|
+
|
240
|
+
ctx[:task] = task.clone if sequence.find { |row| row[1] == task }
|
241
|
+
end
|
242
|
+
|
243
|
+
|
135
244
|
# Move DSL user options such as {Output(:success) => Track(:found)} to
|
136
245
|
# a new key {options[:non_symbol_options]}.
|
137
246
|
# This allows using {options} as a {**ctx}-able hash in Ruby 2.6 and 3.0.
|
@@ -144,24 +253,23 @@ module Trailblazer
|
|
144
253
|
end
|
145
254
|
|
146
255
|
# Process {Output(:semantic) => target} and make them {:connections}.
|
147
|
-
def normalize_connections_from_dsl(ctx, connections:, adds:, non_symbol_options:, **)
|
256
|
+
def normalize_connections_from_dsl(ctx, connections:, adds:, non_symbol_options:, sequence:, normalizers:, **)
|
148
257
|
# Find all {Output() => Track()/Id()/End()}
|
149
|
-
output_configs = non_symbol_options.find_all{ |k,v| k.kind_of?(
|
258
|
+
output_configs = non_symbol_options.find_all{ |k,v| k.kind_of?(Linear::OutputSemantic) }
|
150
259
|
return unless output_configs.any?
|
151
260
|
|
261
|
+
# DISCUSS: how could we add another magnetic_to to an end?
|
152
262
|
output_configs.each do |output, cfg|
|
153
263
|
new_connections, add =
|
154
|
-
if cfg.is_a?(
|
155
|
-
[output_to_track(ctx, output, cfg), cfg.adds]
|
156
|
-
elsif cfg.is_a?(
|
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)
|
157
267
|
[output_to_id(ctx, output, cfg.value), []]
|
158
268
|
elsif cfg.is_a?(Activity::End)
|
159
|
-
|
269
|
+
end_id = Activity::Railway.end_id(**cfg.to_h)
|
270
|
+
end_exists = Activity::Adds::Insert.find_index(ctx[:sequence], end_id)
|
160
271
|
|
161
|
-
|
162
|
-
end_exists = Insert.find_index(ctx[:sequence], end_id)
|
163
|
-
|
164
|
-
_adds = [add_end(cfg, magnetic_to: end_id, id: end_id)] unless end_exists
|
272
|
+
_adds = end_exists ? [] : add_terminus(cfg, id: end_id, sequence: sequence, normalizers: normalizers)
|
165
273
|
|
166
274
|
[output_to_id(ctx, output, end_id), _adds]
|
167
275
|
else
|
@@ -179,23 +287,18 @@ module Trailblazer
|
|
179
287
|
def output_to_track(ctx, output, track)
|
180
288
|
search_strategy = track.options[:wrap_around] ? :WrapAround : :Forward
|
181
289
|
|
182
|
-
{output.value => [Linear::Search.method(search_strategy), track.color]}
|
290
|
+
{output.value => [Linear::Sequence::Search.method(search_strategy), track.color]}
|
183
291
|
end
|
184
292
|
|
185
293
|
def output_to_id(ctx, output, target)
|
186
|
-
{output.value => [Linear::Search.method(:ById), target]}
|
294
|
+
{output.value => [Linear::Sequence::Search.method(:ById), target]}
|
187
295
|
end
|
188
296
|
|
189
|
-
#
|
190
|
-
def
|
191
|
-
|
192
|
-
options = Path::DSL.append_end_options(task: end_event, magnetic_to: magnetic_to, id: id)
|
193
|
-
row = Linear::Sequence.create_row(**options)
|
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)
|
194
300
|
|
195
|
-
|
196
|
-
row: row,
|
197
|
-
insert: row[3][:sequence_insert]
|
198
|
-
}
|
301
|
+
step_options[:adds]
|
199
302
|
end
|
200
303
|
|
201
304
|
# Output(Signal, :semantic) => Id()
|
@@ -207,30 +310,32 @@ module Trailblazer
|
|
207
310
|
|
208
311
|
output_configs.collect do |output, cfg| # {cfg} = Track(:success)
|
209
312
|
outputs = outputs.merge(output.semantic => output)
|
210
|
-
dsl_options = dsl_options.merge(Linear.Output(output.semantic) => cfg)
|
313
|
+
dsl_options = dsl_options.merge(Linear::Strategy.Output(output.semantic) => cfg)
|
211
314
|
end
|
212
315
|
|
213
316
|
ctx[:outputs] = outputs
|
214
317
|
ctx[:non_symbol_options] = non_symbol_options.merge(dsl_options)
|
215
318
|
end
|
216
319
|
|
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.
|
220
|
-
|
221
|
-
ctx[:extensions] = extensions + [Linear.VariableMapping(**config)]
|
222
|
-
end
|
223
|
-
|
224
320
|
# Currently, the {:inherit} option copies over {:connections} from the original step
|
225
321
|
# and merges them with the (prolly) connections passed from the user.
|
226
322
|
def inherit_option(ctx, inherit: false, sequence:, id:, extensions: [], **)
|
227
|
-
return unless inherit
|
323
|
+
return unless inherit === true
|
228
324
|
|
229
|
-
|
230
|
-
|
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
|
231
333
|
|
232
|
-
|
233
|
-
|
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
|
234
339
|
end
|
235
340
|
|
236
341
|
# return connections from {parent} step which are supported by current step
|
@@ -240,12 +345,24 @@ module Trailblazer
|
|
240
345
|
parent_connections.slice(*ctx[:outputs].keys)
|
241
346
|
end
|
242
347
|
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
348
|
+
def create_row(ctx, task:, wirings:, magnetic_to:, data:, **)
|
349
|
+
ctx[:row] = Sequence.create_row(task: task, magnetic_to: magnetic_to, wirings: wirings, **data)
|
350
|
+
end
|
351
|
+
|
352
|
+
def create_add(ctx, row:, sequence_insert:, **)
|
353
|
+
ctx[:add] = {row: row, insert: sequence_insert}
|
354
|
+
end
|
355
|
+
|
356
|
+
def create_adds(ctx, add:, adds:, **)
|
357
|
+
ctx[:adds] = [add] + adds
|
358
|
+
end
|
359
|
+
|
360
|
+
# TODO: document DataVariable() => :name
|
361
|
+
# 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
|
247
364
|
|
248
|
-
|
365
|
+
ctx[:data] = (default_variables_for_data + variables_for_data).collect { |key| [key, ctx[key]] }.to_h
|
249
366
|
end
|
250
367
|
end
|
251
368
|
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Trailblazer
|
2
|
+
class Activity
|
3
|
+
module DSL
|
4
|
+
module Linear
|
5
|
+
class Sequence
|
6
|
+
# Implements a DSL Builder pattern.
|
7
|
+
module Builder
|
8
|
+
# @return Sequence
|
9
|
+
def self.call(method, argument, options, **kws, &block)
|
10
|
+
update_sequence_for(method, argument, options, **kws, &block)
|
11
|
+
end
|
12
|
+
|
13
|
+
# @private
|
14
|
+
# Run a specific normalizer (e.g. for `#step`), apply the adds to the sequence and return the latter.
|
15
|
+
# DISCUSS: where does this method belong? Sequence + Normalizers?
|
16
|
+
def self.update_sequence_for(type, task, options={}, sequence:, **kws, &block)
|
17
|
+
step_options = invoke_normalizer_for(type, task, options, sequence: sequence, **kws, &block)
|
18
|
+
|
19
|
+
_sequence = Activity::Adds.apply_adds(sequence, step_options[:adds])
|
20
|
+
end
|
21
|
+
|
22
|
+
# @private
|
23
|
+
# DISCUSS: used in {Normalizer#add_terminus}, too.
|
24
|
+
def self.invoke_normalizer_for(type, task, options, normalizers:, normalizer_options:, sequence:, &block)
|
25
|
+
# These options represent direct configuration of the very method call that causes the normalizer to be run.
|
26
|
+
library_options = {
|
27
|
+
dsl_track: type,
|
28
|
+
block: block,
|
29
|
+
|
30
|
+
# DISCUSS: for some reason I am not entirely happy with those variables being here. Maybe this will change.
|
31
|
+
normalizers: normalizers,
|
32
|
+
sequence: sequence,
|
33
|
+
}
|
34
|
+
|
35
|
+
_step_options = normalizers.(type,
|
36
|
+
normalizer_options: normalizer_options, # class-level Strategy configuration, such as :step_interface_builder
|
37
|
+
options: task, # macro-options
|
38
|
+
user_options: options, # user-specified options from the DSL method
|
39
|
+
library_options: library_options # see above, "runtime" options (from compile-time, haha).
|
40
|
+
)
|
41
|
+
end
|
42
|
+
end # Builder
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Trailblazer
|
2
|
+
class Activity
|
3
|
+
module DSL
|
4
|
+
module Linear
|
5
|
+
class Sequence
|
6
|
+
# Compile a {Schema} by computing {implementations} and {intermediate} from a {Sequence}.
|
7
|
+
module Compiler
|
8
|
+
module_function
|
9
|
+
|
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
|
13
|
+
end
|
14
|
+
|
15
|
+
# 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]
|
18
|
+
end
|
19
|
+
|
20
|
+
def call(sequence, find_stops: method(:find_stop_task_ids), find_start: method(:find_start_task_ids))
|
21
|
+
_implementations, intermediate_wiring =
|
22
|
+
sequence.inject([[], []]) do |(implementations, intermediates), seq_row|
|
23
|
+
_magnetic_to, task, connections, data = seq_row
|
24
|
+
id = data[:id]
|
25
|
+
|
26
|
+
# execute all {Search}s for one sequence row.
|
27
|
+
connections = find_connections(seq_row, connections, sequence)
|
28
|
+
|
29
|
+
# FIXME: {:extensions} should be initialized
|
30
|
+
implementations += [[id, Schema::Implementation::Task(task, connections.collect { |output, _| output }, data[:extensions] || []) ]]
|
31
|
+
|
32
|
+
intermediates += [
|
33
|
+
[
|
34
|
+
Schema::Intermediate::TaskRef(id, data),
|
35
|
+
# Compute outputs.
|
36
|
+
connections.collect { |output, target_id| Schema::Intermediate::Out(output.semantic, target_id) }
|
37
|
+
]
|
38
|
+
]
|
39
|
+
|
40
|
+
[implementations, intermediates]
|
41
|
+
end
|
42
|
+
|
43
|
+
start_task_ids = find_start.(intermediate_wiring)
|
44
|
+
stop_task_refs = find_stops.(intermediate_wiring)
|
45
|
+
|
46
|
+
intermediate = Schema::Intermediate.new(Hash[intermediate_wiring], stop_task_refs, start_task_ids)
|
47
|
+
implementation = Hash[_implementations]
|
48
|
+
|
49
|
+
Schema::Intermediate.(intermediate, implementation) # implemented in the generic {trailblazer-activity} gem.
|
50
|
+
end
|
51
|
+
|
52
|
+
# private
|
53
|
+
|
54
|
+
def find_connections(seq_row, strategies, sequence)
|
55
|
+
strategies.collect do |search|
|
56
|
+
output, target_seq_row = search.(sequence, seq_row) # invoke the node's "connection search" strategy.
|
57
|
+
|
58
|
+
target_seq_row = sequence[-1] if target_seq_row.nil? # connect to an End if target unknown. # DISCUSS: make this configurable, maybe?
|
59
|
+
|
60
|
+
[
|
61
|
+
output, # implementation
|
62
|
+
target_seq_row[3][:id], # intermediate # FIXME. this sucks.
|
63
|
+
target_seq_row # DISCUSS: needed?
|
64
|
+
]
|
65
|
+
end.compact
|
66
|
+
end
|
67
|
+
end # Compiler
|
68
|
+
end # Sequence
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
class Trailblazer::Activity
|
2
|
+
module DSL
|
3
|
+
module Linear
|
4
|
+
# Search strategies are part of the {wirings}, they find the next step
|
5
|
+
# for an output.
|
6
|
+
class Sequence
|
7
|
+
module Search
|
8
|
+
module_function
|
9
|
+
|
10
|
+
# From this task onwards, find the next task that's "magnetic to" {target_color}.
|
11
|
+
# Note that we only go forward, no back-references are done here.
|
12
|
+
def Forward(output, target_color)
|
13
|
+
->(sequence, me) do
|
14
|
+
target_seq_row = find_in_range(sequence[sequence.index(me)+1..-1], target_color)
|
15
|
+
|
16
|
+
return output, target_seq_row
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Tries to find a track colored step by doing a Forward-search, first, then wraps around going
|
21
|
+
# through all steps from sequence start to self.
|
22
|
+
def WrapAround(output, target_color)
|
23
|
+
->(sequence, me) do
|
24
|
+
my_index = sequence.index(me)
|
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]
|
27
|
+
|
28
|
+
target_seq_row = find_in_range(wrapped_range, target_color)
|
29
|
+
|
30
|
+
return output, target_seq_row
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def Noop(output)
|
35
|
+
->(sequence, me) do
|
36
|
+
return output, [nil,nil,nil,{}] # FIXME
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Find the seq_row with {id} and connect the current node to it.
|
41
|
+
def ById(output, id)
|
42
|
+
->(sequence, me) do
|
43
|
+
index = Adds::Insert.find_index(sequence, id) or return output, sequence[0] # FIXME # or raise "Couldn't find {#{id}}"
|
44
|
+
target_seq_row = sequence[index]
|
45
|
+
|
46
|
+
return output, target_seq_row
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# @private
|
51
|
+
def find_in_range(range, target_color)
|
52
|
+
_target_seq_row = range.find { |seq_row| seq_row[0] == target_color }
|
53
|
+
end
|
54
|
+
end # Search
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class Trailblazer::Activity
|
2
|
+
module DSL
|
3
|
+
module Linear
|
4
|
+
# A {Sequence} consists of rows, each row represents one step (or task) of an activity
|
5
|
+
# and its incoming and outgoing connections.
|
6
|
+
# {Sequence row} consisting of {[magnetic_to, task, connections_searches, data]}.
|
7
|
+
# A Sequence is compiled into an activity using {Compiler}.
|
8
|
+
#
|
9
|
+
# Complies with the Adds interface (#to_a).
|
10
|
+
class Sequence < Array
|
11
|
+
# Row interface is part of the ADDs specification.
|
12
|
+
class Row < Array
|
13
|
+
def id
|
14
|
+
data[:id]
|
15
|
+
end
|
16
|
+
|
17
|
+
def data
|
18
|
+
self[3]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Return {Sequence row} consisting of {[magnetic_to, task, connections_searches, data]}.
|
23
|
+
def self.create_row(task:, magnetic_to:, wirings:, **data)
|
24
|
+
Row[
|
25
|
+
magnetic_to,
|
26
|
+
task,
|
27
|
+
wirings,
|
28
|
+
data # {id: "Start.success"}
|
29
|
+
]
|
30
|
+
end
|
31
|
+
end # Sequence
|
32
|
+
end # Linear
|
33
|
+
end
|
34
|
+
end
|