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