trailblazer-activity-dsl-linear 0.2.3 → 0.2.8

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: c0cf822fabc3908a1f4d17cb3763de0e8207707fdca0fc70e8e044bb25648e2a
4
- data.tar.gz: f593d6ebf87bd6557412f0bb3416281393c8b2bdb65531f71fba8b3dca72e560
3
+ metadata.gz: f2dd213289e88821986b93fc8421474654537f47cee570eb1d3b14e8a6b22020
4
+ data.tar.gz: 8e64b5daeec8cf1781c4e0411b9bba9a482bdd66a52509281996615ec55eb708
5
5
  SHA512:
6
- metadata.gz: 46615d999f5352420c35ef394a747e2e5d1fdcaa6e66e73184ab6c05f3f3e2c1137f52f540914a4f6f96a928a0dc279f508a4cef662b1bbab135b32fcba8e9e3
7
- data.tar.gz: 2fc9847a11f5be34e940fcc489a53630835042ba6567b70440e1149f27e1a5dfcf3c284415c0be05e1ea431ab2f623e6c29176f6781222cabb103892a3442af9
6
+ metadata.gz: 3a8bece3d93808c8267f18200dbb55556f8916d6f0071a5d53f33a462fc34c0d9084c0a1d4ffcef084f459bf738e08b7f358ff189d4e86f3abd716aaa688f611
7
+ data.tar.gz: d6758434610eeb29d00f31744e250b26d4258364d17aaefaf6ea8e3355040a27b7b8adc453514272d076e53aeb313ec7af56573ff57b99e136e6b8941a7bde3a
data/CHANGES.md CHANGED
@@ -1,3 +1,29 @@
1
+ # 0.2.8
2
+
3
+ * Add `Track(:color, wrap_around: true)` option and `Search::WrapAround` so you can find a certain track color (or the beginning of a Path) even when the path was positioned before the actual step in the `Sequence`.
4
+ * Add `:inherit` option so `step` can override an existing step while inheriting the original `:extensions` and `:connections` (which are the `Outputs`). This is great to customize "template" activities.
5
+
6
+ # 0.2.7
7
+
8
+ * `Did you mean ?` suggestions on Linear::Sequence::IndexError.
9
+ * Introduce `Linear::Helper` module for strategy extensions in third-party gems.
10
+ * Convenient way to patch Subprocess itself using `patch` option.
11
+ * Allow multiple `Path()` macro per step.
12
+ * Small fix for defining instance methods as steps using circuit interface.
13
+
14
+ # 0.2.6
15
+
16
+ * Added `@fields` to `Linear::State` to save arbitrary data on the activity/strategy level.
17
+
18
+ # 0.2.5
19
+
20
+ * Patching now requires the [`:patch` option for `Subprocess`](http://2019.trailblazer.to/2.1/docs/activity.html#activity-dsl-options-patching
21
+ ).
22
+
23
+ # 0.2.4
24
+
25
+ * Add a minimal API for patching nested activities. This allows customizing deeply-nested activities without changing the original.
26
+
1
27
  # 0.2.3
2
28
 
3
29
  * Add `Strategy::invoke` which is a short-cut to `TaskWrap.invoke`. It's available as class method in all three strategies.
data/Gemfile CHANGED
@@ -10,3 +10,4 @@ gem "rubocop", require: false
10
10
  # gem "trailblazer-context", path: "../trailblazer-context"
11
11
  # gem "trailblazer-developer", path: "../trailblazer-developer"
12
12
  # gem "trailblazer-activity", path: "../trailblazer-activity"
13
+ # gem "trailblazer-activity", path: "../circuit"
data/README.md CHANGED
@@ -1,29 +1,93 @@
1
- # Trailblazer-Activity-DSL-Linear
1
+ # Activity-DSL-Linear
2
2
 
3
- _The popular Railway/Fasttrack DSL for building activities._
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)
7
+
8
+ DSLs strategies for buildig activities. It is build around [`activity`](https://github.com/trailblazer/trailblazer-activity) gem.
4
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.]
5
11
 
6
- # Overview
12
+ ## Example
7
13
 
8
- This gem allows creating activities by leveraging a handy DSL. Built-in are the strategies `Path`, the popular `Railway` and `FastTrack`. The latter is used for `Trailblazer::Operation`.
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)).
9
15
 
10
- Note that you don't need to use the DSL. You can simply create a InIm structure yourself, or use our online editor.
16
+ ```ruby
17
+ require "trailblazer-activity"
18
+ require "trailblazer-activity-dsl-linear"
11
19
 
12
- Full documentation can be found here: trailblazer.to/2.1/#dsl-linear
20
+ class Memo::Update < Trailblazer::Activity::Railway
21
+ # here goes your business logic
22
+ #
23
+ def find_model(ctx, id:, **)
24
+ ctx[:model] = Memo.find_by(id: id)
25
+ end
13
26
 
14
- ## Normalizer
27
+ def validate(ctx, params:, **)
28
+ return true if params[:body].is_a?(String) && params[:body].size > 10
29
+ ctx[:errors] = "body not long enough"
30
+ false
31
+ end
15
32
 
16
- Normalizers are itself linear activities (or "pipelines") that compute all options necessary for `DSL.insert_task`.
17
- For example, `FailFast.normalizer` will process your options such as `fast_track: true` and add necessary connections and outputs.
33
+ def save(ctx, model:, params:, **)
34
+ model.update_attributes(params)
35
+ end
18
36
 
19
- The different "step types" (think of `step`, `fail`, and `pass`) are again implemented as different normalizers that "inherit" generic steps.
37
+ def log_error(ctx, params:, **)
38
+ ctx[:log] = "Some idiot wrote #{params.inspect}"
39
+ end
20
40
 
41
+ # here comes the DSL describing the layout of the activity
42
+ #
43
+ step :find_model
44
+ step :validate, Output(:failure) => End(:validation_error)
45
+ step :save
46
+ fail :log_error
47
+ end
48
+ ```
21
49
 
22
- `:sequence_insert`
23
- `:connections` are callables to find the connecting tasks
50
+ Visually, this would translate to the following circuit.
51
+
52
+ <img src="http://trailblazer.to/images/2.1/activity-readme-example.png">
53
+
54
+ You can run the activity by invoking its `call` method.
55
+
56
+ ```ruby
57
+ ctx = { id: 1, params: { body: "Awesome!" } }
58
+
59
+ signal, (ctx, *) = Update.( [ctx, {}] )
60
+
61
+ pp ctx #=>
62
+ {:id=>1,
63
+ :params=>{:body=>"Awesome!"},
64
+ :model=>#<Memo body=nil>,
65
+ :errors=>"body not long enough"}
66
+
67
+ pp signal #=> #<struct Trailblazer::Activity::End semantic=:validation_error>
68
+ ```
69
+
70
+ With Activity, modeling business processes turns out to be ridiculously simple: You define what should happen and when, and Trailblazer makes sure _that_ it happens.
71
+
72
+ ## Features
73
+
74
+ * Activities can model any process with arbitrary flow and connections.
75
+ * Nesting and compositions are allowed and encouraged (via Trailblazer's [`dsl-linear`](https://github.com/trailblazer/trailblazer-activity-dsl-linear) gem).
76
+ * Different step interfaces, manual processing of DSL options, etc is all possible.
77
+ * Steps can be any kind of callable objects.
78
+ * Tracing! (via Trailblazer's [`developer`](https://github.com/trailblazer/trailblazer-developer) gem)
79
+
80
+ ## Operation
81
+
82
+ Trailblazer's [`Operation`](https://2019.trailblazer.to/2.1/docs/operation.html#operation-overview) internally uses an activity to model the processes.
83
+
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.
86
+
87
+ <img src="http://2019.trailblazer.to/2.1/dist/img/flow.png">
24
88
 
25
89
  ## License
26
90
 
27
91
  © Copyright 2018, Trailblazer GmbH
28
92
 
29
- Licensed under the LGPLv3 license. We also offer a [commercial-friendly license](http://trailblazer.to/pro).
93
+ Licensed under the LGPLv3 license. We also offer a commercial-friendly [license](https://2019.trailblazer.to/2.1/docs/pro.html#pro-license).
@@ -37,7 +37,17 @@ class Trailblazer::Activity
37
37
  sequence
38
38
  end
39
39
 
40
- class IndexError < IndexError; end
40
+ class IndexError < IndexError
41
+ attr_reader :step_id
42
+
43
+ def initialize(sequence, step_id)
44
+ @step_id = step_id
45
+ valid_ids = sequence.collect{ |row| row[3][:id].inspect }
46
+
47
+ message = %{#{@step_id.inspect} is not a valid step ID. Did you mean any of these ?\n#{valid_ids.join("\n")}}
48
+ super(message)
49
+ end
50
+ end
41
51
  end
42
52
 
43
53
  # Sequence
@@ -48,7 +58,21 @@ class Trailblazer::Activity
48
58
  # Note that we only go forward, no back-references are done here.
49
59
  def Forward(output, target_color)
50
60
  ->(sequence, me) do
51
- target_seq_row = sequence[sequence.index(me)+1..-1].find { |seq_row| seq_row[0] == target_color }
61
+ target_seq_row = find_in_range(sequence[sequence.index(me)+1..-1], target_color)
62
+
63
+ return output, target_seq_row
64
+ end
65
+ end
66
+
67
+ # Tries to find a track colored step by doing a Forward-search, first, then wraps around going
68
+ # through all steps from sequence start to self.
69
+ def WrapAround(output, target_color)
70
+ ->(sequence, me) do
71
+ my_index = sequence.index(me)
72
+ # First, try all elements after me, then go through the elements preceding myself.
73
+ wrapped_range = sequence[my_index+1..-1] + sequence[0..my_index-1]
74
+
75
+ target_seq_row = find_in_range(wrapped_range, target_color)
52
76
 
53
77
  return output, target_seq_row
54
78
  end
@@ -69,6 +93,11 @@ class Trailblazer::Activity
69
93
  return output, target_seq_row
70
94
  end
71
95
  end
96
+
97
+ # @private
98
+ def find_in_range(range, target_color)
99
+ target_seq_row = range.find { |seq_row| seq_row[0] == target_color }
100
+ end
72
101
  end # Search
73
102
 
74
103
  # Sequence
@@ -111,7 +140,7 @@ class Trailblazer::Activity
111
140
  end
112
141
 
113
142
  def find(sequence, insert_id)
114
- index = find_index(sequence, insert_id) or raise Sequence::IndexError.new(insert_id.inspect)
143
+ index = find_index(sequence, insert_id) or raise Sequence::IndexError.new(sequence, insert_id)
115
144
 
116
145
  return index, sequence.clone # Ruby doesn't have an easy way to avoid mutating arrays :(
117
146
  end
@@ -157,10 +186,10 @@ end
157
186
 
158
187
  require "trailblazer/activity/dsl/linear/normalizer"
159
188
  require "trailblazer/activity/dsl/linear/state"
189
+ require "trailblazer/activity/dsl/linear/helper"
160
190
  require "trailblazer/activity/dsl/linear/strategy"
161
191
  require "trailblazer/activity/dsl/linear/compiler"
162
192
  require "trailblazer/activity/path"
163
193
  require "trailblazer/activity/railway"
164
194
  require "trailblazer/activity/fast_track"
165
- require "trailblazer/activity/dsl/linear/helper" # FIXME
166
195
  require "trailblazer/activity/dsl/linear/variable_mapping"
@@ -1,97 +1,144 @@
1
1
  module Trailblazer
2
2
  class Activity
3
- module DSL::Linear # TODO: rename!
4
- # @api private
5
- OutputSemantic = Struct.new(:value)
6
- Id = Struct.new(:value)
7
- Track = Struct.new(:color, :adds)
8
- Extension = Struct.new(:callable) do
9
- def call(*args, &block)
10
- callable.(*args, &block)
11
- end
12
- end
13
-
14
- # Shortcut functions for the DSL.
15
- module_function
16
-
17
- # Output( Left, :failure )
18
- # Output( :failure ) #=> Output::Semantic
19
- def Output(signal, semantic=nil)
20
- return OutputSemantic.new(signal) if semantic.nil?
21
-
22
- Activity.Output(signal, semantic)
23
- end
24
-
25
- def End(semantic)
26
- Activity.End(semantic)
27
- end
28
-
29
- def end_id(_end)
30
- "End.#{_end.to_h[:semantic]}" # TODO: use everywhere
31
- end
32
-
33
- def Track(color)
34
- Track.new(color, []).freeze
35
- end
36
-
37
- def Id(id)
38
- Id.new(id).freeze
39
- end
40
-
41
- def Path(track_color: "track_#{rand}", end_id:"path_end_#{rand}", connect_to:nil, **options, &block)
42
- # DISCUSS: here, we use the global normalizer and don't allow injection.
43
- state = Activity::Path::DSL::State.new(Activity::Path::DSL.OptionsForState(track_name: track_color, end_id: end_id, **options)) # TODO: test injecting {:normalizers}.
44
-
45
- # seq = block.call(state) # state changes.
46
- state.instance_exec(&block)
47
-
48
- seq = state.to_h[:sequence]
49
-
50
- _end_id =
51
- if connect_to
52
- end_id
53
- else
54
- nil
3
+ module DSL
4
+ module Linear
5
+ module Helper
6
+ # @api private
7
+ OutputSemantic = Struct.new(:value)
8
+ Id = Struct.new(:value)
9
+ Track = Struct.new(:color, :adds, :options)
10
+ Extension = Struct.new(:callable) do
11
+ def call(*args, &block)
12
+ callable.(*args, &block)
13
+ end
55
14
  end
56
15
 
57
- seq = strip_start_and_ends(seq, end_id: _end_id) # don't cut off end unless {:connect_to} is set.
58
-
59
- if connect_to
60
- output, _ = seq[-1][2][0].(seq, seq[-1]) # FIXME: the Forward() proc contains the row's Output, and the only current way to retrieve it is calling the search strategy. It should be Forward#to_h
61
-
62
- searches = [Search.ById(output, connect_to.value)]
63
-
64
- row = seq[-1]
65
- row = row[0..1] + [searches] + [row[3]] # FIXME: not mutating an array is so hard: we only want to replace the "searches" element, index 2
66
-
67
- seq = seq[0..-2] + [row]
68
- end
69
-
70
- # Add the path before End.success - not sure this is bullet-proof.
71
- adds = seq.collect do |row|
72
- {
73
- row: row,
74
- insert: [Insert.method(:Prepend), "End.success"]
75
- }
76
- end
77
-
78
- # Connect the Output() => Track(path_track)
79
- return Track.new(track_color, adds)
80
- end
81
-
82
- # Computes the {:outputs} options for {activity}.
83
- def Subprocess(activity)
84
- {
85
- task: activity,
86
- outputs: Hash[activity.to_h[:outputs].collect { |output| [output.semantic, output] }]
87
- }
88
- end
89
-
90
- def normalize(options, local_keys) # TODO: test me.
91
- locals = options.reject { |key, value| ! local_keys.include?(key) }
92
- foreign = options.reject { |key, value| local_keys.include?(key) }
93
- return foreign, locals
94
- end
95
- end
96
- end
16
+ def self.included(base)
17
+ base.extend ClassMethods
18
+ end
19
+
20
+ # Shortcut functions for the DSL.
21
+ module ClassMethods
22
+ # Output( Left, :failure )
23
+ # Output( :failure ) #=> Output::Semantic
24
+ def Output(signal, semantic=nil)
25
+ return OutputSemantic.new(signal) if semantic.nil?
26
+
27
+ Activity.Output(signal, semantic)
28
+ end
29
+
30
+ def End(semantic)
31
+ Activity.End(semantic)
32
+ end
33
+
34
+ def end_id(_end)
35
+ "End.#{_end.to_h[:semantic]}" # TODO: use everywhere
36
+ end
37
+
38
+ def Track(color, wrap_around: false)
39
+ Track.new(color, [], wrap_around: wrap_around).freeze
40
+ end
41
+
42
+ def Id(id)
43
+ Id.new(id).freeze
44
+ end
45
+
46
+ def Path(track_color: "track_#{rand}", end_id:"path_end_#{rand}", connect_to:nil, **options, &block)
47
+ # DISCUSS: here, we use the global normalizer and don't allow injection.
48
+ state = Activity::Path::DSL::State.new(Activity::Path::DSL.OptionsForState(track_name: track_color, end_id: end_id, **options)) # TODO: test injecting {:normalizers}.
49
+
50
+ # seq = block.call(state) # state changes.
51
+ state.instance_exec(&block)
52
+
53
+ seq = state.to_h[:sequence]
54
+
55
+ _end_id =
56
+ if connect_to
57
+ end_id
58
+ else
59
+ nil
60
+ end
61
+
62
+ seq = strip_start_and_ends(seq, end_id: _end_id) # don't cut off end unless {:connect_to} is set.
63
+
64
+ if connect_to
65
+ output, _ = seq[-1][2][0].(seq, seq[-1]) # FIXME: the Forward() proc contains the row's Output, and the only current way to retrieve it is calling the search strategy. It should be Forward#to_h
66
+
67
+ searches = [Search.ById(output, connect_to.value)]
68
+
69
+ row = seq[-1]
70
+ row = row[0..1] + [searches] + [row[3]] # FIXME: not mutating an array is so hard: we only want to replace the "searches" element, index 2
71
+
72
+ seq = seq[0..-2] + [row]
73
+ end
74
+
75
+ # Add the path before End.success - not sure this is bullet-proof.
76
+ adds = seq.collect do |row|
77
+ {
78
+ row: row,
79
+ insert: [Insert.method(:Prepend), "End.success"]
80
+ }
81
+ end
82
+
83
+ # Connect the Output() => Track(path_track)
84
+ return Track.new(track_color, adds, {})
85
+ end
86
+
87
+ # Computes the {:outputs} options for {activity}.
88
+ def Subprocess(activity, patch: {})
89
+ activity = Patch.customize(activity, options: patch)
90
+
91
+ {
92
+ task: activity,
93
+ outputs: Hash[activity.to_h[:outputs].collect { |output| [output.semantic, output] }]
94
+ }
95
+ end
96
+
97
+ module Patch
98
+ module_function
99
+
100
+ def customize(activity, options:)
101
+ options = options.is_a?(Proc) ?
102
+ { [] => options } : # hash-wrapping with empty path, for patching given activity itself
103
+ options
104
+
105
+ options.each do |path, patch|
106
+ activity = call(activity, path, patch) # TODO: test if multiple patches works!
107
+ end
108
+
109
+ activity
110
+ end
111
+
112
+ def call(activity, path, customization)
113
+ task_id, *path = path
114
+
115
+ patch =
116
+ if task_id
117
+ segment_activity = Introspect::Graph(activity).find(task_id).task
118
+ patched_segment_activity = call(segment_activity, path, customization)
119
+
120
+ # Replace the patched subprocess.
121
+ -> { step Subprocess(patched_segment_activity), replace: task_id, id: task_id }
122
+ else
123
+ customization # apply the *actual* patch from the Subprocess() call.
124
+ end
125
+
126
+ patched_activity = Class.new(activity)
127
+ patched_activity.class_exec(&patch)
128
+ patched_activity
129
+ end
130
+ end
131
+
132
+ def normalize(options, local_keys) # TODO: test me.
133
+ locals = options.reject { |key, value| ! local_keys.include?(key) }
134
+ foreign = options.reject { |key, value| local_keys.include?(key) }
135
+ return foreign, locals
136
+ end
137
+ end
138
+ end # Helper
139
+
140
+ include Helper # Introduce Helper constants in DSL::Linear scope
141
+ end # Linear
142
+ end # DSL
143
+ end # Activity
97
144
  end
@@ -20,6 +20,7 @@ module Trailblazer
20
20
  "activity.normalize_id" => method(:normalize_id),
21
21
  "activity.normalize_override" => method(:normalize_override),
22
22
  "activity.wrap_task_with_step_interface" => method(:wrap_task_with_step_interface), # last
23
+ "activity.inherit_option" => method(:inherit_option),
23
24
  },
24
25
 
25
26
  Linear::Insert.method(:Append), "Start.default"
@@ -53,7 +54,13 @@ module Trailblazer
53
54
  # Specific to the "step DSL": if the first argument is a callable, wrap it in a {step_interface_builder}
54
55
  # since its interface expects the step interface, but the circuit will call it with circuit interface.
55
56
  def normalize_step_interface((ctx, flow_options), *)
56
- options = ctx[:options] # either a <#task> or {} from macro
57
+ options = case (step_args = ctx[:options]) # either a <#task> or {} from macro
58
+ when Hash
59
+ # extract task for interfaces like `step task: :instance_method_name`
60
+ step_args[:task].is_a?(Symbol) ? step_args[:task] : step_args
61
+ else
62
+ step_args
63
+ end
57
64
 
58
65
  unless options.is_a?(::Hash)
59
66
  # task = wrap_with_step_interface(task: options, step_interface_builder: ctx[:user_options][:step_interface_builder]) # TODO: make this optional with appropriate wiring.
@@ -122,7 +129,7 @@ module Trailblazer
122
129
 
123
130
  ctx[:wirings] =
124
131
  connections.collect do |semantic, (search_strategy_builder, *search_args)|
125
- output = outputs[semantic] || raise("No `#{semantic}` output found for #{outputs.inspect}")
132
+ output = outputs[semantic] || raise("No `#{semantic}` output found for #{ctx[:id].inspect} and outputs #{outputs.inspect}")
126
133
 
127
134
  search_strategy_builder.( # return proc to be called when compiling Seq, e.g. {ById(output, :id)}
128
135
  output,
@@ -157,6 +164,8 @@ module Trailblazer
157
164
  _adds = [add_end(cfg, magnetic_to: end_id, id: end_id)] unless end_exists
158
165
 
159
166
  [output_to_id(ctx, output, end_id), _adds]
167
+ else
168
+ raise cfg.inspect
160
169
  end
161
170
 
162
171
  connections = connections.merge(new_connections)
@@ -168,8 +177,10 @@ module Trailblazer
168
177
  return Trailblazer::Activity::Right, [new_ctx, flow_options]
169
178
  end
170
179
 
171
- def output_to_track(ctx, output, target)
172
- {output.value => [Linear::Search.method(:Forward), target.color]}
180
+ def output_to_track(ctx, output, track)
181
+ search_strategy = track.options[:wrap_around] ? :WrapAround : :Forward
182
+
183
+ {output.value => [Linear::Search.method(search_strategy), track.color]}
173
184
  end
174
185
 
175
186
  def output_to_id(ctx, output, target)
@@ -219,9 +230,28 @@ module Trailblazer
219
230
  return Trailblazer::Activity::Right, [ctx.merge(new_ctx), flow_options]
220
231
  end
221
232
 
233
+ # Currently, the {:inherit} option copies over {:connections} from the original step
234
+ # and merges them with the (prolly) connections passed from the user.
235
+ def inherit_option((ctx, flow_options), *)
236
+ return Trailblazer::Activity::Right, [ctx, flow_options] unless ctx[:inherit]
237
+
238
+ sequence = ctx[:sequence]
239
+ id = ctx[:id]
240
+
241
+ index = Linear::Insert.find_index(sequence, id)
242
+ row = sequence[index] # from this row we're inheriting options.
243
+
244
+ extensions = (row[3][:extensions]||[]) + (ctx[:extensions]||[]) # FIXME: DEFAULTING, FOR FUCK'S SAKE
245
+
246
+ ctx = ctx.merge(connections: row[3][:connections], extensions: extensions) # "inherit"
247
+
248
+ return Trailblazer::Activity::Right, [ctx, flow_options]
249
+ end
250
+
222
251
  # TODO: make this extendable!
223
252
  def cleanup_options((ctx, flow_options), *)
224
- new_ctx = ctx.reject { |k, v| [:connections, :outputs, :end_id, :step_interface_builder, :failure_end, :track_name, :sequence].include?(k) }
253
+ # new_ctx = ctx.reject { |k, v| [:connections, :outputs, :end_id, :step_interface_builder, :failure_end, :track_name, :sequence].include?(k) }
254
+ new_ctx = ctx.reject { |k, v| [:outputs, :end_id, :step_interface_builder, :failure_end, :track_name, :sequence].include?(k) }
225
255
 
226
256
  return Trailblazer::Activity::Right, [new_ctx, flow_options]
227
257
  end
@@ -3,28 +3,40 @@ module Trailblazer
3
3
  module DSL
4
4
  module Linear
5
5
  # A {State} instance is kept per DSL client, which usually is a subclass of {Path}, {Railway}, etc.
6
+ # State doesn't have any immutable features - all write operations to it must guarantee they only replace
7
+ # instance variables.
8
+ #
9
+ # @private
10
+ #
11
+ # DISCUSS: why do we have this structure? It doesn't cover "immutable copying", that has to be done by its clients.
12
+ # also, copy with to_h
6
13
  class State
7
14
  # remembers how to call normalizers (e.g. track_color), TaskBuilder
8
15
  # remembers sequence
9
- def initialize(normalizers:, initial_sequence:, **normalizer_options)
16
+ def initialize(normalizers:, initial_sequence:, fields: {}.freeze, **normalizer_options)
10
17
  @normalizer = normalizers # compiled normalizers.
11
18
  @sequence = initial_sequence
12
19
  @normalizer_options = normalizer_options
20
+ @fields = fields
13
21
  end
14
22
 
15
23
  # Called to "inherit" a state.
16
24
  def copy
17
- self.class.new(normalizers: @normalizer, initial_sequence: @sequence, **@normalizer_options)
25
+ self.class.new(normalizers: @normalizer, initial_sequence: @sequence, fields: @fields, **@normalizer_options)
18
26
  end
19
27
 
20
28
  def to_h
21
- {sequence: @sequence, normalizers: @normalizer, normalizer_options: @normalizer_options} # FIXME.
29
+ {sequence: @sequence, normalizers: @normalizer, normalizer_options: @normalizer_options, fields: @fields} # FIXME.
22
30
  end
23
31
 
24
32
  def update_sequence(&block)
25
33
  @sequence = yield(to_h)
26
34
  end
27
35
 
36
+ def update_options(fields)
37
+ @fields = fields
38
+ end
39
+
28
40
  # Compiles and maintains all final normalizers for a specific DSL.
29
41
  class Normalizer
30
42
  def compile_normalizer(normalizer_sequence)
@@ -1,5 +1,3 @@
1
- require "forwardable"
2
-
3
1
  module Trailblazer
4
2
  class Activity
5
3
  module DSL
@@ -29,7 +27,7 @@ module Trailblazer
29
27
  options = options.merge(dsl_track: type)
30
28
 
31
29
  # {#update_sequence} is the only way to mutate the state instance.
32
- state.update_sequence do |sequence:, normalizers:, normalizer_options:|
30
+ state.update_sequence do |sequence:, normalizers:, normalizer_options:, fields:|
33
31
  # Compute the sequence rows.
34
32
  options = normalizers.(type, normalizer_options: normalizer_options, options: task, user_options: options.merge(sequence: sequence))
35
33
 
@@ -58,23 +56,29 @@ module Trailblazer
58
56
 
59
57
  private def forward_block(args, block)
60
58
  options = args[1]
61
- if options.is_a?(Hash) # FIXME: doesn't account {task: <>} and repeats logic from Normalizer.
62
- output, proxy = (options.find { |k,v| v.is_a?(BlockProxy) } or return args)
59
+
60
+ return args unless options.is_a?(Hash)
61
+
62
+ # FIXME: doesn't account {task: <>} and repeats logic from Normalizer.
63
+
64
+ # DISCUSS: THIS SHOULD BE DONE IN DSL.Path() which is stateful! the block forwarding should be the only thing happening here!
65
+ evaluated_options =
66
+ options.find_all { |k,v| v.is_a?(BlockProxy) }.collect do |output, proxy|
63
67
  shared_options = {step_interface_builder: @state.instance_variable_get(:@normalizer_options)[:step_interface_builder]} # FIXME: how do we know what to pass on and what not?
64
- return args[0], options.merge(output => Linear.Path(**shared_options, **proxy.options, &block))
68
+
69
+ [output, Linear.Path(**shared_options, **proxy.options, &(proxy.block || block))] # FIXME: the || sucks.
65
70
  end
66
71
 
67
- args
68
- end
72
+ evaluated_options = Hash[evaluated_options]
69
73
 
70
- extend Forwardable
71
- def_delegators Linear, :Output, :End, :Track, :Id, :Subprocess
74
+ return args[0], options.merge(evaluated_options)
75
+ end
72
76
 
73
- def Path(options) # we can't access {block} here, syntactically.
74
- BlockProxy.new(options)
77
+ def Path(options, &block) # syntactically, we can't access the {do ... end} block here.
78
+ BlockProxy.new(options, block)
75
79
  end
76
80
 
77
- BlockProxy = Struct.new(:options)
81
+ BlockProxy = Struct.new(:options, :block)
78
82
 
79
83
  private def merge!(activity)
80
84
  old_seq = @state.instance_variable_get(:@sequence) # TODO: fixme
@@ -3,7 +3,7 @@ module Trailblazer
3
3
  module Activity
4
4
  module DSL
5
5
  module Linear
6
- VERSION = "0.2.3"
6
+ VERSION = "0.2.8"
7
7
  end
8
8
  end
9
9
  end
@@ -154,6 +154,7 @@ module Trailblazer
154
154
  end
155
155
  end
156
156
 
157
+ include Activity::DSL::Linear::Helper
157
158
  extend Activity::DSL::Linear::Strategy
158
159
 
159
160
  initialize!(Railway::DSL::State.new(DSL.OptionsForState()))
@@ -177,6 +177,7 @@ module Trailblazer
177
177
 
178
178
  end # DSL
179
179
 
180
+ include DSL::Linear::Helper
180
181
  extend DSL::Linear::Strategy
181
182
 
182
183
  initialize!(Path::DSL::State.new(DSL.OptionsForState()))
@@ -90,6 +90,8 @@ module Trailblazer
90
90
  )
91
91
  end
92
92
 
93
+ # Add {:failure} output to {:outputs}.
94
+ # TODO: assert that failure_outputs doesn't override existing {:outputs}
93
95
  def normalize_path_outputs((ctx, flow_options), *)
94
96
  outputs = failure_outputs.merge(ctx[:outputs])
95
97
  ctx = ctx.merge(outputs: outputs)
@@ -132,7 +134,6 @@ module Trailblazer
132
134
  pass: Linear::Normalizer.activity_normalizer( Railway::DSL.normalizer_for_pass ),
133
135
  )
134
136
 
135
-
136
137
  def self.OptionsForState(normalizers: Normalizers, failure_end: Activity::End.new(semantic: :failure), **options)
137
138
  options = Path::DSL.OptionsForState(options).
138
139
  merge(normalizers: normalizers, failure_end: failure_end)
@@ -157,6 +158,7 @@ module Trailblazer
157
158
  end
158
159
  end
159
160
 
161
+ include DSL::Linear::Helper
160
162
  extend DSL::Linear::Strategy
161
163
 
162
164
  initialize!(Railway::DSL::State.new(DSL.OptionsForState()))
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.2.3
4
+ version: 0.2.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nick Sutterer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-12-10 00:00:00.000000000 Z
11
+ date: 2020-06-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: trailblazer-activity
@@ -136,7 +136,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
136
136
  version: '0'
137
137
  requirements: []
138
138
  rubyforge_project:
139
- rubygems_version: 2.7.3
139
+ rubygems_version: 2.7.6
140
140
  signing_key:
141
141
  specification_version: 4
142
142
  summary: Simple DSL to define Trailblazer activities.