trailblazer-activity 0.15.0 → 0.16.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +1 -1
- data/CHANGES.md +65 -0
- data/Gemfile +3 -2
- data/lib/trailblazer/activity/adds.rb +122 -119
- data/lib/trailblazer/activity/circuit/task_adapter.rb +3 -3
- data/lib/trailblazer/activity/circuit.rb +6 -2
- data/lib/trailblazer/activity/deprecate.rb +4 -5
- data/lib/trailblazer/activity/introspect/render.rb +53 -0
- data/lib/trailblazer/activity/introspect.rb +27 -113
- data/lib/trailblazer/activity/schema/compiler.rb +106 -0
- data/lib/trailblazer/activity/schema/implementation.rb +2 -1
- data/lib/trailblazer/activity/schema/intermediate.rb +6 -86
- data/lib/trailblazer/activity/schema.rb +27 -4
- data/lib/trailblazer/activity/structures.rb +9 -7
- data/lib/trailblazer/activity/task_wrap/call_task.rb +4 -1
- data/lib/trailblazer/activity/task_wrap/extension.rb +24 -8
- data/lib/trailblazer/activity/task_wrap/pipeline.rb +1 -1
- data/lib/trailblazer/activity/task_wrap/runner.rb +8 -7
- data/lib/trailblazer/activity/task_wrap.rb +7 -5
- data/lib/trailblazer/activity/testing.rb +47 -126
- data/lib/trailblazer/activity/version.rb +1 -1
- data/lib/trailblazer/activity.rb +6 -11
- data/trailblazer-activity.gemspec +0 -1
- metadata +4 -17
- data/lib/trailblazer/activity/config.rb +0 -35
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cf0052816c7f4a1ed202128395c3c8324a27b139bbfcb163314848eaf5132d2d
|
4
|
+
data.tar.gz: 94f9951381191ce8f25816aa27228a17c27c7d271e246c94a8667f315ca2f885
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 34b620b89186a2656671005b026f5f3f28151bcae374d058d047175553dd77b3a0c6c23557be84e03a297c9e7e02185b22ac09e4149ba843bed48db3261451a3
|
7
|
+
data.tar.gz: 512ac5bdb8af2ef4948c70c2292910e4d406f600c4d5a178afdfc02f8b70497f8b15e86ee297e4199ac4f3716be509986a21a7de33346418b60ce22501ab8930
|
data/.github/workflows/ci.yml
CHANGED
@@ -6,7 +6,7 @@ jobs:
|
|
6
6
|
fail-fast: false
|
7
7
|
matrix:
|
8
8
|
# Due to https://github.com/actions/runner/issues/849, we have to use quotes for '3.0'
|
9
|
-
ruby: [2.5, 2.6, 2.7, '3.0', 3.1, head, jruby, jruby-head]
|
9
|
+
ruby: [2.5, 2.6, 2.7, '3.0', 3.1, 3.2, head, jruby, jruby-head]
|
10
10
|
runs-on: ubuntu-latest
|
11
11
|
steps:
|
12
12
|
- uses: actions/checkout@v3
|
data/CHANGES.md
CHANGED
@@ -1,3 +1,68 @@
|
|
1
|
+
# 0.16.0
|
2
|
+
|
3
|
+
* Remove `Activity#[]`. Please use `activity.to_h[:config]`.
|
4
|
+
* Change `Activity#to_h[:nodes]`. This is now a `Schema::Nodes` "hash" that is keyed by task that
|
5
|
+
points to `Nodes::Attributes` data structures (a replacement for `Activity::NodeAttributes`).
|
6
|
+
This decision reduces logic and improves performance: it turned out that most of the time an introspect
|
7
|
+
lookup queries for a task, not ID.
|
8
|
+
* Introduce `Activity::Introspect.Nodes()` as a consistent and fast interface for introspection
|
9
|
+
and remove `Activity::Introspect::TaskMap`.
|
10
|
+
* Remove `Activity::NodeAttributes`.
|
11
|
+
* Move `Introspect::Graph` to `trailblazer-developer`. It's a data structure very specific
|
12
|
+
to rendering, which is not a part of pure runtime behavior. `Activity::Introspect.Graph()` is now deprecated.
|
13
|
+
* `TaskWrap.container_activity_for` now accepts `:id` for setting an ID for the containered activity to
|
14
|
+
anything other than `nil`.
|
15
|
+
* Re-add `:nodes` to the container activity hash as this provides a consistent way for treating all `Activity`s.
|
16
|
+
* Remove `Activity::Config`. This immutable hash interface was used in one place, only, and can easily
|
17
|
+
be replaced with `config.merge()`.
|
18
|
+
* Add `Introspect::Render`. Please consider this private.
|
19
|
+
|
20
|
+
## Intermediate/Implementation
|
21
|
+
|
22
|
+
* Remove `Intermediate.call`, this is now done through `Intermediate::Compiler`.
|
23
|
+
* Introduce `Intermediate::Compiler` which is simplified and is 10% faster.
|
24
|
+
* A terminus ("end event") in `Schema::Intermediate` no longer has outputs but an empty array. The
|
25
|
+
`stop_event: true` option is still required to mark the `TaskRef` as a terminus.
|
26
|
+
* `Schema::Intermediate` now keeps a map `{<terminus ID> => :semantic}` instead of the flat termini ID list and
|
27
|
+
one default start event instead of an array. This looks as follows.
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
Schema::Intermediate.new(
|
31
|
+
{
|
32
|
+
# ...
|
33
|
+
Intermediate::TaskRef("End.success", stop_event: true) => [Inter::Out(:success, nil)]
|
34
|
+
},
|
35
|
+
["End.success"],
|
36
|
+
[:a] # start
|
37
|
+
```
|
38
|
+
|
39
|
+
Now becomes
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
Schema::Intermediate.new(
|
43
|
+
{
|
44
|
+
# ...
|
45
|
+
Intermediate::TaskRef("End.success", stop_event: true) => []
|
46
|
+
},
|
47
|
+
{"End.success" => :success},
|
48
|
+
:a # start
|
49
|
+
```
|
50
|
+
* In line with the change in `Intermediate`, the `Implemention` termini `Task`s now don't have outputs anymore.
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
implementation = {
|
54
|
+
# ...
|
55
|
+
"End.success" => Schema::Implementation::Task(Activity::End.new(semantic: :success), [], []) # No need for outputs here.
|
56
|
+
}
|
57
|
+
```
|
58
|
+
|
59
|
+
# 0.15.1
|
60
|
+
|
61
|
+
* Introduce `Extension.WrapStatic()` as a consistent interface for creating wrap_static extensions
|
62
|
+
exposing the friendly interface.
|
63
|
+
* Deprecate `Extension(merge: ...)` since we have `Extension.WrapStatic` now.
|
64
|
+
* Better deprecation warnings for extensions using `Insert` and not the friendly interface.
|
65
|
+
|
1
66
|
# 0.15.0
|
2
67
|
|
3
68
|
* Rename `Circuit::Run` to `Circuit::Runner` for consistency with `TaskWrap::Runner`.
|
data/Gemfile
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
source "https://rubygems.org"
|
2
2
|
gemspec
|
3
3
|
|
4
|
-
# gem "trailblazer-developer",
|
5
|
-
|
4
|
+
# gem "trailblazer-developer", path: "../trailblazer-developer"
|
5
|
+
gem "benchmark-ips"
|
6
|
+
gem "standard"
|
@@ -1,154 +1,157 @@
|
|
1
1
|
module Trailblazer
|
2
2
|
class Activity
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
3
|
+
# Developer's docs: https://trailblazer.to/2.1/docs/internals#internals-wiring-api-adds-interface
|
4
|
+
#
|
5
|
+
# The Adds interface are mechanics to alter sequences/pipelines.
|
6
|
+
# "one" ADDS structure: {row: ..., insert: [Insert, "id"]}
|
7
|
+
#
|
8
|
+
# To work with the instructions provided here, the pipeline structure
|
9
|
+
# needs to expose {#to_a}.
|
10
|
+
module Adds
|
11
|
+
module_function
|
12
|
+
|
13
|
+
# @returns Sequence/Pipeline New sequence instance
|
14
|
+
# @private
|
15
|
+
def insert_row(pipeline, row:, insert:)
|
16
|
+
insert_function, *args = insert
|
17
|
+
|
18
|
+
insert_function.(pipeline, row, *args)
|
19
|
+
end
|
14
20
|
|
15
|
-
|
21
|
+
# Inserts one or more {Adds} into {pipeline}.
|
22
|
+
def apply_adds(pipeline, adds)
|
23
|
+
adds.each do |add|
|
24
|
+
pipeline = insert_row(pipeline, **add)
|
16
25
|
end
|
17
26
|
|
18
|
-
|
19
|
-
|
20
|
-
adds.each do |add|
|
21
|
-
pipeline = insert_row(pipeline, **add)
|
22
|
-
end
|
23
|
-
|
24
|
-
pipeline
|
25
|
-
end
|
27
|
+
pipeline
|
28
|
+
end
|
26
29
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
# Adds.apply_adds(pipeline, adds)
|
32
|
-
# end
|
33
|
-
|
34
|
-
module FriendlyInterface
|
35
|
-
# @public
|
36
|
-
# @return Array of ADDS
|
37
|
-
#
|
38
|
-
# Translate a collection of friendly interface to ADDS.
|
39
|
-
# This is a mini-DSL, if you want.
|
40
|
-
def self.adds_for(inserts)
|
41
|
-
inserts.collect do |task, options|
|
42
|
-
build_adds(task, **options)
|
43
|
-
end
|
44
|
-
end
|
30
|
+
# @param inserts Array of friendly interface insertions
|
31
|
+
# def call(pipeline, *inserts)
|
32
|
+
# adds = build_adds_for_friendly_interface(inserts)
|
45
33
|
|
46
|
-
|
47
|
-
|
48
|
-
insert, insert_id =
|
49
|
-
append === false ? [:Prepend, prepend] : [:Append, append]
|
34
|
+
# Adds.apply_adds(pipeline, adds)
|
35
|
+
# end
|
50
36
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
37
|
+
module FriendlyInterface
|
38
|
+
# @public
|
39
|
+
# @return Array of ADDS
|
40
|
+
#
|
41
|
+
# Translate a collection of friendly interface to ADDS.
|
42
|
+
# This is a mini-DSL, if you want.
|
43
|
+
def self.adds_for(inserts)
|
44
|
+
inserts.collect do |task, options|
|
45
|
+
build_adds(task, **options)
|
55
46
|
end
|
56
47
|
end
|
57
48
|
|
58
|
-
#
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
49
|
+
# @private
|
50
|
+
def self.build_adds(task, id:, prepend: "task_wrap.call_task", append: false)
|
51
|
+
insert, insert_id =
|
52
|
+
(append === false) ? [:Prepend, prepend] : [:Append, append]
|
53
|
+
|
54
|
+
{
|
55
|
+
insert: [Activity::Adds::Insert.method(insert), insert_id],
|
56
|
+
row: TaskWrap::Pipeline::Row(id, task)
|
57
|
+
}
|
58
|
+
end
|
59
|
+
end
|
65
60
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
61
|
+
# Functions to alter the Sequence/Pipeline by inserting, replacing, or deleting a row.
|
62
|
+
#
|
63
|
+
# they don't mutate the data structure but rebuild it, has to respond to {to_a}
|
64
|
+
#
|
65
|
+
# These methods are invoked via {Adds.apply_adds} and should never be called directly.
|
66
|
+
module Insert
|
67
|
+
module_function
|
70
68
|
|
71
|
-
|
72
|
-
|
69
|
+
# Append {new_row} after {insert_id}.
|
70
|
+
def Append(pipeline, new_row, insert_id = nil)
|
71
|
+
build_from_ary(pipeline, insert_id) do |ary, index|
|
72
|
+
index = ary.size if index.nil? # append to end of pipeline.
|
73
|
+
|
74
|
+
range_before_index(ary, index + 1) + [new_row] + Array(ary[index + 1..-1])
|
73
75
|
end
|
76
|
+
end
|
74
77
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
78
|
+
# Insert {new_row} before {insert_id}.
|
79
|
+
def Prepend(pipeline, new_row, insert_id = nil)
|
80
|
+
build_from_ary(pipeline, insert_id) do |ary, index|
|
81
|
+
index = 0 if index.nil? # Prepend to beginning of pipeline.
|
79
82
|
|
80
|
-
|
81
|
-
end
|
83
|
+
range_before_index(ary, index) + [new_row] + ary[index..-1]
|
82
84
|
end
|
85
|
+
end
|
83
86
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
end
|
87
|
+
def Replace(pipeline, new_row, insert_id)
|
88
|
+
build_from_ary(pipeline, insert_id) do |ary, index|
|
89
|
+
range_before_index(ary, index) + [new_row] + ary[index + 1..-1]
|
88
90
|
end
|
91
|
+
end
|
89
92
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
end
|
93
|
+
def Delete(pipeline, _, insert_id)
|
94
|
+
build_from_ary(pipeline, insert_id) do |ary, index|
|
95
|
+
range_before_index(ary, index) + ary[index + 1..-1]
|
94
96
|
end
|
97
|
+
end
|
95
98
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
99
|
+
# @private
|
100
|
+
def build(sequence, rows)
|
101
|
+
sequence.class.new(rows)
|
102
|
+
end
|
100
103
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
104
|
+
# @private
|
105
|
+
def find_index(ary, insert_id)
|
106
|
+
ary.find_index { |row| row.id == insert_id }
|
107
|
+
end
|
105
108
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
end
|
121
|
-
|
122
|
-
_new_ary = yield(ary, index) # call the block.
|
109
|
+
# Converts the pipeline structure to an array,
|
110
|
+
# automatically finds the index for {insert_id},
|
111
|
+
# and calls the user block with the computed values.
|
112
|
+
#
|
113
|
+
# Single-entry point, could be named {#call}.
|
114
|
+
# @private
|
115
|
+
def apply_on_ary(pipeline, insert_id, raise_index_error: true, &block)
|
116
|
+
ary = pipeline.to_a
|
117
|
+
|
118
|
+
if insert_id.nil?
|
119
|
+
index = nil
|
120
|
+
else
|
121
|
+
index = find_index(ary, insert_id) # DISCUSS: this only makes sense if there are more than {Append} using this.
|
122
|
+
raise IndexError.new(pipeline, insert_id) if index.nil? && raise_index_error
|
123
123
|
end
|
124
124
|
|
125
|
-
|
126
|
-
|
125
|
+
_new_ary = yield(ary, index) # call the block.
|
126
|
+
end
|
127
127
|
|
128
|
-
|
129
|
-
|
130
|
-
end
|
128
|
+
def build_from_ary(pipeline, insert_id, &block)
|
129
|
+
new_ary = apply_on_ary(pipeline, insert_id, &block)
|
131
130
|
|
132
|
-
#
|
133
|
-
|
134
|
-
|
135
|
-
def range_before_index(ary, index)
|
136
|
-
return [] if index == 0
|
137
|
-
ary[0..index-1]
|
138
|
-
end
|
139
|
-
end # Insert
|
131
|
+
# Wrap the sequence/pipeline array into a concrete Sequence/Pipeline.
|
132
|
+
build(pipeline, new_ary)
|
133
|
+
end
|
140
134
|
|
141
|
-
|
142
|
-
|
143
|
-
|
135
|
+
# Always returns a valid, concat-able array for all indices
|
136
|
+
# before the {index}.
|
137
|
+
# @private
|
138
|
+
def range_before_index(ary, index)
|
139
|
+
return [] if index == 0
|
140
|
+
ary[0..index - 1]
|
141
|
+
end
|
142
|
+
end # Insert
|
144
143
|
|
145
|
-
|
146
|
-
|
147
|
-
|
144
|
+
class IndexError < ::IndexError
|
145
|
+
def initialize(sequence, step_id)
|
146
|
+
valid_ids = sequence.to_a.collect { |row| row.id.inspect }
|
148
147
|
|
149
|
-
|
150
|
-
|
148
|
+
message = "\n" \
|
149
|
+
"\e[31m#{step_id.inspect} is not a valid step ID. Did you mean any of these ?\e[0m\n" \
|
150
|
+
"\e[32m#{valid_ids.join("\n")}\e[0m"
|
151
|
+
|
152
|
+
super(message)
|
151
153
|
end
|
152
154
|
end
|
155
|
+
end
|
153
156
|
end
|
154
157
|
end
|
@@ -22,8 +22,8 @@ module Trailblazer
|
|
22
22
|
# and returns the return value of the user's callable. By design, it is *not* circuit-interface compatible.
|
23
23
|
class Step
|
24
24
|
def initialize(step, user_proc, **)
|
25
|
-
@step
|
26
|
-
@user_proc
|
25
|
+
@step = step
|
26
|
+
@user_proc = user_proc
|
27
27
|
end
|
28
28
|
|
29
29
|
# Translate the circuit interface to the step's step-interface. However,
|
@@ -91,7 +91,7 @@ module Trailblazer
|
|
91
91
|
def inspect # TODO: make me private!
|
92
92
|
user_step = @circuit_step.instance_variable_get(:@user_proc) # DISCUSS: to we want Step#to_h?
|
93
93
|
|
94
|
-
%
|
94
|
+
%(#<Trailblazer::Activity::TaskBuilder::Task user_proc=#{Trailblazer::Activity::Introspect.render_task(user_step)}>)
|
95
95
|
end
|
96
96
|
alias_method :to_s, :inspect
|
97
97
|
end
|
@@ -58,7 +58,7 @@ module Trailblazer
|
|
58
58
|
task,
|
59
59
|
signal: last_signal,
|
60
60
|
outputs: @map[task],
|
61
|
-
exec_context: circuit_options[:exec_context]
|
61
|
+
exec_context: circuit_options[:exec_context] # passed at run-time from DSL
|
62
62
|
)
|
63
63
|
end
|
64
64
|
end
|
@@ -66,7 +66,11 @@ module Trailblazer
|
|
66
66
|
|
67
67
|
# Returns the circuit's components.
|
68
68
|
def to_h
|
69
|
-
{
|
69
|
+
{
|
70
|
+
map: @map,
|
71
|
+
end_events: @stop_events,
|
72
|
+
start_task: @start_task
|
73
|
+
}
|
70
74
|
end
|
71
75
|
|
72
76
|
private
|
@@ -5,16 +5,15 @@ module Trailblazer
|
|
5
5
|
|
6
6
|
def warn(caller_location, message)
|
7
7
|
location = caller_location ? location_for(caller_location) : nil
|
8
|
-
warning
|
8
|
+
warning = [location, message].compact.join(" ")
|
9
9
|
|
10
|
-
Kernel.warn %
|
10
|
+
Kernel.warn %([Trailblazer] #{warning}\n)
|
11
11
|
end
|
12
12
|
|
13
13
|
def location_for(caller_location)
|
14
|
-
|
15
|
-
line_no = caller_location.lineno
|
14
|
+
line_no = caller_location.lineno
|
16
15
|
|
17
|
-
%
|
16
|
+
%(#{caller_location.absolute_path}:#{line_no})
|
18
17
|
end
|
19
18
|
end
|
20
19
|
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Trailblazer
|
2
|
+
class Activity
|
3
|
+
module Introspect
|
4
|
+
# @private
|
5
|
+
module Render
|
6
|
+
module_function
|
7
|
+
|
8
|
+
def call(activity, **options)
|
9
|
+
nodes = Introspect.Nodes(activity)
|
10
|
+
circuit_map = activity.to_h[:circuit].to_h[:map]
|
11
|
+
|
12
|
+
content = nodes.collect do |task, node|
|
13
|
+
outgoings = circuit_map[task]
|
14
|
+
|
15
|
+
conns = outgoings.collect do |signal, target|
|
16
|
+
" {#{signal}} => #{inspect_with_matcher(target, **options)}"
|
17
|
+
end
|
18
|
+
|
19
|
+
[
|
20
|
+
inspect_with_matcher(node.task, **options),
|
21
|
+
conns.join("\n")
|
22
|
+
]
|
23
|
+
end
|
24
|
+
|
25
|
+
content = content.join("\n")
|
26
|
+
|
27
|
+
"\n#{content}".gsub(/0x\w+/, "0x")
|
28
|
+
end
|
29
|
+
|
30
|
+
# If Ruby had pattern matching, this function wasn't necessary.
|
31
|
+
def inspect_with_matcher(task, inspect_task: method(:inspect_task), inspect_end: method(:inspect_end))
|
32
|
+
return inspect_task.(task) unless task.is_a?(Trailblazer::Activity::End)
|
33
|
+
inspect_end.(task)
|
34
|
+
end
|
35
|
+
|
36
|
+
def inspect_task(task)
|
37
|
+
task.inspect
|
38
|
+
end
|
39
|
+
|
40
|
+
def inspect_end(task)
|
41
|
+
class_name = strip(task.class)
|
42
|
+
options = task.to_h
|
43
|
+
|
44
|
+
"#<#{class_name}/#{options[:semantic].inspect}>"
|
45
|
+
end
|
46
|
+
|
47
|
+
def strip(string)
|
48
|
+
string.to_s.sub("Trailblazer::Activity::", "")
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|