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
@@ -1,63 +0,0 @@
|
|
1
|
-
module Trailblazer
|
2
|
-
class Activity
|
3
|
-
module DSL
|
4
|
-
module Linear
|
5
|
-
# A {State} instance is kept per DSL client, which usually is a subclass of {Path}, {Railway}, etc.
|
6
|
-
# State doesn't have any immutable features - all write operations to it must guarantee they only replace
|
7
|
-
# instance variables.
|
8
|
-
#
|
9
|
-
# @private
|
10
|
-
#
|
11
|
-
# DISCUSS: why do we have this structure? It doesn't cover "immutable copying", that has to be done by its clients.
|
12
|
-
# also, copy with to_h
|
13
|
-
class State
|
14
|
-
# remembers how to call normalizers (e.g. track_color), TaskBuilder
|
15
|
-
# remembers sequence
|
16
|
-
def initialize(normalizers:, initial_sequence:, fields: {}.freeze, **normalizer_options)
|
17
|
-
@normalizer = normalizers # compiled normalizers.
|
18
|
-
@sequence = initial_sequence
|
19
|
-
@normalizer_options = normalizer_options
|
20
|
-
@fields = fields
|
21
|
-
end
|
22
|
-
|
23
|
-
# Called to "inherit" a state.
|
24
|
-
def copy
|
25
|
-
self.class.new(normalizers: @normalizer, initial_sequence: @sequence, fields: @fields, **@normalizer_options)
|
26
|
-
end
|
27
|
-
|
28
|
-
def to_h
|
29
|
-
{sequence: @sequence, normalizers: @normalizer, normalizer_options: @normalizer_options, fields: @fields} # FIXME.
|
30
|
-
end
|
31
|
-
|
32
|
-
def update_sequence(&block)
|
33
|
-
@sequence = yield(**to_h)
|
34
|
-
end
|
35
|
-
|
36
|
-
def update_options(fields)
|
37
|
-
@fields = fields
|
38
|
-
end
|
39
|
-
|
40
|
-
# Compiles and maintains all final normalizers for a specific DSL.
|
41
|
-
class Normalizer
|
42
|
-
# [gets instantiated at compile time.]
|
43
|
-
#
|
44
|
-
# We simply compile the activities that represent the normalizers for #step, #pass, etc.
|
45
|
-
# This can happen at compile-time, as normalizers are stateless.
|
46
|
-
def initialize(normalizer_sequences)
|
47
|
-
@normalizers = normalizer_sequences
|
48
|
-
end
|
49
|
-
|
50
|
-
# Execute the specific normalizer (step, fail, pass) for a particular option set provided
|
51
|
-
# by the DSL user. This is usually when you call Operation::step.
|
52
|
-
def call(name, ctx)
|
53
|
-
normalizer = @normalizers.fetch(name)
|
54
|
-
wrap_ctx, _ = normalizer.(ctx, nil)
|
55
|
-
wrap_ctx
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end # State
|
59
|
-
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
@@ -1,240 +0,0 @@
|
|
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: nil, output: VariableMapping.default_output, output_with_outer_ctx: false, inject: [])
|
8
|
-
merge_instructions = VariableMapping.merge_instructions_from_dsl(input: input, output: output, output_with_outer_ctx: output_with_outer_ctx, inject: inject)
|
9
|
-
|
10
|
-
TaskWrap::Extension(merge: merge_instructions)
|
11
|
-
end
|
12
|
-
|
13
|
-
module VariableMapping
|
14
|
-
module_function
|
15
|
-
|
16
|
-
# For the input filter we
|
17
|
-
# 1. create a separate {Pipeline} instance {pipe}. Depending on the user's options, this might have up to four steps.
|
18
|
-
# 2. The {pipe} is run in a lamdba {input}, the lambda returns the pipe's ctx[:input_ctx].
|
19
|
-
# 3. The {input} filter in turn is wrapped into an {Activity::TaskWrap::Input} object via {#merge_instructions_for}.
|
20
|
-
# 4. The {TaskWrap::Input} instance is then finally placed into the taskWrap as {"task_wrap.input"}.
|
21
|
-
#
|
22
|
-
# @private
|
23
|
-
def merge_instructions_from_dsl(input:, output:, output_with_outer_ctx:, inject:)
|
24
|
-
# FIXME: this could (should?) be in Normalizer?
|
25
|
-
inject_passthrough = inject.find_all { |name| name.is_a?(Symbol) }
|
26
|
-
inject_with_default = inject.find { |name| name.is_a?(Hash) } # FIXME: we only support one default hash in the DSL so far.
|
27
|
-
|
28
|
-
input_steps = [
|
29
|
-
["input.init_hash", VariableMapping.method(:initial_input_hash)],
|
30
|
-
]
|
31
|
-
|
32
|
-
# With only injections defined, we do not filter out anything, we use the original ctx
|
33
|
-
# and _add_ defaulting for injected variables.
|
34
|
-
if !input # only injections defined
|
35
|
-
input_steps << ["input.default_input", VariableMapping.method(:default_input_ctx)]
|
36
|
-
end
|
37
|
-
|
38
|
-
if input # :input or :input/:inject
|
39
|
-
input_steps << ["input.add_variables", VariableMapping.method(:add_variables)]
|
40
|
-
|
41
|
-
input_filter = Trailblazer::Option(VariableMapping::filter_for(input))
|
42
|
-
end
|
43
|
-
|
44
|
-
if inject_passthrough || inject_with_default
|
45
|
-
input_steps << ["input.add_injections", VariableMapping.method(:add_injections)] # we now allow one filter per injected variable.
|
46
|
-
end
|
47
|
-
|
48
|
-
if inject_passthrough || inject_with_default
|
49
|
-
injections = inject.collect do |name|
|
50
|
-
if name.is_a?(Symbol)
|
51
|
-
[[name, Trailblazer::Option(->(*) { [false, name] })]] # we don't want defaulting, this return value signalizes "please pass-through, only".
|
52
|
-
else # we automatically assume this is a hash of callables
|
53
|
-
name.collect do |_name, filter|
|
54
|
-
[_name, Trailblazer::Option(->(ctx, **kws) { [true, _name, filter.(ctx, **kws)] })] # filter will compute the default value
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end.flatten(1).to_h
|
58
|
-
end
|
59
|
-
|
60
|
-
input_steps << ["input.scope", VariableMapping.method(:scope)]
|
61
|
-
|
62
|
-
|
63
|
-
pipe = Activity::TaskWrap::Pipeline.new(input_steps)
|
64
|
-
|
65
|
-
# gets wrapped by {VariableMapping::Input} and called there.
|
66
|
-
# API: @filter.([ctx, original_flow_options], **original_circuit_options)
|
67
|
-
# input = Trailblazer::Option(->(original_ctx, **) { })
|
68
|
-
input = ->((ctx, flow_options), **circuit_options) do # This filter is called by {TaskWrap::Input#call} in the {activity} gem.
|
69
|
-
wrap_ctx, _ = pipe.({injections: injections, input_filter: input_filter}, [[ctx, flow_options], circuit_options])
|
70
|
-
|
71
|
-
wrap_ctx[:input_ctx]
|
72
|
-
end
|
73
|
-
|
74
|
-
# 1. {} empty input hash
|
75
|
-
# 1. input # dynamic => hash
|
76
|
-
# 2. input_map => hash
|
77
|
-
# 3. inject => hash
|
78
|
-
# 4. Input::Scoped()
|
79
|
-
|
80
|
-
unscope_class = output_with_outer_ctx ? VariableMapping::Output::Unscoped::WithOuterContext : VariableMapping::Output::Unscoped
|
81
|
-
|
82
|
-
output =
|
83
|
-
unscope_class.new(
|
84
|
-
Trailblazer::Option(VariableMapping::filter_for(output))
|
85
|
-
)
|
86
|
-
|
87
|
-
TaskWrap::VariableMapping.merge_instructions_for(input, output, id: input.object_id) # wraps filters: {Input(input), Output(output)}
|
88
|
-
end
|
89
|
-
|
90
|
-
# DISCUSS: improvable sections such as merge vs hash[]=
|
91
|
-
def initial_input_hash(wrap_ctx, original_args)
|
92
|
-
wrap_ctx = wrap_ctx.merge(input_hash: {})
|
93
|
-
|
94
|
-
return wrap_ctx, original_args
|
95
|
-
end
|
96
|
-
|
97
|
-
# Merge all original ctx variables into the new input_ctx.
|
98
|
-
# This happens when no {:input} is provided.
|
99
|
-
def default_input_ctx(wrap_ctx, original_args)
|
100
|
-
((original_ctx, _), _) = original_args
|
101
|
-
|
102
|
-
MergeVariables(original_ctx, wrap_ctx, original_args)
|
103
|
-
end
|
104
|
-
|
105
|
-
# TODO: test {nil} default
|
106
|
-
# FIXME: what if you don't want inject but always the value from the config?
|
107
|
-
# Add injected variables if they're present on
|
108
|
-
# the original, incoming ctx.
|
109
|
-
def add_injections(wrap_ctx, original_args)
|
110
|
-
name2filter = wrap_ctx[:injections]
|
111
|
-
((original_ctx, _), circuit_options) = original_args
|
112
|
-
|
113
|
-
injections =
|
114
|
-
name2filter.collect do |name, filter|
|
115
|
-
# DISCUSS: should we remove {is_defaulted} and infer type from {filter} or the return value?
|
116
|
-
is_defaulted, new_name, default_value = filter.(original_ctx, keyword_arguments: original_ctx.to_hash, **circuit_options) # FIXME: interface? # {filter} exposes {Option} interface
|
117
|
-
|
118
|
-
original_ctx.key?(name) ?
|
119
|
-
[new_name, original_ctx[name]] : (
|
120
|
-
is_defaulted ? [new_name, default_value] : nil
|
121
|
-
)
|
122
|
-
end.compact.to_h # FIXME: are we <2.6 safe here?
|
123
|
-
|
124
|
-
MergeVariables(injections, wrap_ctx, original_args)
|
125
|
-
end
|
126
|
-
|
127
|
-
# Implements {:input}.
|
128
|
-
def add_variables(wrap_ctx, original_args)
|
129
|
-
filter = wrap_ctx[:input_filter]
|
130
|
-
((original_ctx, _), circuit_options) = original_args
|
131
|
-
|
132
|
-
# this is the actual logic.
|
133
|
-
variables = filter.(original_ctx, keyword_arguments: original_ctx.to_hash, **circuit_options)
|
134
|
-
|
135
|
-
MergeVariables(variables, wrap_ctx, original_args)
|
136
|
-
end
|
137
|
-
|
138
|
-
# Finally, create a new input ctx from all the
|
139
|
-
# collected input variables.
|
140
|
-
# This goes into the step/nested OP.
|
141
|
-
def scope(wrap_ctx, original_args)
|
142
|
-
((_, flow_options), _) = original_args
|
143
|
-
|
144
|
-
# this is the actual context passed into the step.
|
145
|
-
wrap_ctx[:input_ctx] = Trailblazer::Context(
|
146
|
-
wrap_ctx[:input_hash],
|
147
|
-
{}, # mutable variables
|
148
|
-
flow_options[:context_options]
|
149
|
-
)
|
150
|
-
|
151
|
-
return wrap_ctx, original_args
|
152
|
-
end
|
153
|
-
|
154
|
-
# Last call in every step. Currently replaces {:input_ctx} by adding variables using {#merge}.
|
155
|
-
# DISCUSS: improve here?
|
156
|
-
def MergeVariables(variables, wrap_ctx, original_args)
|
157
|
-
wrap_ctx[:input_hash] = wrap_ctx[:input_hash].merge(variables)
|
158
|
-
|
159
|
-
return wrap_ctx, original_args
|
160
|
-
end
|
161
|
-
|
162
|
-
# @private
|
163
|
-
# The default {:output} filter only returns the "mutable" part of the inner ctx.
|
164
|
-
# This means only variables added using {inner_ctx[..]=} are merged on the outside.
|
165
|
-
def default_output
|
166
|
-
->(scoped, **) do
|
167
|
-
_wrapped, mutable = scoped.decompose # `_wrapped` is what the `:input` filter returned, `mutable` is what the task wrote to `scoped`.
|
168
|
-
mutable
|
169
|
-
end
|
170
|
-
end
|
171
|
-
|
172
|
-
# Returns a filter proc to be called in an Option.
|
173
|
-
# @private
|
174
|
-
def filter_for(filter)
|
175
|
-
if filter.is_a?(::Array) || filter.is_a?(::Hash)
|
176
|
-
DSL.filter_from_dsl(filter)
|
177
|
-
else
|
178
|
-
filter
|
179
|
-
end
|
180
|
-
end
|
181
|
-
|
182
|
-
# @private
|
183
|
-
def output_option_for(option, pass_outer_ctx) # DISCUSS: not sure I like this.
|
184
|
-
|
185
|
-
return option if pass_outer_ctx
|
186
|
-
# OutputReceivingInnerCtxOnly =
|
187
|
-
|
188
|
-
# don't pass {outer_ctx}, only {inner_ctx}. this is the default.
|
189
|
-
return ->(inner_ctx, outer_ctx, **kws) { option.(inner_ctx, **kws) }
|
190
|
-
end
|
191
|
-
|
192
|
-
|
193
|
-
module DSL
|
194
|
-
# The returned filter compiles a new hash for Scoped/Unscoped that only contains
|
195
|
-
# the desired i/o variables.
|
196
|
-
def self.filter_from_dsl(map)
|
197
|
-
hsh = DSL.hash_for(map)
|
198
|
-
|
199
|
-
->(incoming_ctx, **kwargs) { Hash[hsh.collect { |from_name, to_name| [to_name, incoming_ctx[from_name]] }] }
|
200
|
-
end
|
201
|
-
|
202
|
-
def self.hash_for(ary)
|
203
|
-
return ary if ary.instance_of?(::Hash)
|
204
|
-
Hash[ary.collect { |name| [name, name] }]
|
205
|
-
end
|
206
|
-
end
|
207
|
-
|
208
|
-
module Output
|
209
|
-
# Merge the resulting {@filter.()} hash back into the original ctx.
|
210
|
-
# DISCUSS: do we need the original_ctx as a filter argument?
|
211
|
-
class Unscoped
|
212
|
-
def initialize(filter)
|
213
|
-
@filter = filter
|
214
|
-
end
|
215
|
-
|
216
|
-
# The returned hash from {@filter} is merged with the original ctx.
|
217
|
-
def call(new_ctx, (original_ctx, flow_options), **circuit_options)
|
218
|
-
original_ctx.merge(
|
219
|
-
call_filter(new_ctx, [original_ctx, flow_options], **circuit_options)
|
220
|
-
)
|
221
|
-
end
|
222
|
-
|
223
|
-
def call_filter(new_ctx, (_original_ctx, _flow_options), **circuit_options)
|
224
|
-
# Pass {inner_ctx, **inner_ctx}
|
225
|
-
@filter.(new_ctx, keyword_arguments: new_ctx.to_hash, **circuit_options)
|
226
|
-
end
|
227
|
-
|
228
|
-
class WithOuterContext < Unscoped
|
229
|
-
def call_filter(new_ctx, (original_ctx, _flow_options), **circuit_options)
|
230
|
-
# Pass {inner_ctx, outer_ctx, **inner_ctx}
|
231
|
-
@filter.(new_ctx, original_ctx, keyword_arguments: new_ctx.to_hash, **circuit_options)
|
232
|
-
end
|
233
|
-
end
|
234
|
-
end
|
235
|
-
end
|
236
|
-
end # VariableMapping
|
237
|
-
end
|
238
|
-
end
|
239
|
-
end
|
240
|
-
end
|