trailblazer-activity-dsl-linear 0.2.5 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGES.md +27 -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 +130 -105
- data/lib/trailblazer/activity/dsl/linear/normalizer.rb +48 -14
- 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 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fb92b49935156ee66dcf347cfe7b803cc3be0222ebf84ee676923e48bfb20ea2
|
4
|
+
data.tar.gz: 802be96cb11290483f4b326d9251933b7783f230dbf8cf6f6bf6781b64bb6a4e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3b408a672b9ad2ccbd09352ede7f47938f3506c687a7204ac762da44abd949a9788f61fd7dce096e41c222dafcbc6ee67c4ee304aede850f7f7ffcae90b42137
|
7
|
+
data.tar.gz: 4f13684ec5f770e321f713747460b095033ff39e2c1e9b67f9afc82ac69bec057d4f5e32f4342d88fb763344be9f710aa42e04026fcc44dd824d66a7517e4c69
|
data/CHANGES.md
CHANGED
@@ -1,3 +1,30 @@
|
|
1
|
+
# 0.3.0
|
2
|
+
|
3
|
+
* Fix circuit interface callable to make `step task: :instance_method` use circuit signature.
|
4
|
+
|
5
|
+
# 0.2.9
|
6
|
+
|
7
|
+
* The `Path()` helper, when used with `:end_task` will now automatically _append_ the end task (or terminus) to `End.success`.
|
8
|
+
It used to be placed straight after the last path's element, which made it hard to later insert more steps into that very path.
|
9
|
+
|
10
|
+
# 0.2.8
|
11
|
+
|
12
|
+
* 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.
|
13
|
+
* 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`.
|
14
|
+
Note that this feature is still experimental and might get removed.
|
15
|
+
|
16
|
+
# 0.2.7
|
17
|
+
|
18
|
+
* `Did you mean ?` suggestions on Linear::Sequence::IndexError.
|
19
|
+
* Introduce `Linear::Helper` module for strategy extensions in third-party gems.
|
20
|
+
* Convenient way to patch Subprocess itself using `patch` option.
|
21
|
+
* Allow multiple `Path()` macro per step.
|
22
|
+
* Small fix for defining instance methods as steps using circuit interface.
|
23
|
+
|
24
|
+
# 0.2.6
|
25
|
+
|
26
|
+
* Added `@fields` to `Linear::State` to save arbitrary data on the activity/strategy level.
|
27
|
+
|
1
28
|
# 0.2.5
|
2
29
|
|
3
30
|
* Patching now requires the [`:patch` option for `Subprocess`](http://2019.trailblazer.to/2.1/docs/activity.html#activity-dsl-options-patching
|
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,125 +1,150 @@
|
|
1
1
|
module Trailblazer
|
2
2
|
class Activity
|
3
|
-
module DSL
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.included(base)
|
17
|
+
base.extend ClassMethods
|
55
18
|
end
|
56
19
|
|
57
|
-
|
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}.
|
58
49
|
|
59
|
-
|
60
|
-
|
50
|
+
# seq = block.call(state) # state changes.
|
51
|
+
state.instance_exec(&block)
|
61
52
|
|
62
|
-
|
53
|
+
seq = state.to_h[:sequence]
|
63
54
|
|
64
|
-
|
65
|
-
|
55
|
+
_end_id =
|
56
|
+
if connect_to
|
57
|
+
end_id
|
58
|
+
else
|
59
|
+
nil
|
60
|
+
end
|
66
61
|
|
67
|
-
|
68
|
-
end
|
62
|
+
seq = strip_start_and_ends(seq, end_id: _end_id) # don't cut off end unless {:connect_to} is set.
|
69
63
|
|
70
|
-
|
71
|
-
|
72
|
-
{
|
73
|
-
row: row,
|
74
|
-
insert: [Insert.method(:Prepend), "End.success"]
|
75
|
-
}
|
76
|
-
end
|
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
|
77
66
|
|
78
|
-
|
79
|
-
return Track.new(track_color, adds)
|
80
|
-
end
|
67
|
+
searches = [Search.ById(output, connect_to.value)]
|
81
68
|
|
82
|
-
|
83
|
-
|
84
|
-
patch.each do |path, patch|
|
85
|
-
activity = Patch.(activity, path, patch) # TODO: test if multiple patches works!
|
86
|
-
end
|
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
|
87
71
|
|
88
|
-
|
89
|
-
|
90
|
-
outputs: Hash[activity.to_h[:outputs].collect { |output| [output.semantic, output] }]
|
91
|
-
}
|
92
|
-
end
|
72
|
+
seq = seq[0..-2] + [row]
|
73
|
+
end
|
93
74
|
|
94
|
-
|
95
|
-
|
75
|
+
# Add the path elements before {End.success}.
|
76
|
+
# Termini (or :stop_event) are to be placed after {End.success}.
|
77
|
+
adds = seq.collect do |row|
|
78
|
+
options = row[3]
|
96
79
|
|
97
|
-
|
98
|
-
|
80
|
+
# the terminus of the path goes _after_ {End.success} into the "end group".
|
81
|
+
insert_method = options[:stop_event] ? Insert.method(:Append) : Insert.method(:Prepend)
|
99
82
|
|
83
|
+
{
|
84
|
+
row: row,
|
85
|
+
insert: [insert_method, "End.success"]
|
86
|
+
}
|
87
|
+
end
|
100
88
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
89
|
+
# Connect the Output() => Track(path_track)
|
90
|
+
return Track.new(track_color, adds, {})
|
91
|
+
end
|
92
|
+
|
93
|
+
# Computes the {:outputs} options for {activity}.
|
94
|
+
def Subprocess(activity, patch: {})
|
95
|
+
activity = Patch.customize(activity, options: patch)
|
96
|
+
|
97
|
+
{
|
98
|
+
task: activity,
|
99
|
+
outputs: Hash[activity.to_h[:outputs].collect { |output| [output.semantic, output] }]
|
100
|
+
}
|
101
|
+
end
|
102
|
+
|
103
|
+
module Patch
|
104
|
+
module_function
|
105
|
+
|
106
|
+
def customize(activity, options:)
|
107
|
+
options = options.is_a?(Proc) ?
|
108
|
+
{ [] => options } : # hash-wrapping with empty path, for patching given activity itself
|
109
|
+
options
|
110
|
+
|
111
|
+
options.each do |path, patch|
|
112
|
+
activity = call(activity, path, patch) # TODO: test if multiple patches works!
|
113
|
+
end
|
105
114
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
115
|
+
activity
|
116
|
+
end
|
117
|
+
|
118
|
+
def call(activity, path, customization)
|
119
|
+
task_id, *path = path
|
120
|
+
|
121
|
+
patch =
|
122
|
+
if task_id
|
123
|
+
segment_activity = Introspect::Graph(activity).find(task_id).task
|
124
|
+
patched_segment_activity = call(segment_activity, path, customization)
|
125
|
+
|
126
|
+
# Replace the patched subprocess.
|
127
|
+
-> { step Subprocess(patched_segment_activity), replace: task_id, id: task_id }
|
128
|
+
else
|
129
|
+
customization # apply the *actual* patch from the Subprocess() call.
|
130
|
+
end
|
131
|
+
|
132
|
+
patched_activity = Class.new(activity)
|
133
|
+
patched_activity.class_exec(&patch)
|
134
|
+
patched_activity
|
135
|
+
end
|
110
136
|
end
|
111
137
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
end
|
138
|
+
def normalize(options, local_keys) # TODO: test me.
|
139
|
+
locals = options.reject { |key, value| ! local_keys.include?(key) }
|
140
|
+
foreign = options.reject { |key, value| local_keys.include?(key) }
|
141
|
+
return foreign, locals
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end # Helper
|
145
|
+
|
146
|
+
include Helper # Introduce Helper constants in DSL::Linear scope
|
147
|
+
end # Linear
|
148
|
+
end # DSL
|
149
|
+
end # Activity
|
125
150
|
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,16 +54,26 @@ 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]
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
57
|
+
options = case ctx[:options]
|
58
|
+
when Hash
|
59
|
+
# Circuit Interface
|
60
|
+
task = ctx[:options].fetch(:task)
|
61
|
+
|
62
|
+
if task.is_a?(Symbol)
|
63
|
+
# step task: :find
|
64
|
+
{ options: { task: Trailblazer::Option( task ) } }
|
65
|
+
else
|
66
|
+
# step task: Callable, ... (Subprocess, Proc, macros etc)
|
67
|
+
{ task: task }
|
68
|
+
end
|
69
|
+
else
|
70
|
+
# Step Interface
|
71
|
+
# step :find, ...
|
72
|
+
# step Callable, ... (Method, Proc etc)
|
73
|
+
{ options: { task: ctx[:options], wrap_task: true } }
|
74
|
+
end
|
75
|
+
|
76
|
+
return Trailblazer::Activity::Right, [ctx.merge(options), flow_options]
|
66
77
|
end
|
67
78
|
|
68
79
|
def wrap_task_with_step_interface((ctx, flow_options), **)
|
@@ -122,7 +133,7 @@ module Trailblazer
|
|
122
133
|
|
123
134
|
ctx[:wirings] =
|
124
135
|
connections.collect do |semantic, (search_strategy_builder, *search_args)|
|
125
|
-
output = outputs[semantic] || raise("No `#{semantic}` output found for #{outputs.inspect}")
|
136
|
+
output = outputs[semantic] || raise("No `#{semantic}` output found for #{ctx[:id].inspect} and outputs #{outputs.inspect}")
|
126
137
|
|
127
138
|
search_strategy_builder.( # return proc to be called when compiling Seq, e.g. {ById(output, :id)}
|
128
139
|
output,
|
@@ -157,6 +168,8 @@ module Trailblazer
|
|
157
168
|
_adds = [add_end(cfg, magnetic_to: end_id, id: end_id)] unless end_exists
|
158
169
|
|
159
170
|
[output_to_id(ctx, output, end_id), _adds]
|
171
|
+
else
|
172
|
+
raise cfg.inspect
|
160
173
|
end
|
161
174
|
|
162
175
|
connections = connections.merge(new_connections)
|
@@ -168,8 +181,10 @@ module Trailblazer
|
|
168
181
|
return Trailblazer::Activity::Right, [new_ctx, flow_options]
|
169
182
|
end
|
170
183
|
|
171
|
-
def output_to_track(ctx, output,
|
172
|
-
|
184
|
+
def output_to_track(ctx, output, track)
|
185
|
+
search_strategy = track.options[:wrap_around] ? :WrapAround : :Forward
|
186
|
+
|
187
|
+
{output.value => [Linear::Search.method(search_strategy), track.color]}
|
173
188
|
end
|
174
189
|
|
175
190
|
def output_to_id(ctx, output, target)
|
@@ -219,9 +234,28 @@ module Trailblazer
|
|
219
234
|
return Trailblazer::Activity::Right, [ctx.merge(new_ctx), flow_options]
|
220
235
|
end
|
221
236
|
|
237
|
+
# Currently, the {:inherit} option copies over {:connections} from the original step
|
238
|
+
# and merges them with the (prolly) connections passed from the user.
|
239
|
+
def inherit_option((ctx, flow_options), *)
|
240
|
+
return Trailblazer::Activity::Right, [ctx, flow_options] unless ctx[:inherit]
|
241
|
+
|
242
|
+
sequence = ctx[:sequence]
|
243
|
+
id = ctx[:id]
|
244
|
+
|
245
|
+
index = Linear::Insert.find_index(sequence, id)
|
246
|
+
row = sequence[index] # from this row we're inheriting options.
|
247
|
+
|
248
|
+
extensions = (row[3][:extensions]||[]) + (ctx[:extensions]||[]) # FIXME: DEFAULTING, FOR FUCK'S SAKE
|
249
|
+
|
250
|
+
ctx = ctx.merge(connections: row[3][:connections], extensions: extensions) # "inherit"
|
251
|
+
|
252
|
+
return Trailblazer::Activity::Right, [ctx, flow_options]
|
253
|
+
end
|
254
|
+
|
222
255
|
# TODO: make this extendable!
|
223
256
|
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) }
|
257
|
+
# new_ctx = ctx.reject { |k, v| [:connections, :outputs, :end_id, :step_interface_builder, :failure_end, :track_name, :sequence].include?(k) }
|
258
|
+
new_ctx = ctx.reject { |k, v| [:outputs, :end_id, :step_interface_builder, :failure_end, :track_name, :sequence].include?(k) }
|
225
259
|
|
226
260
|
return Trailblazer::Activity::Right, [new_ctx, flow_options]
|
227
261
|
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.
|
4
|
+
version: 0.3.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: 2020-
|
11
|
+
date: 2020-08-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: trailblazer-activity
|
@@ -135,8 +135,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
135
135
|
- !ruby/object:Gem::Version
|
136
136
|
version: '0'
|
137
137
|
requirements: []
|
138
|
-
|
139
|
-
rubygems_version: 2.7.3
|
138
|
+
rubygems_version: 3.0.8
|
140
139
|
signing_key:
|
141
140
|
specification_version: 4
|
142
141
|
summary: Simple DSL to define Trailblazer activities.
|