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.
Files changed (29) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +1 -1
  3. data/CHANGES.md +52 -0
  4. data/Gemfile +3 -1
  5. data/README.md +9 -17
  6. data/lib/trailblazer/activity/dsl/linear/feature/merge.rb +36 -0
  7. data/lib/trailblazer/activity/dsl/linear/feature/patch.rb +40 -0
  8. data/lib/trailblazer/activity/dsl/linear/feature/variable_mapping/dsl.rb +281 -0
  9. data/lib/trailblazer/activity/dsl/linear/feature/variable_mapping/inherit.rb +38 -0
  10. data/lib/trailblazer/activity/dsl/linear/feature/variable_mapping.rb +298 -0
  11. data/lib/trailblazer/activity/dsl/linear/helper/path.rb +106 -0
  12. data/lib/trailblazer/activity/dsl/linear/helper.rb +54 -128
  13. data/lib/trailblazer/activity/dsl/linear/normalizer/terminus.rb +92 -0
  14. data/lib/trailblazer/activity/dsl/linear/normalizer.rb +194 -77
  15. data/lib/trailblazer/activity/dsl/linear/sequence/builder.rb +47 -0
  16. data/lib/trailblazer/activity/dsl/linear/sequence/compiler.rb +72 -0
  17. data/lib/trailblazer/activity/dsl/linear/sequence/search.rb +58 -0
  18. data/lib/trailblazer/activity/dsl/linear/sequence.rb +34 -0
  19. data/lib/trailblazer/activity/dsl/linear/strategy.rb +116 -71
  20. data/lib/trailblazer/activity/dsl/linear/version.rb +1 -1
  21. data/lib/trailblazer/activity/dsl/linear.rb +21 -177
  22. data/lib/trailblazer/activity/fast_track.rb +42 -53
  23. data/lib/trailblazer/activity/path.rb +52 -127
  24. data/lib/trailblazer/activity/railway.rb +48 -68
  25. data/trailblazer-activity-dsl-linear.gemspec +3 -2
  26. metadata +41 -13
  27. data/lib/trailblazer/activity/dsl/linear/compiler.rb +0 -70
  28. data/lib/trailblazer/activity/dsl/linear/state.rb +0 -63
  29. 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
- module Helper
6
- # @api private
7
- OutputSemantic = Struct.new(:value)
8
- Id = Struct.new(:value)
9
- Track = Struct.new(:color, :adds, :options)
10
- Extension = Struct.new(:callable) do
11
- def call(*args, &block)
12
- callable.(*args, &block)
13
- end
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
- def self.included(base)
17
- base.extend ClassMethods
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
- # Shortcut functions for the DSL.
21
- module ClassMethods
22
- # Output( Left, :failure )
23
- # Output( :failure ) #=> Output::Semantic
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
- # Connect the Output() => Track(path_track)
76
- return Track.new(track_color, adds, {})
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
- task: activity,
102
- outputs: Hash[activity.to_h[:outputs].collect { |output| [output.semantic, output] }]
103
- }
104
- end
36
+ def End(semantic)
37
+ Activity.End(semantic)
38
+ end
105
39
 
106
- module Patch
107
- module_function
40
+ def end_id(semantic:, **)
41
+ "End.#{semantic}" # TODO: use everywhere
42
+ end
108
43
 
109
- def customize(activity, options:)
110
- options = options.is_a?(Proc) ?
111
- { [] => options } : # hash-wrapping with empty path, for patching given activity itself
112
- options
44
+ def Track(color, wrap_around: false)
45
+ Track.new(color, [], wrap_around: wrap_around).freeze
46
+ end
113
47
 
114
- options.each do |path, patch|
115
- activity = call(activity, path, patch) # TODO: test if multiple patches works!
116
- end
48
+ def Id(id)
49
+ Id.new(id).freeze
50
+ end
117
51
 
118
- activity
119
- end
52
+ def Path(**options, &block)
53
+ options = options.merge(block: block) if block_given?
120
54
 
121
- def call(activity, path, customization)
122
- task_id, *path = path
55
+ Linear::PathBranch.new(options) # picked up by normalizer.
56
+ end
123
57
 
124
- patch =
125
- if task_id
126
- segment_activity = Introspect::Graph(activity).find(task_id).task
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
- # Replace the patched subprocess.
130
- -> { step Subprocess(patched_segment_activity), inherit: true, replace: task_id, id: task_id }
131
- else
132
- customization # apply the *actual* patch from the Subprocess() call.
133
- end
62
+ {
63
+ task: activity,
64
+ outputs: Hash[activity.to_h[:outputs].collect { |output| [output.semantic, output] }]
65
+ }
66
+ end
134
67
 
135
- patched_activity = Class.new(activity)
136
- patched_activity.class_exec(&patch)
137
- patched_activity
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
- def normalize(options, local_keys) # TODO: test me.
142
- locals = options.reject { |key, value| ! local_keys.include?(key) }
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