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,298 @@
|
|
1
|
+
module Trailblazer
|
2
|
+
class Activity
|
3
|
+
module DSL
|
4
|
+
module Linear
|
5
|
+
# Normalizer-steps to implement {:input} and {:output}
|
6
|
+
# Returns an Extension instance to be thrown into the `step` DSL arguments.
|
7
|
+
def self.VariableMapping(input_id: "task_wrap.input", output_id: "task_wrap.output", **options)
|
8
|
+
input, output, normalizer_options, non_symbol_options = VariableMapping.merge_instructions_from_dsl(**options)
|
9
|
+
|
10
|
+
extension = VariableMapping.Extension(input, output)
|
11
|
+
|
12
|
+
return TaskWrap::Extension::WrapStatic.new(extension: extension), normalizer_options, non_symbol_options
|
13
|
+
end
|
14
|
+
|
15
|
+
module VariableMapping
|
16
|
+
# Add our normalizer steps to the strategy's normalizer.
|
17
|
+
def self.extend!(strategy, *step_methods) # DISCUSS: should this be implemented in Linear?
|
18
|
+
Linear::Normalizer.extend!(strategy, *step_methods) do |normalizer|
|
19
|
+
Linear::Normalizer.prepend_to(
|
20
|
+
normalizer,
|
21
|
+
"activity.wirings",
|
22
|
+
{
|
23
|
+
# In(), Out(), {:input}, Inject() feature
|
24
|
+
"activity.normalize_input_output_filters" => Linear::Normalizer.Task(VariableMapping::Normalizer.method(:normalize_input_output_filters)),
|
25
|
+
"activity.input_output_dsl" => Linear::Normalizer.Task(VariableMapping::Normalizer.method(:input_output_dsl)),
|
26
|
+
}
|
27
|
+
)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.Extension(input, output, input_id: "task_wrap.input", output_id: "task_wrap.output")
|
32
|
+
TaskWrap.Extension(
|
33
|
+
[input, id: input_id, prepend: "task_wrap.call_task"],
|
34
|
+
[output, id: output_id, append: "task_wrap.call_task"]
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Steps that are added to the DSL normalizer.
|
39
|
+
module Normalizer
|
40
|
+
# Process {In() => [:model], Inject() => [:current_user], Out() => [:model]}
|
41
|
+
def self.normalize_input_output_filters(ctx, non_symbol_options:, **)
|
42
|
+
input_exts = non_symbol_options.find_all { |k,v| k.is_a?(VariableMapping::DSL::In) }
|
43
|
+
output_exts = non_symbol_options.find_all { |k,v| k.is_a?(VariableMapping::DSL::Out) }
|
44
|
+
inject_exts = non_symbol_options.find_all { |k,v| k.is_a?(VariableMapping::DSL::Inject) }
|
45
|
+
|
46
|
+
return unless input_exts.any? || output_exts.any? || inject_exts.any?
|
47
|
+
|
48
|
+
ctx[:inject_filters] = inject_exts
|
49
|
+
ctx[:in_filters] = input_exts
|
50
|
+
ctx[:out_filters] = output_exts
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.input_output_dsl(ctx, extensions: [], **options)
|
54
|
+
# no :input/:output/:inject/Input()/Output() passed.
|
55
|
+
return if (options.keys & [:input, :output, :inject, :inject_filters, :in_filters, :output_filters]).empty?
|
56
|
+
|
57
|
+
extension, normalizer_options, non_symbol_options = Linear.VariableMapping(**options)
|
58
|
+
|
59
|
+
ctx[:extensions] = extensions + [extension] # FIXME: allow {Extension() => extension}
|
60
|
+
ctx.merge!(**normalizer_options) # DISCUSS: is there another way of merging variables into ctx?
|
61
|
+
ctx[:non_symbol_options].merge!(non_symbol_options)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
module_function
|
66
|
+
|
67
|
+
# For the input filter we
|
68
|
+
# 1. create a separate {Pipeline} instance {pipe}. Depending on the user's options, this might have up to four steps.
|
69
|
+
# 2. The {pipe} is run in a lamdba {input}, the lambda returns the pipe's ctx[:input_ctx].
|
70
|
+
# 3. The {input} filter in turn is wrapped into an {Activity::TaskWrap::Input} object via {#merge_instructions_for}.
|
71
|
+
# 4. The {TaskWrap::Input} instance is then finally placed into the taskWrap as {"task_wrap.input"}.
|
72
|
+
#
|
73
|
+
# @private
|
74
|
+
#
|
75
|
+
|
76
|
+
# default_input
|
77
|
+
# <or>
|
78
|
+
# oldway
|
79
|
+
# :input
|
80
|
+
# :inject
|
81
|
+
# newway(initial_input_pipeline)
|
82
|
+
# In,Inject
|
83
|
+
# => input_pipe
|
84
|
+
def merge_instructions_from_dsl(**options)
|
85
|
+
# The overriding {:input} option is set.
|
86
|
+
pipeline, has_mono_options, _ = DSL.pipe_for_mono_input(**options)
|
87
|
+
|
88
|
+
if ! has_mono_options
|
89
|
+
pipeline = DSL.pipe_for_composable_input(**options) # FIXME: rename filters consistently
|
90
|
+
end
|
91
|
+
|
92
|
+
# gets wrapped by {VariableMapping::Input} and called there.
|
93
|
+
# API: @filter.([ctx, original_flow_options], **original_circuit_options)
|
94
|
+
# input = Trailblazer::Option(->(original_ctx, **) { })
|
95
|
+
input = Pipe::Input.new(pipeline)
|
96
|
+
|
97
|
+
|
98
|
+
output_pipeline, has_mono_options, _ = DSL.pipe_for_mono_output(**options)
|
99
|
+
|
100
|
+
if ! has_mono_options
|
101
|
+
output_pipeline = DSL.pipe_for_composable_output(**options)
|
102
|
+
end
|
103
|
+
|
104
|
+
output = Pipe::Output.new(output_pipeline)
|
105
|
+
|
106
|
+
return input, output,
|
107
|
+
# normalizer_options:
|
108
|
+
{
|
109
|
+
variable_mapping_pipelines: [pipeline, output_pipeline],
|
110
|
+
},
|
111
|
+
# non_symbol_options:
|
112
|
+
{
|
113
|
+
Linear::Strategy.DataVariable() => :variable_mapping_pipelines # we want to store {:variable_mapping_pipelines} in {Row.data} for later reference.
|
114
|
+
}
|
115
|
+
# DISCUSS: should we remember the pure pipelines or get it from the compiled extension?
|
116
|
+
# store pipe in the extension (via TW::Extension.data)?
|
117
|
+
end
|
118
|
+
|
119
|
+
# < AddVariables
|
120
|
+
# Option
|
121
|
+
# filter
|
122
|
+
# merge_variables
|
123
|
+
|
124
|
+
# Runtime classes
|
125
|
+
Filter = Struct.new(:aggregate_step, :filter, :name, :add_variables_class)
|
126
|
+
|
127
|
+
# These objects are created via the DSL, keep all i/o steps in a Pipeline
|
128
|
+
# and run the latter when being `call`ed.
|
129
|
+
module Pipe
|
130
|
+
class Input
|
131
|
+
def initialize(pipe, id: :vm_original_ctx)
|
132
|
+
@pipe = pipe
|
133
|
+
@id = id # DISCUSS: untested.
|
134
|
+
end
|
135
|
+
|
136
|
+
def call(wrap_ctx, original_args)
|
137
|
+
(original_ctx, original_flow_options), original_circuit_options = original_args
|
138
|
+
|
139
|
+
# let user compute new ctx for the wrapped task.
|
140
|
+
pipe_ctx, _ = @pipe.({original_ctx: original_ctx}, [[original_ctx, original_flow_options], original_circuit_options])
|
141
|
+
input_ctx = pipe_ctx[:input_ctx]
|
142
|
+
|
143
|
+
wrap_ctx = wrap_ctx.merge(@id => original_ctx) # remember the original ctx under the key {:vm_original_ctx}.
|
144
|
+
|
145
|
+
# instead of the original Context, pass on the filtered `input_ctx` in the wrap.
|
146
|
+
return wrap_ctx, [[input_ctx, original_flow_options], original_circuit_options]
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# API in VariableMapping::Output:
|
151
|
+
# output_ctx = @filter.(returned_ctx, [original_ctx, returned_flow_options], **original_circuit_options)
|
152
|
+
# Returns {output_ctx} that is used after taskWrap finished.
|
153
|
+
class Output < Input
|
154
|
+
# def call(returned_ctx, (original_ctx, returned_flow_options), **original_circuit_options)
|
155
|
+
def call(wrap_ctx, original_args)
|
156
|
+
returned_ctx, returned_flow_options = wrap_ctx[:return_args] # this is the Context returned from {call}ing the wrapped user task.
|
157
|
+
original_ctx = wrap_ctx[@id] # grab the original ctx from before which was set in the {:input} filter.
|
158
|
+
_, original_circuit_options = original_args
|
159
|
+
|
160
|
+
# let user compute the output.
|
161
|
+
pipe_ctx, _ = @pipe.({original_ctx: original_ctx, returned_ctx: returned_ctx}, [[original_ctx, returned_flow_options], original_circuit_options])
|
162
|
+
|
163
|
+
output_ctx = pipe_ctx[:aggregate]
|
164
|
+
|
165
|
+
wrap_ctx = wrap_ctx.merge(return_args: [output_ctx, returned_flow_options]) # DISCUSS: this won't allow tracing in the taskWrap as we're returning {returned_flow_options} from above.
|
166
|
+
|
167
|
+
return wrap_ctx, original_args
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# DISCUSS: improvable sections such as merge vs hash[]=
|
173
|
+
def initial_aggregate(wrap_ctx, original_args)
|
174
|
+
wrap_ctx = wrap_ctx.merge(aggregate: {})
|
175
|
+
|
176
|
+
return wrap_ctx, original_args
|
177
|
+
end
|
178
|
+
|
179
|
+
# Merge all original ctx variables into the new input_ctx.
|
180
|
+
# This happens when no {:input} is provided.
|
181
|
+
def default_input_ctx(wrap_ctx, original_args)
|
182
|
+
default_ctx = wrap_ctx[:original_ctx]
|
183
|
+
|
184
|
+
merge_variables(default_ctx, wrap_ctx, original_args)
|
185
|
+
end
|
186
|
+
|
187
|
+
# Input/output Pipeline step that runs the user's {filter} and adds
|
188
|
+
# variables to the computed ctx.
|
189
|
+
#
|
190
|
+
# Basically implements {:input}.
|
191
|
+
#
|
192
|
+
# AddVariables: I call something with an Option-interface and run the return value through merge_variables().
|
193
|
+
# works on {:aggregate} by (usually) producing a hash fragment that is merged with the existing {:aggregate}
|
194
|
+
class AddVariables
|
195
|
+
def initialize(filter, user_filter)
|
196
|
+
@filter = filter # The users input/output filter.
|
197
|
+
@user_filter = user_filter # this is for introspection.
|
198
|
+
end
|
199
|
+
|
200
|
+
def call(wrap_ctx, original_args)
|
201
|
+
((original_ctx, _), circuit_options) = original_args
|
202
|
+
# puts "@@@@@ #{wrap_ctx[:returned_ctx].inspect}"
|
203
|
+
|
204
|
+
# this is the actual logic.
|
205
|
+
variables = call_filter(wrap_ctx, original_ctx, circuit_options, original_args)
|
206
|
+
|
207
|
+
VariableMapping.merge_variables(variables, wrap_ctx, original_args)
|
208
|
+
end
|
209
|
+
|
210
|
+
def call_filter(wrap_ctx, original_ctx, circuit_options, original_args)
|
211
|
+
_variables = @filter.(original_ctx, keyword_arguments: original_ctx.to_hash, **circuit_options)
|
212
|
+
end
|
213
|
+
|
214
|
+
class ReadFromAggregate < AddVariables # FIXME: REFACTOR
|
215
|
+
def call_filter(wrap_ctx, original_ctx, circuit_options, original_args)
|
216
|
+
new_ctx = wrap_ctx[:aggregate]
|
217
|
+
|
218
|
+
_variables = @filter.(new_ctx, keyword_arguments: new_ctx.to_hash, **circuit_options)
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
class Output < AddVariables
|
223
|
+
def call_filter(wrap_ctx, original_ctx, circuit_options, original_args)
|
224
|
+
new_ctx = wrap_ctx[:returned_ctx]
|
225
|
+
|
226
|
+
@filter.(new_ctx, keyword_arguments: new_ctx.to_hash, **circuit_options)
|
227
|
+
end
|
228
|
+
|
229
|
+
# Pass {inner_ctx, outer_ctx, **inner_ctx}
|
230
|
+
class WithOuterContext < Output
|
231
|
+
def call_filter(wrap_ctx, original_ctx, circuit_options, original_args)
|
232
|
+
new_ctx = wrap_ctx[:returned_ctx]
|
233
|
+
|
234
|
+
@filter.(new_ctx, original_ctx, keyword_arguments: new_ctx.to_hash, **circuit_options)
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
# Always deletes from {:aggregate}.
|
239
|
+
class Delete < AddVariables
|
240
|
+
def call(wrap_ctx, original_args)
|
241
|
+
@filter.collect do |name|
|
242
|
+
wrap_ctx[:aggregate].delete(name) # FIXME: we're mutating a hash here!
|
243
|
+
end
|
244
|
+
|
245
|
+
return wrap_ctx, original_args
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
# Finally, create a new input ctx from all the
|
252
|
+
# collected input variables.
|
253
|
+
# This goes into the step/nested OP.
|
254
|
+
def scope(wrap_ctx, original_args)
|
255
|
+
((_, flow_options), _) = original_args
|
256
|
+
|
257
|
+
# this is the actual context passed into the step.
|
258
|
+
wrap_ctx[:input_ctx] = Trailblazer::Context(
|
259
|
+
wrap_ctx[:aggregate],
|
260
|
+
{}, # mutable variables
|
261
|
+
flow_options[:context_options]
|
262
|
+
)
|
263
|
+
|
264
|
+
return wrap_ctx, original_args
|
265
|
+
end
|
266
|
+
|
267
|
+
# Last call in every step. Currently replaces {:input_ctx} by adding variables using {#merge}.
|
268
|
+
# DISCUSS: improve here?
|
269
|
+
def merge_variables(variables, wrap_ctx, original_args)
|
270
|
+
wrap_ctx[:aggregate] = wrap_ctx[:aggregate].merge(variables)
|
271
|
+
|
272
|
+
return wrap_ctx, original_args
|
273
|
+
end
|
274
|
+
|
275
|
+
# @private
|
276
|
+
# The default {:output} filter only returns the "mutable" part of the inner ctx.
|
277
|
+
# This means only variables added using {inner_ctx[..]=} are merged on the outside.
|
278
|
+
def default_output_ctx(wrap_ctx, original_args)
|
279
|
+
new_ctx = wrap_ctx[:returned_ctx]
|
280
|
+
|
281
|
+
_wrapped, mutable = new_ctx.decompose # `_wrapped` is what the `:input` filter returned, `mutable` is what the task wrote to `scoped`.
|
282
|
+
|
283
|
+
merge_variables(mutable, wrap_ctx, original_args)
|
284
|
+
end
|
285
|
+
|
286
|
+
def merge_with_original(wrap_ctx, original_args)
|
287
|
+
original_ctx = wrap_ctx[:original_ctx] # outer ctx
|
288
|
+
output_variables = wrap_ctx[:aggregate]
|
289
|
+
|
290
|
+
wrap_ctx[:aggregate] = original_ctx.merge(output_variables) # FIXME: use merge_variables()
|
291
|
+
# pp wrap_ctx
|
292
|
+
return wrap_ctx, original_args
|
293
|
+
end
|
294
|
+
end # VariableMapping
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module Trailblazer
|
2
|
+
class Activity
|
3
|
+
module DSL
|
4
|
+
module Linear
|
5
|
+
module Helper
|
6
|
+
# Normalizer logic for {Path() do end}.
|
7
|
+
#
|
8
|
+
# TODO: it would be cool to be able to connect an (empty) path to specific termini,
|
9
|
+
# this would work if we could add multiple magnetic_to.
|
10
|
+
module Path
|
11
|
+
# Normalizer steps to handle Path() macro.
|
12
|
+
module Normalizer
|
13
|
+
module_function
|
14
|
+
# Replace a block-expecting {PathBranch} instance with another one that's holding
|
15
|
+
# the global {:block} from {#step ... do end}.
|
16
|
+
def forward_block_for_path_branch(ctx, options:, normalizer_options:, library_options:, **)
|
17
|
+
block = options[:block]
|
18
|
+
non_symbol_options = options[:non_symbol_options]
|
19
|
+
|
20
|
+
return unless block
|
21
|
+
|
22
|
+
output, path_branch =
|
23
|
+
non_symbol_options.find { |output, cfg| cfg.kind_of?(Linear::PathBranch) }
|
24
|
+
|
25
|
+
path_branch_with_block = Linear::PathBranch.new(
|
26
|
+
normalizer_options.
|
27
|
+
merge(path_branch.options).
|
28
|
+
merge(block: block)
|
29
|
+
)
|
30
|
+
|
31
|
+
ctx[:options] = ctx[:options].merge(non_symbol_options: non_symbol_options.merge(output => path_branch_with_block))
|
32
|
+
end
|
33
|
+
|
34
|
+
# Convert all occurrences of Path() to a corresponding {Track}.
|
35
|
+
# The {Track} instance contains all additional {adds} steps and
|
36
|
+
# is picked up in {Normalizer.normalize_connections_from_dsl}.
|
37
|
+
def convert_paths_to_tracks(ctx, non_symbol_options:, block: false, **)
|
38
|
+
new_tracks = non_symbol_options.
|
39
|
+
find_all { |output, cfg| cfg.kind_of?(Linear::PathBranch) }.
|
40
|
+
collect { |output, cfg| [output, Path.convert_path_to_track(block: ctx[:block], **cfg.options)] }.
|
41
|
+
to_h
|
42
|
+
|
43
|
+
ctx[:non_symbol_options] = non_symbol_options.merge(new_tracks)
|
44
|
+
end
|
45
|
+
end # Normalizer
|
46
|
+
|
47
|
+
module_function
|
48
|
+
|
49
|
+
def convert_path_to_track(track_color: "track_#{rand}", connect_to: nil, before: false, block: nil, **options)
|
50
|
+
# DISCUSS: if anyone overrides `#step` in the "outer" activity, this won't be applied inside the branch.
|
51
|
+
|
52
|
+
# DISCUSS: use Path::Sequencer::Builder here instead?
|
53
|
+
path = Activity::Path(**options, track_name: track_color, &block)
|
54
|
+
|
55
|
+
seq = path.to_h[:sequence]
|
56
|
+
# Strip default ends `Start.default` and `End.success` (if present).
|
57
|
+
seq = seq[1..-1].reject{ |row| row[3][:stop_event] && row.id == 'End.success' }
|
58
|
+
|
59
|
+
if connect_to
|
60
|
+
seq = connect_for_sequence(seq, connect_to: connect_to)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Add the path elements before {End.success}.
|
64
|
+
# Termini (or :stop_event) are to be placed after {End.success}.
|
65
|
+
adds = seq.collect do |row|
|
66
|
+
options = row[3]
|
67
|
+
|
68
|
+
# the terminus of the path goes _after_ {End.success} into the "end group".
|
69
|
+
insert_method = options[:stop_event] ? Activity::Adds::Insert.method(:Append) : Activity::Adds::Insert.method(:Prepend)
|
70
|
+
|
71
|
+
insert_target = "End.success" # insert before/after
|
72
|
+
insert_target = before if before && connect_to.instance_of?(Trailblazer::Activity::DSL::Linear::Track) # FIXME: this is a bit hacky, of course!
|
73
|
+
|
74
|
+
{
|
75
|
+
row: row,
|
76
|
+
insert: [insert_method, insert_target]
|
77
|
+
}
|
78
|
+
end
|
79
|
+
|
80
|
+
# Connect the Output() => Track(path_track)
|
81
|
+
return Linear::Track.new(track_color, adds, {})
|
82
|
+
end
|
83
|
+
|
84
|
+
# Connect last row of the {sequence} to the given step via its {Id}
|
85
|
+
# Useful when steps needs to be inserted in between {Start} and {connect Id()}.
|
86
|
+
private def connect_for_sequence(sequence, connect_to:)
|
87
|
+
output, _ = sequence[-1][2][0].(sequence, sequence[-1]) # FIXME: the Forward() proc contains the row's Output, and the only current way to retrieve it is calling the search strategy. It should be Forward#to_h
|
88
|
+
|
89
|
+
# searches = [Search.ById(output, connect_to.value)]
|
90
|
+
searches = [Sequence::Search.ById(output, connect_to.value)] if connect_to.instance_of?(Trailblazer::Activity::DSL::Linear::Id)
|
91
|
+
searches = [Sequence::Search.Forward(output, connect_to.color)] if connect_to.instance_of?(Trailblazer::Activity::DSL::Linear::Track) # FIXME: use existing mapping logic!
|
92
|
+
|
93
|
+
row = sequence[-1]
|
94
|
+
row = row[0..1] + [searches] + [row[3]] # FIXME: not mutating an array is so hard: we only want to replace the "searches" element, index 2
|
95
|
+
row = Sequence::Row[*row]
|
96
|
+
|
97
|
+
sequence = sequence[0..-2] + [row]
|
98
|
+
|
99
|
+
sequence
|
100
|
+
end
|
101
|
+
end # Path
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -2,151 +2,77 @@ module Trailblazer
|
|
2
2
|
class Activity
|
3
3
|
module DSL
|
4
4
|
module Linear
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
5
|
+
# Data Structures used in the DSL. They're mostly created from helpers
|
6
|
+
# and then get processed in the normalizer.
|
7
|
+
#
|
8
|
+
# @private
|
9
|
+
OutputSemantic = Struct.new(:value)
|
10
|
+
Id = Struct.new(:value)
|
11
|
+
Track = Struct.new(:color, :adds, :options)
|
12
|
+
Extension = Struct.new(:callable) do
|
13
|
+
def call(*args, &block)
|
14
|
+
callable.(*args, &block)
|
14
15
|
end
|
16
|
+
end
|
17
|
+
PathBranch = Struct.new(:options)
|
18
|
+
DataVariableName = Class.new
|
15
19
|
|
16
|
-
|
17
|
-
|
20
|
+
# Shortcut functions for the DSL.
|
21
|
+
# Those are included in {Strategy} so they're available to all Strategy users such
|
22
|
+
# as {Railway} or {Operation}.
|
23
|
+
module Helper
|
24
|
+
# This is the namespace container for {Contract::}, {Policy::} and friends.
|
25
|
+
module Constants
|
18
26
|
end
|
19
27
|
|
20
|
-
#
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
def Output(signal, semantic=nil)
|
25
|
-
return OutputSemantic.new(signal) if semantic.nil?
|
26
|
-
|
27
|
-
Activity.Output(signal, semantic)
|
28
|
-
end
|
29
|
-
|
30
|
-
def End(semantic)
|
31
|
-
Activity.End(semantic)
|
32
|
-
end
|
33
|
-
|
34
|
-
def end_id(_end)
|
35
|
-
"End.#{_end.to_h[:semantic]}" # TODO: use everywhere
|
36
|
-
end
|
37
|
-
|
38
|
-
def Track(color, wrap_around: false)
|
39
|
-
Track.new(color, [], wrap_around: wrap_around).freeze
|
40
|
-
end
|
41
|
-
|
42
|
-
def Id(id)
|
43
|
-
Id.new(id).freeze
|
44
|
-
end
|
45
|
-
|
46
|
-
def Path(track_color: "track_#{rand}", connect_to: nil, before: false, **options, &block)
|
47
|
-
path = Activity::Path(track_name: track_color, **options)
|
48
|
-
activity = Class.new(path) { self.instance_exec(&block) }
|
49
|
-
|
50
|
-
seq = activity.instance_variable_get(:@state).to_h[:sequence] # TODO: fix @state interface
|
51
|
-
# Strip default ends `Start.default` and `End.success` (if present).
|
52
|
-
seq = seq[1..-1].reject{ |row| row[3][:stop_event] && row[3][:id] == 'End.success' }
|
53
|
-
|
54
|
-
if connect_to
|
55
|
-
seq = connect_for_sequence(seq, connect_to: connect_to)
|
56
|
-
end
|
57
|
-
|
58
|
-
# Add the path elements before {End.success}.
|
59
|
-
# Termini (or :stop_event) are to be placed after {End.success}.
|
60
|
-
adds = seq.collect do |row|
|
61
|
-
options = row[3]
|
62
|
-
|
63
|
-
# the terminus of the path goes _after_ {End.success} into the "end group".
|
64
|
-
insert_method = options[:stop_event] ? Insert.method(:Append) : Insert.method(:Prepend)
|
65
|
-
|
66
|
-
insert_target = "End.success" # insert before/after
|
67
|
-
insert_target = before if before && connect_to.instance_of?(Trailblazer::Activity::DSL::Linear::Helper::Track) # FIXME: this is a bit hacky, of course!
|
68
|
-
|
69
|
-
{
|
70
|
-
row: row,
|
71
|
-
insert: [insert_method, insert_target]
|
72
|
-
}
|
73
|
-
end
|
28
|
+
# Output( Left, :failure )
|
29
|
+
# Output( :failure ) #=> Output::Semantic
|
30
|
+
def Output(signal, semantic=nil)
|
31
|
+
return OutputSemantic.new(signal) if semantic.nil?
|
74
32
|
|
75
|
-
|
76
|
-
|
77
|
-
end
|
78
|
-
|
79
|
-
# Connect last row of the {sequence} to the given step via its {Id}
|
80
|
-
# Useful when steps needs to be inserted in between {Start} and {connect Id()}.
|
81
|
-
private def connect_for_sequence(sequence, connect_to:)
|
82
|
-
output, _ = sequence[-1][2][0].(sequence, sequence[-1]) # FIXME: the Forward() proc contains the row's Output, and the only current way to retrieve it is calling the search strategy. It should be Forward#to_h
|
83
|
-
|
84
|
-
# searches = [Search.ById(output, connect_to.value)]
|
85
|
-
searches = [Search.ById(output, connect_to.value)] if connect_to.instance_of?(Trailblazer::Activity::DSL::Linear::Helper::Id)
|
86
|
-
searches = [Search.Forward(output, connect_to.color)] if connect_to.instance_of?(Trailblazer::Activity::DSL::Linear::Helper::Track) # FIXME: use existing mapping logic!
|
87
|
-
|
88
|
-
row = sequence[-1]
|
89
|
-
row = row[0..1] + [searches] + [row[3]] # FIXME: not mutating an array is so hard: we only want to replace the "searches" element, index 2
|
90
|
-
|
91
|
-
sequence = sequence[0..-2] + [row]
|
92
|
-
|
93
|
-
sequence
|
94
|
-
end
|
95
|
-
|
96
|
-
# Computes the {:outputs} options for {activity}.
|
97
|
-
def Subprocess(activity, patch: {})
|
98
|
-
activity = Patch.customize(activity, options: patch)
|
33
|
+
Activity.Output(signal, semantic)
|
34
|
+
end
|
99
35
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
}
|
104
|
-
end
|
36
|
+
def End(semantic)
|
37
|
+
Activity.End(semantic)
|
38
|
+
end
|
105
39
|
|
106
|
-
|
107
|
-
|
40
|
+
def end_id(semantic:, **)
|
41
|
+
"End.#{semantic}" # TODO: use everywhere
|
42
|
+
end
|
108
43
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
options
|
44
|
+
def Track(color, wrap_around: false)
|
45
|
+
Track.new(color, [], wrap_around: wrap_around).freeze
|
46
|
+
end
|
113
47
|
|
114
|
-
|
115
|
-
|
116
|
-
|
48
|
+
def Id(id)
|
49
|
+
Id.new(id).freeze
|
50
|
+
end
|
117
51
|
|
118
|
-
|
119
|
-
|
52
|
+
def Path(**options, &block)
|
53
|
+
options = options.merge(block: block) if block_given?
|
120
54
|
|
121
|
-
|
122
|
-
|
55
|
+
Linear::PathBranch.new(options) # picked up by normalizer.
|
56
|
+
end
|
123
57
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
patched_segment_activity = call(segment_activity, path, customization)
|
58
|
+
# Computes the {:outputs} options for {activity}.
|
59
|
+
def Subprocess(activity, patch: {})
|
60
|
+
activity = Patch.customize(activity, options: patch)
|
128
61
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
62
|
+
{
|
63
|
+
task: activity,
|
64
|
+
outputs: Hash[activity.to_h[:outputs].collect { |output| [output.semantic, output] }]
|
65
|
+
}
|
66
|
+
end
|
134
67
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
end
|
139
|
-
end
|
68
|
+
def In(**kws); VariableMapping::DSL::In(**kws); end
|
69
|
+
def Out(**kws); VariableMapping::DSL::Out(**kws); end
|
70
|
+
def Inject(**kws); VariableMapping::DSL::Inject(**kws); end
|
140
71
|
|
141
|
-
|
142
|
-
|
143
|
-
foreign = options.reject { |key, value| local_keys.include?(key) }
|
144
|
-
return foreign, locals
|
145
|
-
end
|
72
|
+
def DataVariable
|
73
|
+
DataVariableName.new
|
146
74
|
end
|
147
75
|
end # Helper
|
148
|
-
|
149
|
-
include Helper # Introduce Helper constants in DSL::Linear scope
|
150
76
|
end # Linear
|
151
77
|
end # DSL
|
152
78
|
end # Activity
|