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 +4 -4
- data/CHANGES.md +26 -0
- data/Gemfile +1 -0
- data/README.md +77 -13
- data/lib/trailblazer/activity/dsl/linear.rb +33 -4
- data/lib/trailblazer/activity/dsl/linear/helper.rb +139 -92
- data/lib/trailblazer/activity/dsl/linear/normalizer.rb +35 -5
- data/lib/trailblazer/activity/dsl/linear/state.rb +15 -3
- data/lib/trailblazer/activity/dsl/linear/strategy.rb +17 -13
- data/lib/trailblazer/activity/dsl/linear/version.rb +1 -1
- data/lib/trailblazer/activity/fast_track.rb +1 -0
- data/lib/trailblazer/activity/path.rb +1 -0
- data/lib/trailblazer/activity/railway.rb +3 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f2dd213289e88821986b93fc8421474654537f47cee570eb1d3b14e8a6b22020
|
4
|
+
data.tar.gz: 8e64b5daeec8cf1781c4e0411b9bba9a482bdd66a52509281996615ec55eb708
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
#
|
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)
|
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
|
-
|
12
|
+
## Example
|
7
13
|
|
8
|
-
|
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
|
-
|
16
|
+
```ruby
|
17
|
+
require "trailblazer-activity"
|
18
|
+
require "trailblazer-activity-dsl-linear"
|
11
19
|
|
12
|
-
|
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
|
-
|
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
|
-
|
17
|
-
|
33
|
+
def save(ctx, model:, params:, **)
|
34
|
+
model.update_attributes(params)
|
35
|
+
end
|
18
36
|
|
19
|
-
|
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
|
-
|
23
|
-
|
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
|
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
|
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]
|
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
|
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
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
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
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
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,
|
172
|
-
|
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
|
-
|
62
|
-
|
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
|
-
|
68
|
+
|
69
|
+
[output, Linear.Path(**shared_options, **proxy.options, &(proxy.block || block))] # FIXME: the || sucks.
|
65
70
|
end
|
66
71
|
|
67
|
-
|
68
|
-
end
|
72
|
+
evaluated_options = Hash[evaluated_options]
|
69
73
|
|
70
|
-
|
71
|
-
|
74
|
+
return args[0], options.merge(evaluated_options)
|
75
|
+
end
|
72
76
|
|
73
|
-
def Path(options) # we can't access {
|
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
|
@@ -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.
|
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:
|
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.
|
139
|
+
rubygems_version: 2.7.6
|
140
140
|
signing_key:
|
141
141
|
specification_version: 4
|
142
142
|
summary: Simple DSL to define Trailblazer activities.
|