trailblazer-activity-dsl-linear 0.5.0 → 1.0.0.beta1
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 +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
|