trailblazer-activity-dsl-linear 0.2.3 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/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.
|