trailblazer-activity-dsl-linear 0.4.3 → 0.5.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 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