trailblazer-activity-dsl-linear 1.0.0 → 1.2.0

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 +2 -3
  3. data/CHANGES.md +100 -0
  4. data/Gemfile +7 -4
  5. data/Rakefile +1 -1
  6. data/lib/trailblazer/activity/dsl/linear/feature/merge.rb +2 -2
  7. data/lib/trailblazer/activity/dsl/linear/feature/patch.rb +9 -5
  8. data/lib/trailblazer/activity/dsl/linear/feature/variable_mapping/dsl.rb +241 -156
  9. data/lib/trailblazer/activity/dsl/linear/feature/variable_mapping/runtime.rb +276 -0
  10. data/lib/trailblazer/activity/dsl/linear/feature/variable_mapping.rb +70 -226
  11. data/lib/trailblazer/activity/dsl/linear/helper/path.rb +37 -18
  12. data/lib/trailblazer/activity/dsl/linear/helper.rb +38 -17
  13. data/lib/trailblazer/activity/dsl/linear/normalizer/extensions.rb +63 -0
  14. data/lib/trailblazer/activity/dsl/linear/normalizer/inherit.rb +90 -0
  15. data/lib/trailblazer/activity/dsl/linear/normalizer/output_tuples.rb +160 -0
  16. data/lib/trailblazer/activity/dsl/linear/normalizer/terminus.rb +26 -29
  17. data/lib/trailblazer/activity/dsl/linear/normalizer.rb +99 -160
  18. data/lib/trailblazer/activity/dsl/linear/sequence/builder.rb +3 -2
  19. data/lib/trailblazer/activity/dsl/linear/sequence/compiler.rb +21 -17
  20. data/lib/trailblazer/activity/dsl/linear/sequence/search.rb +2 -8
  21. data/lib/trailblazer/activity/dsl/linear/strategy.rb +56 -17
  22. data/lib/trailblazer/activity/dsl/linear/version.rb +1 -1
  23. data/lib/trailblazer/activity/dsl/linear.rb +13 -1
  24. data/lib/trailblazer/activity/fast_track.rb +96 -67
  25. data/lib/trailblazer/activity/path.rb +35 -53
  26. data/lib/trailblazer/activity/railway.rb +63 -65
  27. data/trailblazer-activity-dsl-linear.gemspec +8 -8
  28. metadata +27 -18
  29. data/lib/trailblazer/activity/dsl/linear/feature/variable_mapping/inherit.rb +0 -38
@@ -0,0 +1,276 @@
1
+ module Trailblazer
2
+ class Activity
3
+ module DSL::Linear
4
+ module VariableMapping
5
+ module_function
6
+
7
+ # Runtime classes
8
+
9
+ # These objects are created via the DSL, keep all i/o steps in a Pipeline
10
+ # and run the latter when being `call`ed.
11
+ module Pipe
12
+ class Input
13
+ def initialize(pipe, id: :vm_original_ctx)
14
+ @pipe = pipe
15
+ @id = id
16
+ end
17
+
18
+ def call(wrap_ctx, original_args)
19
+ (original_ctx, original_flow_options), original_circuit_options = original_args
20
+
21
+ # let user compute new ctx for the wrapped task.
22
+ pipe_ctx, _ = @pipe.({original_ctx: original_ctx, aggregate: {}}, original_args)
23
+ ctx_from_input = pipe_ctx[:input_ctx]
24
+
25
+ wrap_ctx = wrap_ctx.merge(@id => original_ctx) # remember the original ctx under the key {@id}.
26
+
27
+ # instead of the original Context, pass on the filtered `ctx_from_input` in the wrap.
28
+ return wrap_ctx, [[ctx_from_input, original_flow_options], original_circuit_options]
29
+ end
30
+ end
31
+
32
+ # API in VariableMapping::Output:
33
+ # output_ctx = @filter.(returned_ctx, [original_ctx, returned_flow_options], **original_circuit_options)
34
+ # Returns {output_ctx} that is used after taskWrap finished.
35
+ class Output < Input
36
+ def call(wrap_ctx, original_args)
37
+ returned_ctx, returned_flow_options = wrap_ctx[:return_args] # this is the Context returned from {call}ing the wrapped user task.
38
+ original_ctx = wrap_ctx[@id] # grab the original ctx from before which was set in the {:input} filter.
39
+ _, original_circuit_options = original_args
40
+
41
+ # let user compute the output.
42
+ pipe_ctx, _ = @pipe.({original_ctx: original_ctx, returned_ctx: returned_ctx, aggregate: {}}, [[original_ctx, returned_flow_options], original_circuit_options])
43
+ ctx_from_output = pipe_ctx[:aggregate]
44
+
45
+ wrap_ctx = wrap_ctx.merge(return_args: [ctx_from_output, returned_flow_options]) # DISCUSS: this won't allow tracing in the taskWrap as we're returning {returned_flow_options} from above.
46
+
47
+ return wrap_ctx, original_args
48
+ end
49
+ end
50
+ end
51
+
52
+ # Merge all original ctx variables into the new input_ctx.
53
+ # This happens when no {:input} is provided.
54
+ def default_input_ctx(wrap_ctx, original_args)
55
+ default_ctx = wrap_ctx[:original_ctx]
56
+
57
+ merge_variables(default_ctx, wrap_ctx, original_args)
58
+ end
59
+
60
+ # Write one particular variable to the {aggregate} using {aggregate[:name] = (value)}.
61
+ #
62
+ # This is much faster than merging a hash, and provides better overriding semantics. (to be done!)
63
+ #
64
+ # @param filter Any circuit-step compatible callable that exposes {#call(args, **circuit_options)}
65
+ # and returns [value, new_ctx]
66
+ #
67
+
68
+ # Filter
69
+ class VariableFromCtx
70
+ def initialize(variable_name:)
71
+ @variable_name = variable_name
72
+ end
73
+
74
+ # Grab @variable_name from {ctx}.
75
+ def call((ctx, _), **) # Circuit-step interface
76
+ return ctx[@variable_name], ctx
77
+ end
78
+ end
79
+
80
+ # Filter
81
+ class VariablePresent < VariableFromCtx
82
+ # Grab @variable_name from {ctx} if it's there.
83
+ def call((ctx, _), **) # Circuit-step interface
84
+ return ctx.key?(@variable_name), ctx
85
+ end
86
+ end
87
+
88
+ # TODO: * ALL FILTERS and conditions expose circuit-step interface.
89
+ # @param name Identifier for the pipeline
90
+ # Call {user_filter} and set return value as variable on aggregate.
91
+ class SetVariable
92
+ def initialize(write_name:, filter:, user_filter:, name:, **)
93
+ @write_name = write_name
94
+ @filter = filter
95
+ @name = name
96
+ end
97
+
98
+ attr_reader :name # TODO: used when adding to pipeline, change to to_h
99
+
100
+ def call(wrap_ctx, original_args, filter = @filter)
101
+ wrap_ctx = self.class.set_variable_for_filter(filter, @write_name, wrap_ctx, original_args)
102
+
103
+ return wrap_ctx, original_args
104
+ end
105
+
106
+ def self.set_variable_for_filter(filter, write_name, wrap_ctx, original_args)
107
+ value = call_filter(filter, wrap_ctx, original_args)
108
+ wrap_ctx = set_variable(value, write_name, wrap_ctx, original_args)
109
+
110
+ wrap_ctx
111
+ end
112
+
113
+ # Call a filter with a Circuit-Step interface.
114
+ def self.call_filter(filter, wrap_ctx, (args, circuit_options))
115
+ value, _ = filter.(args, **circuit_options) # circuit-step interface
116
+ value
117
+ end
118
+
119
+ def self.set_variable(value, write_name, wrap_ctx, original_args)
120
+ wrap_ctx[:aggregate][write_name] = value # yes, we're mutating, but this is ok as we're on some private hash.
121
+ wrap_ctx # DISCUSS: could be omitted.
122
+ end
123
+
124
+ # Set variable on ctx if {condition} is true.
125
+ class Conditioned < SetVariable
126
+ def initialize(condition:, **options)
127
+ super(**options)
128
+
129
+ @condition = condition # DISCUSS: adding this as an "optional" step in a "Railway"
130
+ end
131
+
132
+ def call(wrap_ctx, original_args)
133
+ decision, _ = SetVariable.call_filter(@condition, wrap_ctx, original_args)
134
+
135
+ return super if decision
136
+ return wrap_ctx, original_args
137
+ end
138
+ end
139
+
140
+ # Set variable on ctx if {condition} is true.
141
+ # Otherwise, set default_filter variable on ctx.
142
+ class Default < SetVariable
143
+ def initialize(default_filter:, condition:, **options)
144
+ super(**options)
145
+
146
+ @default_filter = default_filter
147
+ @condition = condition
148
+ end
149
+
150
+ def call(wrap_ctx, original_args)
151
+ # FIXME: redundant with Conditioned.
152
+ decision, _ = SetVariable.call_filter(@condition, wrap_ctx, original_args)
153
+
154
+ filter = decision ? @filter : @default_filter
155
+
156
+ super(wrap_ctx, original_args, filter)
157
+ end
158
+ end # Default
159
+
160
+ # TODO: we don't have Out(:variable), yet!
161
+ class Output < SetVariable
162
+ # Call a filter with a Circuit-Step interface.
163
+ def self.call_filter(filter, wrap_ctx, original_args)
164
+ new_ctx = wrap_ctx[:returned_ctx]
165
+
166
+ call_filter_with_ctx(filter, new_ctx, wrap_ctx, original_args)
167
+ end
168
+
169
+ def self.call_filter_with_ctx(filter, ctx, wrap_ctx, ((_, flow_options), circuit_options))
170
+ SetVariable.call_filter(filter, wrap_ctx, [[ctx, flow_options], circuit_options])
171
+ end
172
+ end
173
+
174
+ # Do everything SetVariable does but read from {aggregate}, not from {ctx}.
175
+ # TODO: it would be cool to have this also for AddVariables.
176
+ class ReadFromAggregate < SetVariable
177
+ def self.call_filter(filter, wrap_ctx, original_args)
178
+ new_ctx = wrap_ctx[:aggregate]
179
+
180
+ Output.call_filter_with_ctx(filter, new_ctx, wrap_ctx, original_args)
181
+ end
182
+ end
183
+
184
+ # @private
185
+ # Always deletes from {:aggregate}.
186
+ class Delete < SetVariable
187
+ def call(wrap_ctx, original_args)
188
+ wrap_ctx[:aggregate].delete(@write_name) # FIXME: we're mutating a hash here!
189
+
190
+ return wrap_ctx, original_args
191
+ end
192
+ end
193
+ end # SetVariable
194
+
195
+ # AddVariables: I call something with an Option-interface and run the return value through merge_variables().
196
+ # works on {:aggregate} by (usually) producing a hash fragment that is merged with the existing {:aggregate}
197
+
198
+ # Add a hash of variables to aggregate after running a filter (which returns a hash!).
199
+ # Note that we only use those for "old-style" callables that produce hashes.
200
+ class AddVariables < SetVariable
201
+ def self.set_variable(variables, write_name, wrap_ctx, original_args)
202
+ wrap_ctx, _ = VariableMapping.merge_variables(variables, wrap_ctx, original_args)
203
+ wrap_ctx
204
+ end
205
+
206
+ # Merge hash of Out into aggregate.
207
+ # TODO: deprecate and remove.
208
+ class Output < SetVariable::Output
209
+ def self.set_variable(*args)
210
+ AddVariables.set_variable(*args)
211
+ end
212
+
213
+ # Pass {inner_ctx, outer_ctx, **inner_ctx}
214
+ class WithOuterContext_Deprecated < Output
215
+ def self.call_filter(filter, wrap_ctx, ((original_ctx, _), circuit_options))
216
+ new_ctx = wrap_ctx[:returned_ctx] # FIXME: redundant.
217
+
218
+ # Here, due to a stupid API decision, we have to call an Option with two positional args.
219
+ filter.(new_ctx, original_ctx, keyword_arguments: new_ctx.to_hash, **circuit_options)
220
+ end
221
+ end
222
+
223
+ class WithOuterContext < Output
224
+ def self.call_filter(filter, wrap_ctx, ((original_ctx, flow_options), circuit_options))
225
+ new_ctx = wrap_ctx[:returned_ctx]
226
+ new_ctx = new_ctx.merge(outer_ctx: original_ctx)
227
+
228
+ Output.call_filter_with_ctx(filter, new_ctx, wrap_ctx, [[original_ctx, flow_options], circuit_options])
229
+ end
230
+ end
231
+ end
232
+ end
233
+
234
+ def merge_variables(variables, wrap_ctx, original_args, receiver = wrap_ctx[:aggregate])
235
+ wrap_ctx[:aggregate] = receiver.merge(variables)
236
+
237
+ return wrap_ctx, original_args
238
+ end
239
+
240
+ # Finally, create a new input ctx from all the
241
+ # collected input variables.
242
+ # This goes into the step/nested OP.
243
+ def scope(wrap_ctx, original_args)
244
+ ((_, flow_options), _) = original_args
245
+
246
+ # this is the actual context passed into the step.
247
+ wrap_ctx[:input_ctx] = Trailblazer::Context(
248
+ wrap_ctx[:aggregate],
249
+ {}, # mutable variables
250
+ flow_options[:context_options]
251
+ )
252
+
253
+ return wrap_ctx, original_args
254
+ end
255
+
256
+ # @private
257
+ # The default {:output} filter only returns the "mutable" part of the inner ctx.
258
+ # This means only variables added using {inner_ctx[..]=} are merged on the outside.
259
+ def default_output_ctx(wrap_ctx, original_args)
260
+ new_ctx = wrap_ctx[:returned_ctx]
261
+
262
+ _wrapped, mutable = new_ctx.decompose # `_wrapped` is what the `:input` filter returned, `mutable` is what the task wrote to `scoped`.
263
+
264
+ merge_variables(mutable, wrap_ctx, original_args)
265
+ end
266
+
267
+ def merge_with_original(wrap_ctx, original_args)
268
+ original_ctx = wrap_ctx[:original_ctx] # outer ctx
269
+ output_variables = wrap_ctx[:aggregate]
270
+
271
+ merge_variables(output_variables, wrap_ctx, original_args, original_ctx)
272
+ end
273
+ end # VariableMapping
274
+ end
275
+ end
276
+ end
@@ -5,11 +5,10 @@ module Trailblazer
5
5
  # Normalizer-steps to implement {:input} and {:output}
6
6
  # Returns an Extension instance to be thrown into the `step` DSL arguments.
7
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)
8
+ input, output = VariableMapping.merge_instructions_from_dsl(**options)
9
+ extension = VariableMapping.Extension(input, output)
9
10
 
10
- extension = VariableMapping.Extension(input, output)
11
-
12
- return TaskWrap::Extension::WrapStatic.new(extension: extension), normalizer_options, non_symbol_options
11
+ TaskWrap::Extension::WrapStatic.new(extension: extension)
13
12
  end
14
13
 
15
14
  module VariableMapping
@@ -20,9 +19,10 @@ module Trailblazer
20
19
  normalizer,
21
20
  "activity.wirings",
22
21
  {
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)),
22
+ # In(), Out(), {:input}, Inject() feature
23
+ "activity.convert_symbol_options" => Linear::Normalizer.Task(VariableMapping::Normalizer.method(:convert_symbol_options)),
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
26
  }
27
27
  )
28
28
  end
@@ -37,259 +37,103 @@ module Trailblazer
37
37
 
38
38
  # Steps that are added to the DSL normalizer.
39
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)
40
+ # TODO: remove me once {:input} API is removed.
41
+ # Convert {:input}, {:output} and {:inject} to In() and friends.
42
+ def self.convert_symbol_options(ctx, non_symbol_options:, output_with_outer_ctx: nil, **)
43
+ input, output, inject = ctx.delete(:input), ctx.delete(:output), ctx.delete(:inject)
44
+ return unless input || output || inject
105
45
 
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
46
+ dsl_options = {}
118
47
 
119
- # < AddVariables
120
- # Option
121
- # filter
122
- # merge_variables
48
+ # TODO: warn, deprecate etc
49
+ dsl_options.merge!(VariableMapping::DSL.In() => input) if input
123
50
 
124
- # Runtime classes
125
- Filter = Struct.new(:aggregate_step, :filter, :name, :add_variables_class)
51
+ if output
52
+ options = {}
53
+ options = options.merge(with_outer_ctx: output_with_outer_ctx) unless output_with_outer_ctx.nil?
126
54
 
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.
55
+ dsl_options.merge!(VariableMapping::DSL.Out(**options) => output)
134
56
  end
135
57
 
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}.
58
+ if inject
59
+ inject.collect do |filter|
60
+ filter = filter.is_a?(Symbol) ? [filter] : filter
144
61
 
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]
62
+ dsl_options.merge!(VariableMapping::DSL.Inject() => filter)
63
+ end
147
64
  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
65
 
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
66
+ ctx.merge!(
67
+ non_symbol_options: non_symbol_options.merge(dsl_options),
68
+ input_output_inject_options: [{input: input, output: output, inject: inject}, dsl_options], # yes, there were {:input} options.
69
+ )
169
70
  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
71
 
176
- return wrap_ctx, original_args
177
- end
72
+ # Process {In() => [:model], Inject() => [:current_user], Out() => [:model]}
73
+ def self.normalize_input_output_filters(ctx, non_symbol_options:, input_output_inject_options: [], **)
74
+ in_exts = non_symbol_options.find_all { |k, v| k.is_a?(VariableMapping::DSL::In) || k.is_a?(VariableMapping::DSL::Inject) }
75
+ output_exts = non_symbol_options.find_all { |k, v| k.is_a?(VariableMapping::DSL::Out) }
178
76
 
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]
77
+ return unless in_exts.any? || output_exts.any?
183
78
 
184
- merge_variables(default_ctx, wrap_ctx, original_args)
185
- end
79
+ deprecate_input_output_inject_option(input_output_inject_options, in_exts, output_exts)
186
80
 
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.
81
+ ctx[:in_filters] = in_exts
82
+ ctx[:out_filters] = output_exts
198
83
  end
199
84
 
200
- def call(wrap_ctx, original_args)
201
- ((original_ctx, _), circuit_options) = original_args
202
- # puts "@@@@@ #{wrap_ctx[:returned_ctx].inspect}"
85
+ def self.input_output_dsl(ctx, non_symbol_options:, in_filters: nil, out_filters: nil, **options)
86
+ # no :input/:output/:inject/Input()/Output() passed.
87
+ return unless in_filters || out_filters
203
88
 
204
- # this is the actual logic.
205
- variables = call_filter(wrap_ctx, original_ctx, circuit_options, original_args)
89
+ extension = Linear.VariableMapping(in_filters: in_filters, out_filters: out_filters, **options)
206
90
 
207
- VariableMapping.merge_variables(variables, wrap_ctx, original_args)
208
- end
91
+ record = Linear::Normalizer::Inherit.Record((in_filters + out_filters).to_h, type: :variable_mapping)
209
92
 
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
93
+ non_symbol_options = non_symbol_options.merge(record)
94
+ non_symbol_options = non_symbol_options.merge(Linear::Strategy.Extension(is_generic: true) => extension)
213
95
 
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
96
+ ctx.merge!(
97
+ non_symbol_options: non_symbol_options
98
+ )
220
99
  end
221
100
 
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
101
+ # TODO: remove for TRB 2.2.
102
+ def self.deprecate_input_output_inject_option(input_output_inject_options, *composable_options)
103
+ return unless input_output_inject_options.any?
104
+ options, _dsl_options = input_output_inject_options
228
105
 
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
106
+ deprecated_options_count = options.find_all { |(name, option)| option }.count + (options[:inject] ? options[:inject].count - 1 : 0)
107
+ composable_options_count = composable_options.collect { |options| options.size }.sum
237
108
 
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
109
+ return if composable_options_count == deprecated_options_count
244
110
 
245
- return wrap_ctx, original_args
246
- end
247
- end
111
+ Activity::Deprecate.warn Linear::Deprecate.dsl_caller_location, %(You are mixing #{options.inspect} with In(), Out() and Inject().\n#{VariableMapping.deprecation_link})
248
112
  end
249
113
  end
250
114
 
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
115
+ module_function
274
116
 
117
+ # For the input filter we
118
+ # 1. create a separate {Pipeline} instance {pipe}. Depending on the user's options, this might have up to four steps.
119
+ # 2. The {pipe} is run in a lamdba {input}, the lambda returns the pipe's ctx[:input_ctx].
120
+ # 3. The {input} filter in turn is wrapped into an {Activity::TaskWrap::Input} object via {#merge_instructions_for}.
121
+ # 4. The {TaskWrap::Input} instance is then finally placed into the taskWrap as {"task_wrap.input"}.
122
+ #
275
123
  # @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]
124
+ #
125
+ def merge_instructions_from_dsl(**options)
126
+ pipeline = DSL.pipe_for_composable_input(**options) # FIXME: rename filters consistently
127
+ input = Pipe::Input.new(pipeline)
280
128
 
281
- _wrapped, mutable = new_ctx.decompose # `_wrapped` is what the `:input` filter returned, `mutable` is what the task wrote to `scoped`.
129
+ output_pipeline = DSL.pipe_for_composable_output(**options)
130
+ output = Pipe::Output.new(output_pipeline)
282
131
 
283
- merge_variables(mutable, wrap_ctx, original_args)
132
+ return input, output
284
133
  end
285
134
 
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
135
+ def deprecation_link
136
+ %(Please refer to https://trailblazer.to/2.1/docs/activity.html#activity-variable-mapping-deprecation-notes and have a nice day.)
293
137
  end
294
138
  end # VariableMapping
295
139
  end