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