trailblazer-activity-dsl-linear 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +2 -3
- data/CHANGES.md +100 -0
- data/Gemfile +7 -4
- data/Rakefile +1 -1
- data/lib/trailblazer/activity/dsl/linear/feature/merge.rb +2 -2
- data/lib/trailblazer/activity/dsl/linear/feature/patch.rb +9 -5
- data/lib/trailblazer/activity/dsl/linear/feature/variable_mapping/dsl.rb +241 -156
- data/lib/trailblazer/activity/dsl/linear/feature/variable_mapping/runtime.rb +276 -0
- data/lib/trailblazer/activity/dsl/linear/feature/variable_mapping.rb +70 -226
- data/lib/trailblazer/activity/dsl/linear/helper/path.rb +37 -18
- data/lib/trailblazer/activity/dsl/linear/helper.rb +38 -17
- data/lib/trailblazer/activity/dsl/linear/normalizer/extensions.rb +63 -0
- data/lib/trailblazer/activity/dsl/linear/normalizer/inherit.rb +90 -0
- data/lib/trailblazer/activity/dsl/linear/normalizer/output_tuples.rb +160 -0
- data/lib/trailblazer/activity/dsl/linear/normalizer/terminus.rb +26 -29
- data/lib/trailblazer/activity/dsl/linear/normalizer.rb +99 -160
- data/lib/trailblazer/activity/dsl/linear/sequence/builder.rb +3 -2
- data/lib/trailblazer/activity/dsl/linear/sequence/compiler.rb +21 -17
- data/lib/trailblazer/activity/dsl/linear/sequence/search.rb +2 -8
- data/lib/trailblazer/activity/dsl/linear/strategy.rb +56 -17
- data/lib/trailblazer/activity/dsl/linear/version.rb +1 -1
- data/lib/trailblazer/activity/dsl/linear.rb +13 -1
- data/lib/trailblazer/activity/fast_track.rb +96 -67
- data/lib/trailblazer/activity/path.rb +35 -53
- data/lib/trailblazer/activity/railway.rb +63 -65
- data/trailblazer-activity-dsl-linear.gemspec +8 -8
- metadata +27 -18
- 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
|
8
|
+
input, output = VariableMapping.merge_instructions_from_dsl(**options)
|
9
|
+
extension = VariableMapping.Extension(input, output)
|
9
10
|
|
10
|
-
|
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
|
-
|
24
|
-
"activity.
|
25
|
-
"activity.
|
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
|
-
#
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
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
|
-
|
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
|
-
#
|
120
|
-
|
121
|
-
# filter
|
122
|
-
# merge_variables
|
48
|
+
# TODO: warn, deprecate etc
|
49
|
+
dsl_options.merge!(VariableMapping::DSL.In() => input) if input
|
123
50
|
|
124
|
-
|
125
|
-
|
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
|
-
|
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
|
-
|
137
|
-
|
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
|
-
|
146
|
-
|
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
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
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
|
-
|
177
|
-
|
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
|
-
|
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
|
-
|
185
|
-
end
|
79
|
+
deprecate_input_output_inject_option(input_output_inject_options, in_exts, output_exts)
|
186
80
|
|
187
|
-
|
188
|
-
|
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
|
201
|
-
((
|
202
|
-
|
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
|
-
|
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
|
-
|
208
|
-
end
|
91
|
+
record = Linear::Normalizer::Inherit.Record((in_filters + out_filters).to_h, type: :variable_mapping)
|
209
92
|
|
210
|
-
|
211
|
-
|
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
|
-
|
215
|
-
|
216
|
-
|
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
|
-
|
223
|
-
|
224
|
-
|
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
|
-
|
230
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
#
|
277
|
-
|
278
|
-
|
279
|
-
|
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
|
-
|
129
|
+
output_pipeline = DSL.pipe_for_composable_output(**options)
|
130
|
+
output = Pipe::Output.new(output_pipeline)
|
282
131
|
|
283
|
-
|
132
|
+
return input, output
|
284
133
|
end
|
285
134
|
|
286
|
-
def
|
287
|
-
|
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
|