trailblazer-activity-dsl-linear 0.4.3 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9953750b2e208755037a9f48a7847edc3a3ccdf54bd4ab121be8e8666b85e41f
4
- data.tar.gz: 1d36eeb6036cf7a7a8fc234c78ccf90507de7f3f380a6a3fd92f432e1efe246f
3
+ metadata.gz: 6185e8d95445bb10e611433a6885be07ec85524cdb5d45e1aed6449610391dd1
4
+ data.tar.gz: e0af2bf5eba1bbb381c35675c3352ba99b5a3598964e9deffeec75d8e6b3be58
5
5
  SHA512:
6
- metadata.gz: d50d156f2daed3fccf7c78d978896e4a94d5cf6ef854f9e77ace7186700703c45c1aed21a551b27f0343d4d0ed33694958be936fc6a5651505cf6b368052dd83
7
- data.tar.gz: 2b53f6d85d4e0e65407cb90c82ce34be3c55e3ba6b1dfdb25ae64a04be5d19617410c91da532f09cb2ed6058eb7e28e18e11bc3e68d5f83b197ef0792d3e2b15
6
+ metadata.gz: 3ae89d32dddf81784df0abb2d103c3672125c80f6637e5cae76a3a460addb75e7707352a43643d8359273cb7c2e6c5e96636e19c714adebe5da657cd74d5c73e
7
+ data.tar.gz: c9f56bcb32fd1e1ba4adb7e8b39af43093d96a0eadb9ff21cbe35a2207a4c7b70728ee1eda7de00af1bf0972afbb51744143ff6f3d05595c6457c209f31b38c4
data/CHANGES.md CHANGED
@@ -1,3 +1,9 @@
1
+ # 0.5.0
2
+
3
+ * Introduce `:inject` option to pass-through injected variables and to default input variables.
4
+ * Remove `VariableMapping::Input::Scoped` as we're now using a separate `Pipeline` for input filtering.
5
+ * Massively simplify (and accelerate!) the `Normalizer` layer by using `TaskWrap::Pipeline` instead of `Activity::Path`. Note that you can alter a normalizer by using the `TaskWrap::Pipeline` API now.
6
+
1
7
  # 0.4.3
2
8
 
3
9
  * Limit `trailblazer-activity` dependency to `< 0.13.0`.
data/Gemfile CHANGED
@@ -9,4 +9,5 @@ gem "rubocop", require: false
9
9
 
10
10
  # gem "trailblazer-developer", path: "../trailblazer-developer"
11
11
  # gem "trailblazer-activity", path: "../trailblazer-activity"
12
+ gem "trailblazer-activity", github: "trailblazer/trailblazer-activity"
12
13
  # gem "trailblazer-activity", path: "../circuit"
data/README.md CHANGED
@@ -1,17 +1,17 @@
1
1
  # Activity-DSL-Linear
2
2
 
3
3
  The `activity-dsl-linear` gem brings:
4
- - [Path](https://2019.trailblazer.to/2.1/docs/activity.html#activity-strategy-path)
5
- - [Railway](https://2019.trailblazer.to/2.1/docs/activity.html#activity-strategy-railway)
6
- - [Fasttrack](https://2019.trailblazer.to/2.1/docs/activity.html#activity-strategy-fasttrack)
4
+ - [Path](https://trailblazer.to/2.1/docs/activity.html#activity-strategy-path)
5
+ - [Railway](https://trailblazer.to/2.1/docs/activity.html#activity-strategy-railway)
6
+ - [Fasttrack](https://trailblazer.to/2.1/docs/activity.html#activity-strategy-fasttrack)
7
7
 
8
8
  DSLs strategies for buildig activities. It is build around [`activity`](https://github.com/trailblazer/trailblazer-activity) gem.
9
9
 
10
- Please find the [full documentation on the Trailblazer website](https://2019.trailblazer.to/2.1/docs/activity.html#activity-strategy). [Note that the docs are WIP.]
10
+ Please find the [full documentation on the Trailblazer website](https://trailblazer.to/2.1/docs/activity.html#activity-strategy).
11
11
 
12
12
  ## Example
13
13
 
14
- The `activity-dsl-linear` gem provides three default patterns to model processes: `Path`, `Railway` and `FastTrack`. Here's an example of what a railway activity could look like, along with some more complex connections (you can read more about Railway strategy in the [docs](https://2019.trailblazer.to/2.1/docs/activity.html#activity-strategy-railway)).
14
+ The `activity-dsl-linear` gem provides three default patterns to model processes: `Path`, `Railway` and `FastTrack`. Here's an example of what a railway activity could look like, along with some more complex connections (you can read more about Railway strategy in the [docs](https://trailblazer.to/2.1/docs/activity.html#activity-strategy-railway)).
15
15
 
16
16
  ```ruby
17
17
  require "trailblazer-activity"
@@ -79,15 +79,15 @@ With Activity, modeling business processes turns out to be ridiculously simple:
79
79
 
80
80
  ## Operation
81
81
 
82
- Trailblazer's [`Operation`](https://2019.trailblazer.to/2.1/docs/operation.html#operation-overview) internally uses an activity to model the processes.
82
+ Trailblazer's [`Operation`](https://trailblazer.to/2.1/docs/operation.html#operation-overview) internally uses an activity to model the processes.
83
83
 
84
84
  ## Workflow
85
- Activities can be formed into bigger compounds and using workflow, you can build long-running processes such as a moderated blog post or a parcel delivery. Also, you don't have to use the DSL but can use the [`editor`](https://2019.trailblazer.to/2.1/docs/pro.html#pro-editor)instead(cool for more complex, long-running flows). Here comes a sample screenshot.
85
+ Activities can be formed into bigger compounds and using workflow, you can build long-running processes such as a moderated blog post or a parcel delivery. Also, you don't have to use the DSL but can use the [`editor`](https://trailblazer.to/2.1/docs/pro.html#pro-editor)instead(cool for more complex, long-running flows). Here comes a sample screenshot.
86
86
 
87
- <img src="http://2019.trailblazer.to/2.1/dist/img/flow.png">
87
+ <img src="http://trailblazer.to/2.1/dist/img/flow.png">
88
88
 
89
89
  ## License
90
90
 
91
91
  © Copyright 2018, Trailblazer GmbH
92
92
 
93
- Licensed under the LGPLv3 license. We also offer a commercial-friendly [license](https://2019.trailblazer.to/2.1/docs/pro.html#pro-license).
93
+ Licensed under the LGPLv3 license. We also offer a commercial-friendly [license](https://trailblazer.to/2.1/docs/pro.html#pro-license).
@@ -7,154 +7,149 @@ module Trailblazer
7
7
  module Normalizer
8
8
  module_function
9
9
 
10
- # activity_normalizer.([{options:, user_options:, normalizer_options: }])
11
- def activity_normalizer(sequence)
12
- seq = Activity::Path::DSL.prepend_to_path( # this doesn't particularly put the steps after the Path steps.
13
- sequence,
14
-
15
- {
16
- "activity.normalize_step_interface" => method(:normalize_step_interface), # first
17
- "activity.normalize_for_macro" => method(:merge_user_options),
18
- "activity.normalize_normalizer_options" => method(:merge_normalizer_options),
19
- "activity.normalize_context" => method(:normalize_context),
20
- "activity.normalize_id" => method(:normalize_id),
21
- "activity.normalize_override" => method(:normalize_override),
22
- "activity.wrap_task_with_step_interface" => method(:wrap_task_with_step_interface), # last
23
- "activity.inherit_option" => method(:inherit_option),
24
- },
10
+ # Wrap {task} with {Trailblazer::Option} and execute it with kw args in {#call}.
11
+ # Note that this instance always return {Right}.
12
+ class Task < TaskBuilder::Task
13
+ def call(wrap_ctx, flow_options={})
14
+ result = call_option(@task, [wrap_ctx, flow_options]) # DISCUSS: this mutates {ctx}.
25
15
 
26
- Linear::Insert.method(:Append), "Start.default"
27
- )
16
+ return wrap_ctx, flow_options
17
+ end
18
+ end
28
19
 
29
- seq = Trailblazer::Activity::Path::DSL.prepend_to_path( # this doesn't particularly put the steps after the Path steps.
30
- seq,
20
+ def Task(user_proc)
21
+ Normalizer::Task.new(Trailblazer::Option(user_proc), user_proc)
22
+ end
31
23
 
24
+ # activity_normalizer.([{options:, user_options:, normalizer_options: }])
25
+ def activity_normalizer(pipeline)
26
+ pipeline = TaskWrap::Pipeline.prepend(
27
+ pipeline,
28
+ nil, # this means, put it to the beginning.
32
29
  {
33
- "activity.normalize_outputs_from_dsl" => method(:normalize_outputs_from_dsl), # Output(Signal, :semantic) => Id()
34
- "activity.normalize_connections_from_dsl" => method(:normalize_connections_from_dsl),
35
- "activity.input_output_dsl" => method(:input_output_dsl), # FIXME: make this optional and allow to dynamically change normalizer steps
30
+ "activity.normalize_step_interface" => Normalizer.Task(method(:normalize_step_interface)), # first
31
+ "activity.normalize_for_macro" => Normalizer.Task(method(:merge_user_options)),
32
+ "activity.normalize_normalizer_options" => Normalizer.Task(method(:merge_normalizer_options)),
33
+ "activity.normalize_non_symbol_options" => Normalizer.Task(method(:normalize_non_symbol_options)),
34
+ "activity.normalize_context" => method(:normalize_context),
35
+ "activity.normalize_id" => Normalizer.Task(method(:normalize_id)),
36
+ "activity.normalize_override" => Normalizer.Task(method(:normalize_override)),
37
+ "activity.wrap_task_with_step_interface" => Normalizer.Task(method(:wrap_task_with_step_interface)), # last
38
+ "activity.inherit_option" => Normalizer.Task(method(:inherit_option)),
36
39
  },
37
-
38
- Linear::Insert.method(:Prepend), "path.wirings"
39
40
  )
40
41
 
41
- seq = Trailblazer::Activity::Path::DSL.prepend_to_path( # this doesn't particularly put the steps after the Path steps.
42
- seq,
43
-
42
+ pipeline = TaskWrap::Pipeline.prepend(
43
+ pipeline,
44
+ "path.wirings",
44
45
  {
45
- "activity.cleanup_options" => method(:cleanup_options),
46
+ "activity.normalize_outputs_from_dsl" => Normalizer.Task(method(:normalize_outputs_from_dsl)), # Output(Signal, :semantic) => Id()
47
+ "activity.normalize_connections_from_dsl" => Normalizer.Task(method(:normalize_connections_from_dsl)),
48
+ "activity.input_output_dsl" => Normalizer.Task(method(:input_output_dsl)),
46
49
  },
50
+ )
47
51
 
48
- Linear::Insert.method(:Prepend), "End.success"
52
+ pipeline = TaskWrap::Pipeline.append(
53
+ pipeline,
54
+ nil,
55
+ ["activity.cleanup_options", method(:cleanup_options)]
49
56
  )
50
- # pp seq
51
- seq
57
+
58
+ pipeline
52
59
  end
53
60
 
54
61
  # Specific to the "step DSL": if the first argument is a callable, wrap it in a {step_interface_builder}
55
62
  # since its interface expects the step interface, but the circuit will call it with circuit interface.
56
- def normalize_step_interface((ctx, flow_options), *)
57
- options = case ctx[:options]
63
+ def normalize_step_interface(ctx, options:, **)
64
+ options = case options
58
65
  when Hash
59
66
  # Circuit Interface
60
- task = ctx[:options].fetch(:task)
61
- id = ctx[:options][:id]
67
+ task = options.fetch(:task)
68
+ id = options[:id]
62
69
 
63
70
  if task.is_a?(Symbol)
64
71
  # step task: :find, id: :load
65
- { **ctx[:options], id: (id || task), task: Trailblazer::Option(task) }
72
+ { **options, id: (id || task), task: Trailblazer::Option(task) }
66
73
  else
67
74
  # step task: Callable, ... (Subprocess, Proc, macros etc)
68
- ctx[:options] # NOOP
75
+ options # NOOP
69
76
  end
70
77
  else
71
78
  # Step Interface
72
79
  # step :find, ...
73
80
  # step Callable, ... (Method, Proc etc)
74
- { task: ctx[:options], wrap_task: true }
81
+ { task: options, wrap_task: true }
75
82
  end
76
83
 
77
- return Trailblazer::Activity::Right, [ctx.merge(options: options), flow_options]
84
+ ctx[:options] = options
78
85
  end
79
86
 
80
- def wrap_task_with_step_interface((ctx, flow_options), **)
81
- return Trailblazer::Activity::Right, [ctx, flow_options] unless ctx[:wrap_task]
82
-
83
- step_interface_builder = ctx[:step_interface_builder] # FIXME: use kw!
84
- task = ctx[:task] # FIXME: use kw!
87
+ def wrap_task_with_step_interface(ctx, wrap_task: false, step_interface_builder:, task:, **)
88
+ return unless wrap_task
85
89
 
86
- wrapped_task = step_interface_builder.(task)
87
-
88
- return Trailblazer::Activity::Right, [ctx.merge(task: wrapped_task), flow_options]
90
+ ctx[:task] = step_interface_builder.(task)
89
91
  end
90
92
 
91
- def normalize_id((ctx, flow_options), **)
92
- id = ctx[:id] || ctx[:task]
93
-
94
- return Trailblazer::Activity::Right, [ctx.merge(id: id), flow_options]
93
+ def normalize_id(ctx, id: false, task:, **)
94
+ ctx[:id] = id || task
95
95
  end
96
96
 
97
97
  # {:override} really only makes sense for {step Macro(), {override: true}} where the {user_options}
98
98
  # dictate the overriding.
99
- def normalize_override((ctx, flow_options), *)
100
- ctx = ctx.merge(replace: ctx[:id] || raise) if ctx[:override]
101
-
102
- return Trailblazer::Activity::Right, [ctx, flow_options]
99
+ def normalize_override(ctx, id:, override: false, **)
100
+ return unless override
101
+ ctx[:replace] = (id || raise)
103
102
  end
104
103
 
105
104
  # make ctx[:options] the actual ctx
106
- def merge_user_options((ctx, flow_options), *)
107
- options = ctx[:options] # either a <#task> or {} from macro
108
-
109
- ctx = ctx.merge(options: options.merge(ctx[:user_options])) # Note that the user options are merged over the macro options.
110
-
111
- return Trailblazer::Activity::Right, [ctx, flow_options]
105
+ def merge_user_options(ctx, options:, **)
106
+ # {options} are either a <#task> or {} from macro
107
+ ctx[:options] = options.merge(ctx[:user_options]) # Note that the user options are merged over the macro options.
112
108
  end
113
109
 
114
110
  # {:normalizer_options} such as {:track_name} get overridden by user/macro.
115
- def merge_normalizer_options((ctx, flow_options), *)
116
- normalizer_options = ctx[:normalizer_options] # either a <#task> or {} from macro
117
-
118
- ctx = ctx.merge(options: normalizer_options.merge(ctx[:options])) #
119
-
120
- return Trailblazer::Activity::Right, [ctx, flow_options]
111
+ def merge_normalizer_options(ctx, normalizer_options:, options:, **)
112
+ ctx[:options] = normalizer_options.merge(options)
121
113
  end
122
114
 
123
- def normalize_context((ctx, flow_options), *)
115
+ def normalize_context(ctx, flow_options)
124
116
  ctx = ctx[:options]
125
117
 
126
- return Trailblazer::Activity::Right, [ctx, flow_options]
118
+ return ctx, flow_options
127
119
  end
128
120
 
129
121
  # Compile the actual {Seq::Row}'s {wiring}.
130
122
  # This combines {:connections} and {:outputs}
131
- def compile_wirings((ctx, flow_options), *)
132
- connections = ctx[:connections] || raise # FIXME
133
- outputs = ctx[:outputs] || raise # FIXME
134
-
123
+ def compile_wirings(ctx, connections:, outputs:, id:, **)
135
124
  ctx[:wirings] =
136
125
  connections.collect do |semantic, (search_strategy_builder, *search_args)|
137
- output = outputs[semantic] || raise("No `#{semantic}` output found for #{ctx[:id].inspect} and outputs #{outputs.inspect}")
126
+ output = outputs[semantic] || raise("No `#{semantic}` output found for #{id.inspect} and outputs #{outputs.inspect}")
138
127
 
139
128
  search_strategy_builder.( # return proc to be called when compiling Seq, e.g. {ById(output, :id)}
140
129
  output,
141
130
  *search_args
142
131
  )
143
132
  end
144
-
145
- return Trailblazer::Activity::Right, [ctx, flow_options]
146
133
  end
147
134
 
148
- # Process {Output(:semantic) => target}.
149
- def normalize_connections_from_dsl((ctx, flow_options), *)
150
- new_ctx = ctx.reject { |output, cfg| output.kind_of?(Activity::DSL::Linear::OutputSemantic) }
151
- connections = new_ctx[:connections]
152
- adds = new_ctx[:adds]
135
+ # Move DSL user options such as {Output(:success) => Track(:found)} to
136
+ # a new key {options[:non_symbol_options]}.
137
+ # This allows using {options} as a {**ctx}-able hash in Ruby 2.6 and 3.0.
138
+ def normalize_non_symbol_options(ctx, options:, **)
139
+ symbol_options = options.find_all { |k, v| k.is_a?(Symbol) }.to_h
140
+ non_symbol_options = options.slice(*(options.keys - symbol_options.keys))
141
+ # raise unless (symbol_options.size+non_symbol_options.size) == options.size
142
+
143
+ ctx[:options] = symbol_options.merge(non_symbol_options: non_symbol_options)
144
+ end
153
145
 
146
+ # Process {Output(:semantic) => target} and make them {:connections}.
147
+ def normalize_connections_from_dsl(ctx, connections:, adds:, non_symbol_options:, **)
154
148
  # Find all {Output() => Track()/Id()/End()}
155
- (ctx.keys - new_ctx.keys).each do |output|
156
- cfg = ctx[output]
149
+ output_configs = non_symbol_options.find_all{ |k,v| k.kind_of?(Activity::DSL::Linear::OutputSemantic) }
150
+ return unless output_configs.any?
157
151
 
152
+ output_configs.each do |output, cfg|
158
153
  new_connections, add =
159
154
  if cfg.is_a?(Activity::DSL::Linear::Track)
160
155
  [output_to_track(ctx, output, cfg), cfg.adds]
@@ -177,9 +172,8 @@ module Trailblazer
177
172
  adds += add
178
173
  end
179
174
 
180
- new_ctx = new_ctx.merge(connections: connections, adds: adds)
181
-
182
- return Trailblazer::Activity::Right, [new_ctx, flow_options]
175
+ ctx[:connections] = connections
176
+ ctx[:adds] = adds
183
177
  end
184
178
 
185
179
  def output_to_track(ctx, output, track)
@@ -205,53 +199,38 @@ module Trailblazer
205
199
  end
206
200
 
207
201
  # Output(Signal, :semantic) => Id()
208
- def normalize_outputs_from_dsl((ctx, flow_options), *)
209
- new_ctx = ctx.reject { |output, cfg| output.kind_of?(Activity::Output) }
202
+ def normalize_outputs_from_dsl(ctx, non_symbol_options:, outputs:, **)
203
+ output_configs = non_symbol_options.find_all{ |k,v| k.kind_of?(Activity::Output) }
204
+ return unless output_configs.any?
210
205
 
211
- outputs = ctx[:outputs]
212
206
  dsl_options = {}
213
207
 
214
- (ctx.keys - new_ctx.keys).collect do |output|
215
- cfg = ctx[output] # e.g. Track(:success)
216
-
217
- outputs = outputs.merge(output.semantic => output)
208
+ output_configs.collect do |output, cfg| # {cfg} = Track(:success)
209
+ outputs = outputs.merge(output.semantic => output)
218
210
  dsl_options = dsl_options.merge(Linear.Output(output.semantic) => cfg)
219
211
  end
220
212
 
221
- new_ctx = new_ctx.merge(outputs: outputs).merge(dsl_options)
222
-
223
- return Trailblazer::Activity::Right, [new_ctx, flow_options]
213
+ ctx[:outputs] = outputs
214
+ ctx[:non_symbol_options] = non_symbol_options.merge(dsl_options)
224
215
  end
225
216
 
226
- def input_output_dsl((ctx, flow_options), *)
227
- config = ctx.select { |k,v| [:input, :output, :output_with_outer_ctx].include?(k) } # TODO: optimize this, we don't have to go through the entire hash.
228
-
229
- return Trailblazer::Activity::Right, [ctx, flow_options] if config.size == 0 # no :input/:output passed.
217
+ def input_output_dsl(ctx, extensions: [], **)
218
+ config = ctx.select { |k,v| [:input, :output, :output_with_outer_ctx, :inject].include?(k) } # TODO: optimize this, we don't have to go through the entire hash.
219
+ return unless config.any? # no :input/:output/:inject passed.
230
220
 
231
- new_ctx = {}
232
- new_ctx[:extensions] = ctx[:extensions] || [] # merge DSL extensions with I/O.
233
- new_ctx[:extensions] += [Linear.VariableMapping(**config)]
234
-
235
- return Trailblazer::Activity::Right, [ctx.merge(new_ctx), flow_options]
221
+ ctx[:extensions] = extensions + [Linear.VariableMapping(**config)]
236
222
  end
237
223
 
238
224
  # Currently, the {:inherit} option copies over {:connections} from the original step
239
225
  # and merges them with the (prolly) connections passed from the user.
240
- def inherit_option((ctx, flow_options), *)
241
- return Trailblazer::Activity::Right, [ctx, flow_options] unless ctx[:inherit]
242
-
243
- sequence = ctx[:sequence]
244
- id = ctx[:id]
226
+ def inherit_option(ctx, inherit: false, sequence:, id:, extensions: [], **)
227
+ return unless inherit
245
228
 
246
229
  index = Linear::Insert.find_index(sequence, id)
247
230
  row = sequence[index] # from this row we're inheriting options.
248
231
 
249
- connections = get_inheritable_connections(ctx, row[3][:connections])
250
- extensions = Array(row[3][:extensions]) + Array(ctx[:extensions])
251
-
252
- ctx = ctx.merge(connections: connections, extensions: extensions) # "inherit"
253
-
254
- return Trailblazer::Activity::Right, [ctx, flow_options]
232
+ ctx[:connections] = get_inheritable_connections(ctx, row[3][:connections])
233
+ ctx[:extensions] = Array(row[3][:extensions]) + Array(extensions)
255
234
  end
256
235
 
257
236
  # return connections from {parent} step which are supported by current step
@@ -262,11 +241,11 @@ module Trailblazer
262
241
  end
263
242
 
264
243
  # TODO: make this extendable!
265
- def cleanup_options((ctx, flow_options), *)
244
+ def cleanup_options(ctx, flow_options)
266
245
  # new_ctx = ctx.reject { |k, v| [:connections, :outputs, :end_id, :step_interface_builder, :failure_end, :track_name, :sequence].include?(k) }
267
- new_ctx = ctx.reject { |k, v| [:outputs, :end_id, :step_interface_builder, :failure_end, :track_name, :sequence].include?(k) }
246
+ new_ctx = ctx.reject { |k, v| [:outputs, :end_id, :step_interface_builder, :failure_end, :track_name, :sequence, :non_symbol_options].include?(k) }
268
247
 
269
- return Trailblazer::Activity::Right, [new_ctx, flow_options]
248
+ return new_ctx, flow_options
270
249
  end
271
250
  end
272
251
 
@@ -39,27 +39,20 @@ module Trailblazer
39
39
 
40
40
  # Compiles and maintains all final normalizers for a specific DSL.
41
41
  class Normalizer
42
- def compile_normalizer(normalizer_sequence)
43
- process = Trailblazer::Activity::DSL::Linear::Compiler.(normalizer_sequence)
44
- process.to_h[:circuit]
45
- end
46
-
47
42
  # [gets instantiated at compile time.]
48
43
  #
49
44
  # We simply compile the activities that represent the normalizers for #step, #pass, etc.
50
45
  # This can happen at compile-time, as normalizers are stateless.
51
46
  def initialize(normalizer_sequences)
52
- @normalizers = Hash[
53
- normalizer_sequences.collect { |name, seq| [name, compile_normalizer(seq)] }
54
- ]
47
+ @normalizers = normalizer_sequences
55
48
  end
56
49
 
57
50
  # Execute the specific normalizer (step, fail, pass) for a particular option set provided
58
51
  # by the DSL user. This is usually when you call Operation::step.
59
- def call(name, *args)
52
+ def call(name, ctx)
60
53
  normalizer = @normalizers.fetch(name)
61
- _signal, (options, _) = normalizer.(*args)
62
- options
54
+ wrap_ctx, _ = normalizer.(ctx, nil)
55
+ wrap_ctx
63
56
  end
64
57
  end
65
58
  end # State
@@ -4,28 +4,164 @@ module Trailblazer
4
4
  module Linear
5
5
  # Normalizer-steps to implement {:input} and {:output}
6
6
  # Returns an Extension instance to be thrown into the `step` DSL arguments.
7
- def self.VariableMapping(input: VariableMapping.default_input, output: VariableMapping.default_output, output_with_outer_ctx: false)
8
- input =
9
- VariableMapping::Input::Scoped.new(
10
- Trailblazer::Option(VariableMapping::filter_for(input))
11
- )
12
-
13
- unscope_class = output_with_outer_ctx ? VariableMapping::Output::Unscoped::WithOuterContext : VariableMapping::Output::Unscoped
14
-
15
- output =
16
- unscope_class.new(
17
- Trailblazer::Option(VariableMapping::filter_for(output))
18
- )
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)
19
9
 
20
- TaskWrap::Extension(
21
- merge: TaskWrap::VariableMapping.merge_for(input, output, id: input.object_id), # wraps filters: {Input(input), Output(output)}
22
- )
10
+ TaskWrap::Extension(merge: merge_instructions)
23
11
  end
24
12
 
25
13
  module VariableMapping
26
14
  module_function
27
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
+ #
28
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.
29
165
  def default_output
30
166
  ->(scoped, **) do
31
167
  _wrapped, mutable = scoped.decompose # `_wrapped` is what the `:input` filter returned, `mutable` is what the task wrote to `scoped`.
@@ -33,11 +169,6 @@ module Trailblazer
33
169
  end
34
170
  end
35
171
 
36
- # @private
37
- def default_input
38
- ->(ctx, **) { ctx }
39
- end
40
-
41
172
  # Returns a filter proc to be called in an Option.
42
173
  # @private
43
174
  def filter_for(filter)
@@ -74,22 +205,6 @@ module Trailblazer
74
205
  end
75
206
  end
76
207
 
77
- module Input
78
- class Scoped
79
- def initialize(filter)
80
- @filter = filter
81
- end
82
-
83
- def call((original_ctx, flow_options), **circuit_options)
84
- Trailblazer::Context(
85
- @filter.(original_ctx, keyword_arguments: original_ctx.to_hash, **circuit_options),
86
- {},
87
- flow_options[:context_options]
88
- )
89
- end
90
- end
91
- end
92
-
93
208
  module Output
94
209
  # Merge the resulting {@filter.()} hash back into the original ctx.
95
210
  # DISCUSS: do we need the original_ctx as a filter argument?
@@ -98,19 +213,20 @@ module Trailblazer
98
213
  @filter = filter
99
214
  end
100
215
 
216
+ # The returned hash from {@filter} is merged with the original ctx.
101
217
  def call(new_ctx, (original_ctx, flow_options), **circuit_options)
102
218
  original_ctx.merge(
103
219
  call_filter(new_ctx, [original_ctx, flow_options], **circuit_options)
104
220
  )
105
221
  end
106
222
 
107
- def call_filter(new_ctx, (original_ctx, flow_options), **circuit_options)
223
+ def call_filter(new_ctx, (_original_ctx, _flow_options), **circuit_options)
108
224
  # Pass {inner_ctx, **inner_ctx}
109
225
  @filter.(new_ctx, keyword_arguments: new_ctx.to_hash, **circuit_options)
110
226
  end
111
227
 
112
228
  class WithOuterContext < Unscoped
113
- def call_filter(new_ctx, (original_ctx, flow_options), **circuit_options)
229
+ def call_filter(new_ctx, (original_ctx, _flow_options), **circuit_options)
114
230
  # Pass {inner_ctx, outer_ctx, **inner_ctx}
115
231
  @filter.(new_ctx, original_ctx, keyword_arguments: new_ctx.to_hash, **circuit_options)
116
232
  end
@@ -3,7 +3,7 @@ module Trailblazer
3
3
  module Activity
4
4
  module DSL
5
5
  module Linear
6
- VERSION = "0.4.3"
6
+ VERSION = "0.5.0"
7
7
  end
8
8
  end
9
9
  end
@@ -6,7 +6,7 @@ module Trailblazer
6
6
  end
7
7
  end
8
8
 
9
- # Implementation module that can be passed to `Activity[]`.
9
+ # Implementation module that can be passed to `Activity()`.
10
10
  class FastTrack
11
11
  Linear = Activity::DSL::Linear
12
12
 
@@ -22,105 +22,97 @@ module Trailblazer
22
22
  end
23
23
 
24
24
  def normalizer_for_fail
25
- sequence = step_options(Trailblazer::Activity::Railway::DSL.normalizer_for_fail)
25
+ pipeline = step_options(Trailblazer::Activity::Railway::DSL.normalizer_for_fail)
26
26
 
27
- Path::DSL.prepend_to_path(
28
- sequence,
27
+ TaskWrap::Pipeline.prepend(
28
+ pipeline,
29
+ "path.wirings",
29
30
 
30
31
  {
31
- "fast_track.fail_fast_option_for_fail" => method(:fail_fast_option_for_fail),
32
- },
33
- Linear::Insert.method(:Prepend), "path.wirings"
32
+ "fast_track.fail_fast_option_for_fail" => Linear::Normalizer.Task(method(:fail_fast_option_for_fail)),
33
+ }
34
34
  )
35
35
  end
36
36
 
37
37
  def normalizer_for_pass
38
- sequence = step_options(Trailblazer::Activity::Railway::DSL.normalizer_for_pass)
38
+ pipeline = step_options(Trailblazer::Activity::Railway::DSL.normalizer_for_pass)
39
39
 
40
- Path::DSL.prepend_to_path(
41
- sequence,
40
+ TaskWrap::Pipeline.prepend(
41
+ pipeline,
42
+ "path.wirings",
42
43
 
43
44
  {
44
- "fast_track.pass_fast_option_for_pass" => method(:pass_fast_option_for_pass),
45
- },
46
- Linear::Insert.method(:Prepend), "path.wirings"
45
+ "fast_track.pass_fast_option_for_pass" => Linear::Normalizer.Task(method(:pass_fast_option_for_pass)),
46
+ }
47
47
  )
48
48
  end
49
49
 
50
- def step_options(sequence)
51
- Path::DSL.prepend_to_path( # this doesn't particularly put the steps after the Path steps.
52
- sequence,
50
+ def step_options(pipeline)
51
+ TaskWrap::Pipeline.prepend(
52
+ pipeline,
53
+ "path.wirings",
53
54
 
54
55
  {
55
- "fast_track.pass_fast_option" => method(:pass_fast_option),
56
- "fast_track.fail_fast_option" => method(:fail_fast_option),
57
- "fast_track.fast_track_option" => method(:fast_track_option),
58
- },
59
- Linear::Insert.method(:Prepend), "path.wirings"
56
+ "fast_track.pass_fast_option" => Linear::Normalizer.Task(method(:pass_fast_option)),
57
+ "fast_track.fail_fast_option" => Linear::Normalizer.Task(method(:fail_fast_option)),
58
+ "fast_track.fast_track_option" => Linear::Normalizer.Task(method(:fast_track_option)),
59
+ }
60
60
  )
61
61
  end
62
62
 
63
- def pass_fast_option((ctx, flow_options), *)
64
- ctx = merge_connections_for(ctx, ctx, :pass_fast, :success)
63
+ def pass_fast_option(ctx, **)
64
+ ctx = merge_connections_for!(ctx, :pass_fast, :success, **ctx)
65
65
 
66
- ctx = merge_connections_for(ctx, ctx, :pass_fast, :pass_fast, :pass_fast)
67
- ctx = merge_outputs_for(ctx,
68
- pass_fast: Activity.Output(Activity::FastTrack::PassFast, :pass_fast),
66
+ ctx = merge_connections_for!(ctx, :pass_fast, :pass_fast, :pass_fast, **ctx)
67
+ ctx = merge_outputs_for!(ctx,
68
+ {pass_fast: Activity.Output(Activity::FastTrack::PassFast, :pass_fast)},
69
+ **ctx
69
70
  )
70
-
71
- return Right, [ctx, flow_options]
72
71
  end
73
72
 
74
- def pass_fast_option_for_pass((ctx, flow_options), *)
75
- ctx = merge_connections_for(ctx, ctx, :pass_fast, :failure)
76
- ctx = merge_connections_for(ctx, ctx, :pass_fast, :success)
77
-
78
- return Right, [ctx, flow_options]
73
+ def pass_fast_option_for_pass(ctx, **)
74
+ ctx = merge_connections_for!(ctx, :pass_fast, :failure, **ctx)
75
+ ctx = merge_connections_for!(ctx, :pass_fast, :success, **ctx)
79
76
  end
80
77
 
81
- def fail_fast_option((ctx, flow_options), *)
82
- ctx = merge_connections_for(ctx, ctx, :fail_fast, :failure)
78
+ def fail_fast_option(ctx, **)
79
+ ctx = merge_connections_for!(ctx, :fail_fast, :failure, **ctx)
83
80
 
84
- ctx = merge_connections_for(ctx, ctx, :fail_fast, :fail_fast, :fail_fast)
85
- ctx = merge_outputs_for(ctx,
86
- fail_fast: Activity.Output(Activity::FastTrack::FailFast, :fail_fast),
81
+ ctx = merge_connections_for!(ctx, :fail_fast, :fail_fast, :fail_fast, **ctx)
82
+ ctx = merge_outputs_for!(ctx,
83
+ {fail_fast: Activity.Output(Activity::FastTrack::FailFast, :fail_fast)},
84
+ **ctx
87
85
  )
88
-
89
- return Right, [ctx, flow_options]
90
86
  end
91
87
 
92
- def fail_fast_option_for_fail((ctx, flow_options), *)
93
- ctx = merge_connections_for(ctx, ctx, :fail_fast, :failure)
94
- ctx = merge_connections_for(ctx, ctx, :fail_fast, :success)
95
-
96
- return Right, [ctx, flow_options]
88
+ def fail_fast_option_for_fail(ctx, **)
89
+ ctx = merge_connections_for!(ctx, :fail_fast, :failure, **ctx)
90
+ ctx = merge_connections_for!(ctx, :fail_fast, :success, **ctx)
97
91
  end
98
92
 
99
- def fast_track_option((ctx, flow_options), *)
100
- return Right, [ctx, flow_options] unless ctx[:fast_track]
93
+ def fast_track_option(ctx, fast_track: false, **)
94
+ return unless fast_track
101
95
 
102
- ctx = merge_connections_for(ctx, ctx, :fast_track, :fail_fast, :fail_fast)
103
- ctx = merge_connections_for(ctx, ctx, :fast_track, :pass_fast, :pass_fast)
96
+ ctx = merge_connections_for!(ctx, :fast_track, :fail_fast, :fail_fast, **ctx)
97
+ ctx = merge_connections_for!(ctx, :fast_track, :pass_fast, :pass_fast, **ctx)
104
98
 
105
- ctx = merge_outputs_for(ctx,
106
- pass_fast: Activity.Output(Activity::FastTrack::PassFast, :pass_fast),
107
- fail_fast: Activity.Output(Activity::FastTrack::FailFast, :fail_fast),
99
+ ctx = merge_outputs_for!(ctx,
100
+ {pass_fast: Activity.Output(Activity::FastTrack::PassFast, :pass_fast),
101
+ fail_fast: Activity.Output(Activity::FastTrack::FailFast, :fail_fast)},
102
+ **ctx
108
103
  )
109
-
110
- return Right, [ctx, flow_options]
111
104
  end
112
105
 
113
- def merge_connections_for(ctx, options, option_name, semantic, magnetic_to=option_name)
114
- return ctx unless options[option_name]
106
+ def merge_connections_for!(ctx, option_name, semantic, magnetic_to=option_name, connections:, **)
107
+ return ctx unless ctx[option_name]
115
108
 
116
- connections = ctx[:connections].merge(semantic => [Linear::Search.method(:Forward), magnetic_to])
117
- ctx = ctx.merge(connections: connections)
109
+ ctx[:connections] = connections.merge(semantic => [Linear::Search.method(:Forward), magnetic_to])
110
+ ctx
118
111
  end
119
112
 
120
- def merge_outputs_for(ctx, outputs)
121
- ctx = ctx.merge(
122
- outputs: outputs.merge(ctx[:outputs])
123
- )
113
+ def merge_outputs_for!(ctx, new_outputs, outputs:, **)
114
+ ctx[:outputs] = new_outputs.merge(outputs)
115
+ ctx
124
116
  end
125
117
 
126
118
  def initial_sequence(initial_sequence:, fail_fast_end: Activity::End.new(semantic: :fail_fast), pass_fast_end: Activity::End.new(semantic: :pass_fast), **_o)
@@ -166,7 +158,6 @@ module Trailblazer
166
158
  extend Activity::DSL::Linear::Strategy
167
159
 
168
160
  initialize!(Railway::DSL::State.new(**DSL.OptionsForState()))
169
-
170
161
  end # FastTrack
171
162
  end
172
163
  end
@@ -9,20 +9,30 @@ module Trailblazer
9
9
  module_function
10
10
 
11
11
  def normalizer
12
- prepend_step_options(
13
- initial_sequence(track_name: :success, end_task: Activity::End.new(semantic: :success), end_id: "End.success")
14
- )
12
+ TaskWrap::Pipeline.new(normalizer_steps.to_a)
13
+ end
14
+
15
+ # Return {Path::Normalizer} sequence.
16
+ private def normalizer_steps
17
+ {
18
+ "path.outputs" => Linear::Normalizer.Task(method(:merge_path_outputs)),
19
+ "path.connections" => Linear::Normalizer.Task(method(:merge_path_connections)),
20
+ "path.sequence_insert" => Linear::Normalizer.Task(method(:normalize_sequence_insert)),
21
+ "path.normalize_duplications" => Linear::Normalizer.Task(method(:normalize_duplications)),
22
+ "path.magnetic_to" => Linear::Normalizer.Task(method(:normalize_magnetic_to)),
23
+ "path.wirings" => Linear::Normalizer.Task(Linear::Normalizer.method(:compile_wirings)),
24
+ }
15
25
  end
16
26
 
17
27
  def start_sequence(track_name:)
18
28
  start_default = Activity::Start.new(semantic: :default)
19
29
  start_event = Linear::Sequence.create_row(task: start_default, id: "Start.default", magnetic_to: nil, wirings: [Linear::Search::Forward(unary_outputs[:success], track_name)])
20
- _sequence = Linear::Sequence[start_event]
30
+ _sequence = Linear::Sequence[start_event]
21
31
  end
22
32
 
23
33
  # DISCUSS: still not sure this should sit here.
24
34
  # Pseudo-DSL that prepends {steps} to {sequence}.
25
- def prepend_to_path(sequence, steps, insertion_method=Linear::Insert.method(:Prepend), insert_id="End.success")
35
+ def prepend_to_path(sequence, steps, insertion_method=Linear::Insert.method(:Prepend), insert_id="End.success") # FIXME: where do we need you?
26
36
  new_rows = steps.collect do |id, task|
27
37
  Linear::Sequence.create_row(
28
38
  task: task,
@@ -43,32 +53,24 @@ module Trailblazer
43
53
  {success: [Linear::Search.method(:Forward), track_name]}
44
54
  end
45
55
 
46
- def merge_path_outputs((ctx, flow_options), *)
47
- ctx = {outputs: unary_outputs}.merge(ctx)
48
-
49
- return Right, [ctx, flow_options]
56
+ def merge_path_outputs(ctx, outputs: nil, **)
57
+ ctx[:outputs] = outputs || unary_outputs
50
58
  end
51
59
 
52
- def merge_path_connections((ctx, flow_options), *)
53
- raise unless track_name = ctx[:track_name]# TODO: make track_name required kw.
54
- ctx = {connections: unary_connections(track_name: track_name)}.merge(ctx)
55
-
56
- return Right, [ctx, flow_options]
60
+ def merge_path_connections(ctx, track_name:, connections: nil, **)
61
+ ctx[:connections] = connections || unary_connections(track_name: track_name)
57
62
  end
58
63
 
59
64
  # Processes {:before,:after,:replace,:delete} options and
60
65
  # defaults to {before: "End.success"} which, yeah.
61
- def normalize_sequence_insert((ctx, flow_options), *)
66
+ def normalize_sequence_insert(ctx, end_id:, **)
62
67
  insertion = ctx.keys & sequence_insert_options.keys
63
68
  insertion = insertion[0] || :before
64
- raise if ctx[:end_id].nil? # FIXME
65
- target = ctx[insertion] || ctx[:end_id]
69
+ target = ctx[insertion] || end_id
66
70
 
67
71
  insertion_method = sequence_insert_options[insertion]
68
72
 
69
- ctx = ctx.merge(sequence_insert: [Linear::Insert.method(insertion_method), target])
70
-
71
- return Right, [ctx, flow_options]
73
+ ctx[:sequence_insert] = [Linear::Insert.method(insertion_method), target]
72
74
  end
73
75
 
74
76
  # @private
@@ -81,51 +83,25 @@ module Trailblazer
81
83
  }
82
84
  end
83
85
 
84
- def normalize_duplications((ctx, flow_options), *)
85
- return Right, [ctx, flow_options] if ctx[:replace]
86
-
87
- signal, (ctx, flow_options) = raise_on_duplicate_id([ctx, flow_options])
88
- signal, (ctx, flow_options) = clone_duplicate_activity([ctx, flow_options])
86
+ def normalize_duplications(ctx, replace: false, **)
87
+ return if replace
89
88
 
90
- return signal, [ctx, flow_options]
89
+ raise_on_duplicate_id(ctx, **ctx)
90
+ clone_duplicate_activity(ctx, **ctx) # DISCUSS: mutates {ctx}.
91
91
  end
92
92
 
93
- def raise_on_duplicate_id((ctx, flow_options), *)
94
- id, sequence = ctx[:id], ctx[:sequence]
93
+ def raise_on_duplicate_id(ctx, id:, sequence:, **)
95
94
  raise "ID #{id} is already taken. Please specify an `:id`." if sequence.find { |row| row[3][:id] == id }
96
-
97
- return Right, [ctx, flow_options]
98
- end
99
-
100
- def clone_duplicate_activity((ctx, flow_options), *)
101
- return Right, [ctx, flow_options] unless ctx[:task].is_a?(Class)
102
-
103
- task, sequence = ctx[:task], ctx[:sequence]
104
- ctx = ctx.merge(task: task.clone) if sequence.find { |row| row[1] == task }
105
-
106
- return Right, [ctx, flow_options]
107
95
  end
108
96
 
109
- def normalize_magnetic_to((ctx, flow_options), *) # TODO: merge with Railway.merge_magnetic_to
110
- raise unless track_name = ctx[:track_name]# TODO: make track_name required kw.
111
-
112
- ctx = {magnetic_to: track_name}.merge(ctx)
97
+ def clone_duplicate_activity(ctx, task:, sequence:, **)
98
+ return unless task.is_a?(Class)
113
99
 
114
- return Right, [ctx, flow_options]
100
+ ctx[:task] = task.clone if sequence.find { |row| row[1] == task }
115
101
  end
116
102
 
117
- # Return {Path::Normalizer} sequence.
118
- def prepend_step_options(sequence)
119
- prepend_to_path(
120
- sequence,
121
-
122
- "path.outputs" => method(:merge_path_outputs),
123
- "path.connections" => method(:merge_path_connections),
124
- "path.sequence_insert" => method(:normalize_sequence_insert),
125
- "path.normalize_duplications" => method(:normalize_duplications),
126
- "path.magnetic_to" => method(:normalize_magnetic_to),
127
- "path.wirings" => Linear::Normalizer.method(:compile_wirings),
128
- )
103
+ def normalize_magnetic_to(ctx, track_name:, **) # TODO: merge with Railway.merge_magnetic_to
104
+ ctx[:magnetic_to] = ctx.key?(:magnetic_to) ? ctx[:magnetic_to] : track_name # FIXME: can we be magnetic_to {nil}?
129
105
  end
130
106
 
131
107
  # Returns an initial two-step sequence with {Start.default > End.success}.
@@ -161,7 +137,7 @@ module Trailblazer
161
137
  # DISCUSS: maybe make this a function?
162
138
  # These are the normalizers for an {Activity}, to be injected into a State.
163
139
  Normalizers = Linear::State::Normalizer.new(
164
- step: Linear::Normalizer.activity_normalizer( Path::DSL.normalizer ), # here, we extend the generic FastTrack::step_normalizer with the Activity-specific DSL
140
+ step: Linear::Normalizer.activity_normalizer(Path::DSL.normalizer), # here, we extend the generic FastTrack::step_normalizer with the Activity-specific DSL
165
141
  )
166
142
 
167
143
  # pp Normalizers
@@ -198,7 +174,7 @@ module Trailblazer
198
174
  initialize!(Path::DSL::State.new(**DSL.OptionsForState()))
199
175
  end # Path
200
176
 
201
- def self.Path(options)
177
+ def self.Path(**options)
202
178
  Class.new(Path) do
203
179
  initialize!(Path::DSL::State.new(**Path::DSL.OptionsForState(**options)))
204
180
  end
@@ -15,95 +15,74 @@ module Trailblazer
15
15
  # TODO: make this easier, even at this step.
16
16
 
17
17
  def normalizer_for_fail
18
- sequence = normalizer
19
-
20
- id = "railway.magnetic_to.fail"
21
- task = Fail.method(:merge_magnetic_to)
22
-
23
- # TODO: use prepend_to_path
24
- sequence = Linear::DSL.insert_task(sequence,
25
- task: task,
26
- magnetic_to: :success, id: id,
27
- wirings: [Linear::Search.Forward(Path::DSL.unary_outputs[:success], :success)],
28
- sequence_insert: [Linear::Insert.method(:Prepend), "path.wirings"])
29
-
30
- id = "railway.connections.fail.success_to_failure"
31
- task = Fail.method(:connect_success_to_failure)
32
-
33
- sequence = Linear::DSL.insert_task(sequence,
34
- task: task,
35
- magnetic_to: :success, id: id,
36
- wirings: [Linear::Search.Forward(Path::DSL.unary_outputs[:success], :success)],
37
- sequence_insert: [Linear::Insert.method(:Replace), "path.connections"])
18
+ pipeline = TaskWrap::Pipeline.prepend(
19
+ normalizer,
20
+ "path.wirings",
21
+ {
22
+ "railway.magnetic_to.fail" => Linear::Normalizer.Task(Fail.method(:merge_magnetic_to)),
23
+ }
24
+ )
25
+
26
+ pipeline = TaskWrap::Pipeline.prepend(
27
+ pipeline,
28
+ "path.connections",
29
+ {
30
+ "railway.connections.fail.success_to_failure" => Linear::Normalizer.Task(Fail.method(:connect_success_to_failure)),
31
+ },
32
+ replace: 1 # replace {"path.connections"}
33
+ )
38
34
  end
39
35
 
40
36
  def normalizer_for_pass
41
- sequence = normalizer
42
-
43
- id = "railway.connections.pass.failure_to_success"
44
- task = Pass.method(:connect_failure_to_success)
45
-
46
- sequence = Linear::DSL.insert_task(sequence,
47
- task: task,
48
- magnetic_to: :success, id: id,
49
- wirings: [Linear::Search.Forward(Path::DSL.unary_outputs[:success], :success)],
50
- sequence_insert: [Linear::Insert.method(:Append), "path.connections"])
37
+ pipeline = TaskWrap::Pipeline.insert_after(
38
+ normalizer,
39
+ "path.connections",
40
+ ["railway.connections.pass.failure_to_success", Linear::Normalizer.Task(Pass.method(:connect_failure_to_success))],
41
+ )
51
42
  end
52
43
 
53
44
  module Fail
54
45
  module_function
55
46
 
56
- def merge_magnetic_to((ctx, flow_options), *)
57
- ctx = ctx.merge(magnetic_to: :failure)
58
-
59
- return Right, [ctx, flow_options]
47
+ def merge_magnetic_to(ctx, **)
48
+ ctx[:magnetic_to] = :failure
60
49
  end
61
50
 
62
- def connect_success_to_failure((ctx, flow_options), *)
63
- ctx = {connections: {success: [Linear::Search.method(:Forward), :failure]}}.merge(ctx)
64
-
65
- return Right, [ctx, flow_options]
51
+ def connect_success_to_failure(ctx, connections: nil, **)
52
+ ctx[:connections] = connections || {success: [Linear::Search.method(:Forward), :failure]}
66
53
  end
67
54
  end
68
55
 
69
56
  module Pass
70
57
  module_function
71
58
 
72
- def connect_failure_to_success((ctx, flow_options), *)
73
- connections = ctx[:connections].merge({failure: [Linear::Search.method(:Forward), :success]})
74
-
75
- return Right, [ctx.merge(connections: connections), flow_options]
59
+ def connect_failure_to_success(ctx, connections:, **)
60
+ ctx[:connections] = connections.merge({failure: [Linear::Search.method(:Forward), :success]})
76
61
  end
77
62
  end
78
63
 
79
64
  # Add {Railway} steps to normalizer path.
80
- def step_options(sequence)
81
- Path::DSL.prepend_to_path( # this doesn't particularly put the steps after the Path steps.
82
- sequence,
83
-
65
+ def step_options(pipeline)
66
+ TaskWrap::Pipeline.prepend(
67
+ pipeline,
68
+ "path.wirings",
84
69
  {
85
- "railway.outputs" => method(:normalize_path_outputs),
86
- "railway.connections" => method(:normalize_path_connections),
70
+ "railway.outputs" => Linear::Normalizer.Task(method(:normalize_path_outputs)),
71
+ "railway.connections" => Linear::Normalizer.Task(method(:normalize_path_connections)),
87
72
  },
88
-
89
- Linear::Insert.method(:Prepend), "path.wirings" # override where it's added.
90
73
  )
91
74
  end
92
75
 
93
76
  # Add {:failure} output to {:outputs}.
94
77
  # TODO: assert that failure_outputs doesn't override existing {:outputs}
95
- def normalize_path_outputs((ctx, flow_options), *)
96
- outputs = failure_outputs.merge(ctx[:outputs])
97
- ctx = ctx.merge(outputs: outputs)
78
+ def normalize_path_outputs(ctx, outputs:, **)
79
+ outputs = failure_outputs.merge(outputs)
98
80
 
99
- return Right, [ctx, flow_options]
81
+ ctx[:outputs] = outputs
100
82
  end
101
83
 
102
- def normalize_path_connections((ctx, flow_options), *)
103
- connections = failure_connections.merge(ctx[:connections])
104
- ctx = ctx.merge(connections: connections)
105
-
106
- return Right, [ctx, flow_options]
84
+ def normalize_path_connections(ctx, connections:, **)
85
+ ctx[:connections] = failure_connections.merge(connections)
107
86
  end
108
87
 
109
88
  def failure_outputs
@@ -115,16 +94,16 @@ module Trailblazer
115
94
 
116
95
  def initial_sequence(failure_end:, initial_sequence:, **path_options)
117
96
  # TODO: this could be an Activity itself but maybe a bit too much for now.
118
- sequence = Path::DSL.append_end(initial_sequence, task: failure_end, magnetic_to: :failure, id: "End.failure")
97
+ _seq = Path::DSL.append_end(initial_sequence, task: failure_end, magnetic_to: :failure, id: "End.failure")
119
98
  end
120
99
 
121
100
  class State < Path::DSL::State
122
101
  def fail(*args)
123
- seq = Linear::Strategy.task_for!(self, :fail, *args) # mutate @state
102
+ _seq = Linear::Strategy.task_for!(self, :fail, *args) # mutate @state
124
103
  end
125
104
 
126
105
  def pass(*args)
127
- seq = Linear::Strategy.task_for!(self, :pass, *args) # mutate @state
106
+ _seq = Linear::Strategy.task_for!(self, :pass, *args) # mutate @state
128
107
  end
129
108
  end # Instance
130
109
 
@@ -162,7 +141,6 @@ module Trailblazer
162
141
  extend DSL::Linear::Strategy
163
142
 
164
143
  initialize!(Railway::DSL::State.new(**DSL.OptionsForState()))
165
-
166
144
  end # Railway
167
145
 
168
146
  def self.Railway(options)
@@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
19
19
  end
20
20
  spec.require_paths = ["lib"]
21
21
 
22
- spec.add_dependency "trailblazer-activity", ">= 0.12.2", "< 0.13.0"
22
+ spec.add_dependency "trailblazer-activity", ">= 0.13.0", "< 1.0.0"
23
23
 
24
24
  spec.add_development_dependency "bundler"
25
25
  spec.add_development_dependency "minitest", "~> 5.0"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: trailblazer-activity-dsl-linear
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.3
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nick Sutterer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-11-26 00:00:00.000000000 Z
11
+ date: 2021-12-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: trailblazer-activity
@@ -16,20 +16,20 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 0.12.2
19
+ version: 0.13.0
20
20
  - - "<"
21
21
  - !ruby/object:Gem::Version
22
- version: 0.13.0
22
+ version: 1.0.0
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
26
26
  requirements:
27
27
  - - ">="
28
28
  - !ruby/object:Gem::Version
29
- version: 0.12.2
29
+ version: 0.13.0
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
- version: 0.13.0
32
+ version: 1.0.0
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: bundler
35
35
  requirement: !ruby/object:Gem::Requirement