trailblazer-activity 0.14.0 → 0.15.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 +36 -0
- data/lib/trailblazer/activity/adds.rb +3 -2
- data/lib/trailblazer/activity/circuit/task_adapter.rb +113 -0
- data/lib/trailblazer/activity/circuit.rb +4 -4
- data/lib/trailblazer/activity/deprecate.rb +21 -0
- data/lib/trailblazer/activity/introspect.rb +71 -4
- data/lib/trailblazer/activity/schema/implementation.rb +3 -1
- data/lib/trailblazer/activity/schema/intermediate.rb +7 -5
- data/lib/trailblazer/activity/schema.rb +4 -2
- data/lib/trailblazer/activity/task_wrap/extension.rb +1 -1
- data/lib/trailblazer/activity/task_wrap/pipeline.rb +21 -0
- data/lib/trailblazer/activity/task_wrap.rb +24 -11
- data/lib/trailblazer/activity/testing.rb +8 -9
- data/lib/trailblazer/activity/version.rb +1 -1
- data/lib/trailblazer/activity.rb +13 -3
- metadata +4 -3
- data/lib/trailblazer/activity/task_builder.rb +0 -53
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6b06f440b4b9b94230fad09bc1b2024ee3297d3b454cae381f01053af65dfe8d
|
|
4
|
+
data.tar.gz: 1cd2bae0d2bd7a7f18cacee0d54af07fa4e8783b75b2852816c6882bb5f17b6a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4844b0b023f93fc12f218932a7d574d96e94684adaab4a186f85cb378cb7e7e92b889bec061af70bc6731d83a56a0fe5ff2651f5f250a0e4d091b454304193e5
|
|
7
|
+
data.tar.gz: 3a41f612cecfabdc2e5d0fef50411f5db1a5c6867a8c88ce784e569bfcc2b17b62bfda7a4104b8bbbb68cb157c311961622e7f64b9e5cc811ff09ee1db133296
|
data/CHANGES.md
CHANGED
|
@@ -1,3 +1,39 @@
|
|
|
1
|
+
# 0.15.0
|
|
2
|
+
|
|
3
|
+
* Rename `Circuit::Run` to `Circuit::Runner` for consistency with `TaskWrap::Runner`.
|
|
4
|
+
* Add `:flat_activity` keyword argument to `Testing.nested_activity`, so you can inject any activity for `:D`.
|
|
5
|
+
Also, allow to change `:D`'s ID with the `:d_id` option.
|
|
6
|
+
* Introduce `Deprecate.warn` to have consistent deprecation warnings across all gems.
|
|
7
|
+
* Introduce `Activity.call` as a top-level entry point and abstraction for `TaskWrap.invoke`.
|
|
8
|
+
|
|
9
|
+
## TaskWrap
|
|
10
|
+
|
|
11
|
+
* Remove the `:wrap_static` keyword argument for `TaskWrap.invoke` and replace it with `:container_activity`.
|
|
12
|
+
* Make `TaskWrap.initial_wrap_static` return `INITIAL_TASK_WRAP` instead of recompiling it for every `#invoke`.
|
|
13
|
+
* Introduce `TaskWrap.container_activity_for` to build "host activities" that are used to provide a wrap_static to
|
|
14
|
+
the actually run activity. This is also used in the `Each()` macro and other places.
|
|
15
|
+
* Allow `append: nil` for friendly interface.
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
TaskWrap.Extension([method(:add_1), id: "user.add_1", append: nil])`.
|
|
19
|
+
```
|
|
20
|
+
This appends the step to the end of the pipeline.
|
|
21
|
+
|
|
22
|
+
## Introspect
|
|
23
|
+
|
|
24
|
+
* Add `Introspect.find_path` to retrieve a `Graph::Node` and its hosting activity from a deeply nested graph.
|
|
25
|
+
Note that this method is still considered private.
|
|
26
|
+
* Add `Introspect::TaskMap` as a slim interface for introspecting `Activity` instances. Note that `Graph` might get
|
|
27
|
+
moved to `developer` as it is very specific to rendering circuits.
|
|
28
|
+
|
|
29
|
+
## TaskAdapter
|
|
30
|
+
|
|
31
|
+
* Rename `Activity::TaskBuilder.Binary()` to `Activity::Circuit::TaskAdapter.for_step()`. It returns a `TaskAdapter`
|
|
32
|
+
instance, which is a bit more descriptive than a `Task` instance.
|
|
33
|
+
* Add `Circuit.Step(callable_with_step_interface)` which accepts a step-interface callable and, when called, returns
|
|
34
|
+
its result and the `ctx`. This is great for deciders in macros where you don't want the step's result on the `ctx`.
|
|
35
|
+
* Add `TaskWrap::Pipeline::TaskBuilder`.
|
|
36
|
+
|
|
1
37
|
# 0.14.0
|
|
2
38
|
|
|
3
39
|
* Remove `Pipeline.insert_before` and friends. Pipeline is now altered using ADDS mechanics, just
|
|
@@ -44,8 +44,9 @@ module Trailblazer
|
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
# @private
|
|
47
|
-
def self.build_adds(task, id:, prepend: "task_wrap.call_task", append:
|
|
48
|
-
insert, insert_id =
|
|
47
|
+
def self.build_adds(task, id:, prepend: "task_wrap.call_task", append: false)
|
|
48
|
+
insert, insert_id =
|
|
49
|
+
append === false ? [:Prepend, prepend] : [:Append, append]
|
|
49
50
|
|
|
50
51
|
{
|
|
51
52
|
insert: [Activity::Adds::Insert.method(insert), insert_id],
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
module Trailblazer
|
|
2
|
+
class Activity
|
|
3
|
+
# Circuit::TaskAdapter: Uses Circuit::Step to translate incoming, and returns a circuit-interface
|
|
4
|
+
# compatible return set.
|
|
5
|
+
class Circuit
|
|
6
|
+
# Create a `Circuit::Step` instance. Mostly this is used inside a `TaskAdapter`.
|
|
7
|
+
#
|
|
8
|
+
# @param [callable_with_step_interface] Any kind of callable object or `:instance_method` that receives
|
|
9
|
+
# a step interface.
|
|
10
|
+
# @param [:option] If true, the user's callable argument is wrapped in `Trailblazer::Option`.
|
|
11
|
+
# @return [Circuit::Step, Circuit::Step::Option] Returns a callable circuit-step.
|
|
12
|
+
# @see https://trailblazer.to/2.1/docs/activity#activity-internals-step-interface
|
|
13
|
+
def self.Step(callable_with_step_interface, option: false)
|
|
14
|
+
if option
|
|
15
|
+
return Step::Option.new(Trailblazer::Option(callable_with_step_interface), callable_with_step_interface)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
Step.new(callable_with_step_interface, callable_with_step_interface) # DISCUSS: maybe we can ditch this, only if performance is cool here, but where would we use that?
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# {Step#call} translates the incoming circuit-interface to the step-interface,
|
|
22
|
+
# and returns the return value of the user's callable. By design, it is *not* circuit-interface compatible.
|
|
23
|
+
class Step
|
|
24
|
+
def initialize(step, user_proc, **)
|
|
25
|
+
@step = step
|
|
26
|
+
@user_proc = user_proc
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Translate the circuit interface to the step's step-interface. However,
|
|
30
|
+
# this only translates the calling interface, not the returning.
|
|
31
|
+
def call((ctx, flow_options), **circuit_options)
|
|
32
|
+
result = @step.(ctx, **ctx.to_hash)
|
|
33
|
+
# in an immutable environment we should return the ctx from the step.
|
|
34
|
+
return result, ctx
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# In {Step::Option}, {@step} is expected to be wrapped in a {Trailblazer::Option}.
|
|
38
|
+
# To remember: when calling an Option instance, you need to pass {:keyword_arguments} explicitely,
|
|
39
|
+
# because of beautiful Ruby 2.5 and 2.6.
|
|
40
|
+
#
|
|
41
|
+
# This is often needed for "decider" chunks where the user can run either a method or a callable
|
|
42
|
+
# but you only want back the return value, not a Binary circuit-interface return set.
|
|
43
|
+
class Option < Step
|
|
44
|
+
def call((ctx, _flow_options), **circuit_options)
|
|
45
|
+
result = @step.(ctx, keyword_arguments: ctx.to_hash, **circuit_options) # circuit_options contains :exec_context.
|
|
46
|
+
# in an immutable environment we should return the ctx from the step.
|
|
47
|
+
return result, ctx
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
class TaskAdapter
|
|
53
|
+
# Returns a `TaskAdapter` instance always exposes the complete circuit-interface,
|
|
54
|
+
# and can be used directly in a {Circuit}.
|
|
55
|
+
#
|
|
56
|
+
# @note This used to be called `TaskBuilder::Task`.
|
|
57
|
+
# @param [step] Any kind of callable object or `:instance_method` that receives
|
|
58
|
+
# a step interface.
|
|
59
|
+
# @param [:option] If true, the user's callable argument is wrapped in `Trailblazer::Option`.
|
|
60
|
+
# @return [TaskAdapter] a circuit-interface compatible object to use in a `Circuit`.
|
|
61
|
+
def self.for_step(step, binary: true, **options_for_step)
|
|
62
|
+
circuit_step = Circuit.Step(step, **options_for_step)
|
|
63
|
+
|
|
64
|
+
new(circuit_step)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# @param [circuit_step] Exposes a Circuit::Step.call([ctx, flow_options], **circuit_options) interface
|
|
68
|
+
def initialize(circuit_step, **)
|
|
69
|
+
@circuit_step = circuit_step
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def call((ctx, flow_options), **circuit_options)
|
|
73
|
+
result, ctx = @circuit_step.([ctx, flow_options], **circuit_options)
|
|
74
|
+
|
|
75
|
+
# Return an appropriate signal which direction to go next.
|
|
76
|
+
signal = TaskAdapter.binary_signal_for(result, Activity::Right, Activity::Left)
|
|
77
|
+
|
|
78
|
+
return signal, [ctx, flow_options]
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Translates the return value of the user step into a valid signal.
|
|
82
|
+
# Note that it passes through subclasses of {Activity::Signal}.
|
|
83
|
+
def self.binary_signal_for(result, on_true, on_false)
|
|
84
|
+
if result.is_a?(Class) && result < Activity::Signal
|
|
85
|
+
result
|
|
86
|
+
else
|
|
87
|
+
result ? on_true : on_false
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def inspect # TODO: make me private!
|
|
92
|
+
user_step = @circuit_step.instance_variable_get(:@user_proc) # DISCUSS: to we want Step#to_h?
|
|
93
|
+
|
|
94
|
+
%{#<Trailblazer::Activity::TaskBuilder::Task user_proc=#{Trailblazer::Activity::Introspect.render_task(user_step)}>}
|
|
95
|
+
end
|
|
96
|
+
alias_method :to_s, :inspect
|
|
97
|
+
end
|
|
98
|
+
end # Circuit
|
|
99
|
+
|
|
100
|
+
# TODO: remove when we drop compatibility.
|
|
101
|
+
module TaskBuilder
|
|
102
|
+
# @deprecated Use {Trailblazer::Activity::Circuit::TaskAdapter.for_step()} instead.
|
|
103
|
+
def self.Binary(user_proc)
|
|
104
|
+
Activity::Circuit::TaskAdapter.for_step(user_proc, option: true)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
class << self
|
|
108
|
+
extend Gem::Deprecate
|
|
109
|
+
deprecate :Binary, "Trailblazer::Activity::Circuit::TaskAdapter.for_step()", 2023, 12
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end # Activity
|
|
113
|
+
end
|
|
@@ -24,7 +24,7 @@ module Trailblazer
|
|
|
24
24
|
|
|
25
25
|
# @param args [Array] all arguments to be passed to the task's `call`
|
|
26
26
|
# @param task [callable] task to call
|
|
27
|
-
|
|
27
|
+
Runner = ->(task, args, **circuit_options) { task.(args, **circuit_options) }
|
|
28
28
|
|
|
29
29
|
# Runs the circuit until we hit a stop event.
|
|
30
30
|
#
|
|
@@ -37,8 +37,8 @@ module Trailblazer
|
|
|
37
37
|
# @return [last_signal, options, flow_options, *args]
|
|
38
38
|
#
|
|
39
39
|
# NOTE: returned circuit_options are discarded when calling the runner.
|
|
40
|
-
def call(args, start_task: @start_task, runner:
|
|
41
|
-
circuit_options = circuit_options.merge(
|
|
40
|
+
def call(args, start_task: @start_task, runner: Runner, **circuit_options)
|
|
41
|
+
circuit_options = circuit_options.merge(runner: runner) # TODO: set the :runner option via arguments_for_call to save the merge?
|
|
42
42
|
task = start_task
|
|
43
43
|
|
|
44
44
|
loop do
|
|
@@ -49,7 +49,7 @@ module Trailblazer
|
|
|
49
49
|
)
|
|
50
50
|
|
|
51
51
|
# Stop execution of the circuit when we hit a stop event (< End). This could be an task's End or Suspend.
|
|
52
|
-
return [
|
|
52
|
+
return [last_signal, args] if @stop_events.include?(task)
|
|
53
53
|
|
|
54
54
|
if (next_task = next_for(task, last_signal))
|
|
55
55
|
task = next_task
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Trailblazer
|
|
2
|
+
class Activity
|
|
3
|
+
module Deprecate
|
|
4
|
+
module_function
|
|
5
|
+
|
|
6
|
+
def warn(caller_location, message)
|
|
7
|
+
location = caller_location ? location_for(caller_location) : nil
|
|
8
|
+
warning = [location, message].compact.join(" ")
|
|
9
|
+
|
|
10
|
+
Kernel.warn %{[Trailblazer] #{warning}\n}
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def location_for(caller_location)
|
|
14
|
+
caller_location = caller_location
|
|
15
|
+
line_no = caller_location.lineno
|
|
16
|
+
|
|
17
|
+
%{#{caller_location.absolute_path}:#{line_no}}
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -1,15 +1,54 @@
|
|
|
1
1
|
module Trailblazer
|
|
2
2
|
class Activity
|
|
3
3
|
# The Introspect API provides inflections for `Activity` instances.
|
|
4
|
-
#
|
|
5
|
-
#
|
|
4
|
+
#
|
|
5
|
+
# It abstracts internals about circuits and provides a convenient API to third-parties
|
|
6
|
+
# such as tracing, rendering an activity, or finding particular tasks.
|
|
6
7
|
module Introspect
|
|
8
|
+
# A TaskMap is a way to introspect an {Activity}. It allows finding a {TaskAttributes}
|
|
9
|
+
# instance by its ID given at compile-time, or its task.
|
|
10
|
+
#
|
|
11
|
+
# It is much simpler and faster than the Graph interface that might get (re)moved.
|
|
12
|
+
def self.TaskMap(activity)
|
|
13
|
+
schema = activity.to_h
|
|
14
|
+
nodes = schema[:nodes]
|
|
15
|
+
|
|
16
|
+
task_map_tuples =
|
|
17
|
+
nodes.collect do |node_attributes|
|
|
18
|
+
[
|
|
19
|
+
task = node_attributes[:task],
|
|
20
|
+
TaskMap::TaskAttributes(id: node_attributes[:id], task: task)
|
|
21
|
+
]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
TaskMap[task_map_tuples].freeze
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
class TaskMap < Hash
|
|
28
|
+
TaskAttributes = Struct.new(:id, :task) # TODO: extend at some point.
|
|
29
|
+
|
|
30
|
+
def self.TaskAttributes(id:, task:)
|
|
31
|
+
TaskMap::TaskAttributes.new(id, task).freeze
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def find_by_id(id)
|
|
35
|
+
tuple = find { |task, attrs| attrs[:id] == id } or return
|
|
36
|
+
tuple[1]
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
7
40
|
# TODO: order of step/fail/pass in Node would be cool to have
|
|
8
41
|
|
|
42
|
+
# TODO: Remove Graph. This is only useful to render the full
|
|
43
|
+
# circuit, which is a very specific task that could sit in `developer`,
|
|
44
|
+
# instead.
|
|
45
|
+
# Some thoughts here:
|
|
46
|
+
# * where do we need Schema.outputs? and where task.outputs?
|
|
47
|
+
#
|
|
48
|
+
#
|
|
9
49
|
# @private This API is still under construction.
|
|
10
50
|
class Graph
|
|
11
51
|
def initialize(activity)
|
|
12
|
-
@activity = activity
|
|
13
52
|
@schema = activity.to_h or raise
|
|
14
53
|
@circuit = @schema[:circuit]
|
|
15
54
|
@map = @circuit.to_h[:map]
|
|
@@ -22,6 +61,7 @@ module Trailblazer
|
|
|
22
61
|
find_with_block(&block)
|
|
23
62
|
end
|
|
24
63
|
|
|
64
|
+
# TODO: convert to {#to_a}.
|
|
25
65
|
def collect(strategy: :circuit)
|
|
26
66
|
@map.keys.each_with_index.collect { |task, i| yield find_with_block { |node| node.task == task }, i }
|
|
27
67
|
end
|
|
@@ -43,8 +83,15 @@ module Trailblazer
|
|
|
43
83
|
node_for(existing)
|
|
44
84
|
end
|
|
45
85
|
|
|
86
|
+
# Build a {Graph::Node} with outputs etc.
|
|
46
87
|
def node_for(node_attributes)
|
|
47
|
-
Node(
|
|
88
|
+
Node(
|
|
89
|
+
node_attributes.task,
|
|
90
|
+
node_attributes.id,
|
|
91
|
+
node_attributes.outputs, # [#<struct Trailblazer::Activity::Output signal=Trailblazer::Activity::Right, semantic=:success>]
|
|
92
|
+
outgoings_for(node_attributes),
|
|
93
|
+
node_attributes.data,
|
|
94
|
+
)
|
|
48
95
|
end
|
|
49
96
|
|
|
50
97
|
def Node(*args)
|
|
@@ -69,6 +116,26 @@ module Trailblazer
|
|
|
69
116
|
Graph.new(*args)
|
|
70
117
|
end
|
|
71
118
|
|
|
119
|
+
# @private
|
|
120
|
+
def self.find_path(activity, segments)
|
|
121
|
+
raise ArgumentError.new(%{[Trailblazer] Please pass #{activity}.to_h[:activity] into #find_path.}) unless activity.kind_of?(Trailblazer::Activity)
|
|
122
|
+
|
|
123
|
+
task_attributes = TaskMap::TaskAttributes(id: nil, task: activity)
|
|
124
|
+
last_graph, last_activity = nil, TaskWrap.container_activity_for(activity) # needed for empty/root path
|
|
125
|
+
|
|
126
|
+
segments.each do |segment|
|
|
127
|
+
task_map = Introspect.TaskMap(activity)
|
|
128
|
+
task, task_attributes = (task_map.find { |task, attributes| attributes[:id] == segment } or return) # DISCUSS: should we abstract these internals of {TaskMap} in {find_by_id}?
|
|
129
|
+
|
|
130
|
+
last_activity = activity
|
|
131
|
+
last_graph = task_map
|
|
132
|
+
|
|
133
|
+
activity = task
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
return task_attributes, last_activity, last_graph
|
|
137
|
+
end
|
|
138
|
+
|
|
72
139
|
def self.render_task(proc)
|
|
73
140
|
if proc.is_a?(Method)
|
|
74
141
|
|
|
@@ -4,7 +4,9 @@ class Trailblazer::Activity
|
|
|
4
4
|
# Implementation structures
|
|
5
5
|
Task = Struct.new(:circuit_task, :outputs, :extensions)
|
|
6
6
|
|
|
7
|
-
def self.Task(task, outputs, extensions=[])
|
|
7
|
+
def self.Task(task, outputs, extensions=[])
|
|
8
|
+
Task.new(task, outputs, extensions)
|
|
9
|
+
end
|
|
8
10
|
end
|
|
9
11
|
end
|
|
10
12
|
end
|
|
@@ -22,6 +22,7 @@ class Trailblazer::Activity
|
|
|
22
22
|
nodes = node_attributes(intermediate, implementation)
|
|
23
23
|
outputs = outputs(intermediate.stop_task_ids, nodes)
|
|
24
24
|
config = config(implementation, config: config)
|
|
25
|
+
|
|
25
26
|
Schema.new(circuit, outputs, nodes, config)
|
|
26
27
|
end
|
|
27
28
|
|
|
@@ -50,7 +51,7 @@ class Trailblazer::Activity
|
|
|
50
51
|
# Compute the connections for {circuit_task}.
|
|
51
52
|
def self.connections_for(outs, task_outputs, implementation)
|
|
52
53
|
Hash[
|
|
53
|
-
outs.collect { |required_out|
|
|
54
|
+
outs.collect { |required_out| # Intermediate::Out, it's abstract.
|
|
54
55
|
[
|
|
55
56
|
for_semantic(task_outputs, required_out.semantic).signal,
|
|
56
57
|
implementation.fetch(required_out.target).circuit_task
|
|
@@ -60,11 +61,12 @@ class Trailblazer::Activity
|
|
|
60
61
|
end
|
|
61
62
|
|
|
62
63
|
def self.node_attributes(intermediate, implementation)
|
|
63
|
-
intermediate.wiring.collect do |task_ref, outputs|
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
intermediate.wiring.collect do |task_ref, outputs|
|
|
65
|
+
id = task_ref.id
|
|
66
|
+
implementation_task = implementation.fetch(id)
|
|
67
|
+
data = task_ref[:data] # TODO: allow adding data from implementation.
|
|
66
68
|
|
|
67
|
-
NodeAttributes.new(
|
|
69
|
+
NodeAttributes.new(id, implementation_task.outputs, implementation_task.circuit_task, data)
|
|
68
70
|
end
|
|
69
71
|
end
|
|
70
72
|
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
module Trailblazer
|
|
2
2
|
class Activity
|
|
3
|
-
NodeAttributes
|
|
3
|
+
# In NodeAttributes we store data from Intermediate and Implementing compile-time.
|
|
4
|
+
# This would be lost otherwise.
|
|
5
|
+
NodeAttributes = Struct.new(:id, :outputs, :task, :data) # TODO: rename to Schema::Task::Attributes.
|
|
4
6
|
|
|
5
|
-
# Schema is primitive data structure + an invoker (usually coming from Activity etc)
|
|
6
7
|
class Schema < Struct.new(:circuit, :outputs, :nodes, :config)
|
|
8
|
+
# {:nodes} is passed directly from {compile_activity}. We need to store this data here.
|
|
7
9
|
|
|
8
10
|
# @!method to_h()
|
|
9
11
|
# Returns a hash containing the schema's components.
|
|
@@ -14,7 +14,7 @@ module Trailblazer
|
|
|
14
14
|
# An {Extension} can be used for {:wrap_runtime}. It expects a collection of
|
|
15
15
|
# "friendly interface" arrays.
|
|
16
16
|
#
|
|
17
|
-
# TaskWrap.Extension([
|
|
17
|
+
# TaskWrap.Extension([task, id: "my_logger", append: "task_wrap.call_task"], [...])
|
|
18
18
|
#
|
|
19
19
|
# If you want a {wrap_static} extension, wrap it using `Extension.WrapStatic.new`.
|
|
20
20
|
def self.Extension(*inserts, merge: nil)
|
|
@@ -59,6 +59,27 @@ Please use the new TaskWrap.Extension() API: #FIXME!!!"
|
|
|
59
59
|
TaskWrap::Extension.new(*inserts)
|
|
60
60
|
end
|
|
61
61
|
end # Merge
|
|
62
|
+
|
|
63
|
+
# Implements adapter for a callable in a Pipeline.
|
|
64
|
+
class TaskAdapter < Circuit::TaskAdapter
|
|
65
|
+
# Returns a {Pipeline::TaskAdapter} instance that can be used directly in a Pipeline.
|
|
66
|
+
# When `call`ed, it returns a Pipeline-interface return set.
|
|
67
|
+
#
|
|
68
|
+
# @see Circuit::TaskAdapter.for_step
|
|
69
|
+
def self.for_step(callable, **)
|
|
70
|
+
circuit_step = Circuit.Step(callable, option: false) # Since we don't have {:exec_context} in Pipeline, Option doesn't make much sense.
|
|
71
|
+
|
|
72
|
+
TaskAdapter.new(circuit_step) # return a {Pipeline::TaskAdapter}
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def call(wrap_ctx, args)
|
|
76
|
+
_result, _new_wrap_ctx = @circuit_step.([wrap_ctx, args]) # For funny reasons, the Circuit::Step's call interface is compatible to the Pipeline's.
|
|
77
|
+
|
|
78
|
+
# DISCUSS: we're mutating wrap_ctx, that's the whole point of this abstraction (plus kwargs).
|
|
79
|
+
|
|
80
|
+
return wrap_ctx, args
|
|
81
|
+
end
|
|
82
|
+
end # TaskAdapter
|
|
62
83
|
end
|
|
63
84
|
end
|
|
64
85
|
end
|
|
@@ -14,14 +14,14 @@ module Trailblazer
|
|
|
14
14
|
|
|
15
15
|
# Compute runtime arguments necessary to execute a taskWrap per task of the activity.
|
|
16
16
|
# This method is the top-level entry, called only once for the entire activity graph.
|
|
17
|
-
# [:
|
|
18
|
-
|
|
17
|
+
# [:container_activity] the top-most "activity". This only has to look like an Activity
|
|
18
|
+
# and exposes a #[] interface so [:wrap_static] can be read and it's compatible to {Trace}.
|
|
19
|
+
# It is the virtual activity that "hosts" the actual {activity}.
|
|
20
|
+
def invoke(activity, args, wrap_runtime: {}, container_activity: container_activity_for(activity), **circuit_options)
|
|
19
21
|
circuit_options = circuit_options.merge(
|
|
20
22
|
runner: TaskWrap::Runner,
|
|
21
23
|
wrap_runtime: wrap_runtime,
|
|
22
|
-
#
|
|
23
|
-
# access {activity[:wrap_static]} to compile the effective taskWrap.
|
|
24
|
-
activity: {wrap_static: {activity => wrap_static}, nodes: {}}, # for Runner. Ideally we'd have a list of all static_wraps here (even nested).
|
|
24
|
+
activity: container_activity, # for Runner. Ideally we'd have a list of all static_wraps here (even nested).
|
|
25
25
|
)
|
|
26
26
|
|
|
27
27
|
# signal, (ctx, flow), circuit_options =
|
|
@@ -31,14 +31,27 @@ module Trailblazer
|
|
|
31
31
|
# {:extension} API
|
|
32
32
|
# Extend the static taskWrap from a macro or DSL call.
|
|
33
33
|
# Gets executed in {Intermediate.call} which also provides {config}.
|
|
34
|
+
def initial_wrap_static
|
|
35
|
+
INITIAL_WRAP_STATIC
|
|
36
|
+
end
|
|
34
37
|
|
|
35
|
-
|
|
36
|
-
|
|
38
|
+
# This is the top-most "activity" that hosts the actual activity being run.
|
|
39
|
+
# The data structure is used in {TaskWrap.wrap_static_for}, where we
|
|
40
|
+
# access {activity[:wrap_static]} to compile the effective taskWrap.
|
|
41
|
+
#
|
|
42
|
+
# It's also needed in Trace/Introspect and mimicks the host containing the actual activity.
|
|
43
|
+
#
|
|
44
|
+
# DISCUSS: we could cache that on Strategy/Operation level.
|
|
45
|
+
# merging the **config hash is 1/4 slower than before.
|
|
46
|
+
def container_activity_for(activity, wrap_static: initial_wrap_static, **config)
|
|
47
|
+
{
|
|
48
|
+
wrap_static: {activity => wrap_static},
|
|
49
|
+
**config
|
|
50
|
+
}
|
|
37
51
|
end
|
|
52
|
+
|
|
53
|
+
INITIAL_WRAP_STATIC = Pipeline.new([Pipeline.Row("task_wrap.call_task", TaskWrap.method(:call_task))].freeze)
|
|
38
54
|
end # TaskWrap
|
|
39
55
|
end
|
|
40
56
|
end
|
|
41
|
-
|
|
42
|
-
require "trailblazer/activity/task_wrap/call_task"
|
|
43
|
-
require "trailblazer/activity/task_wrap/runner"
|
|
44
|
-
require "trailblazer/activity/task_wrap/extension"
|
|
57
|
+
|
|
@@ -65,7 +65,7 @@ module Trailblazer
|
|
|
65
65
|
end
|
|
66
66
|
|
|
67
67
|
# Use {TaskWrap.invoke} to call the activity.
|
|
68
|
-
def assert_invoke(activity, terminus: :success, seq: "[]", circuit_options: {}, expected_ctx_variables: {}, **ctx_variables)
|
|
68
|
+
def assert_invoke(activity, terminus: :success, seq: "[]", circuit_options: {}, expected_ctx_variables: {}, **ctx_variables)
|
|
69
69
|
signal, (ctx, _flow_options) = TaskWrap.invoke(
|
|
70
70
|
activity,
|
|
71
71
|
[
|
|
@@ -94,7 +94,7 @@ module Trailblazer
|
|
|
94
94
|
Success = Activity::End(:success)
|
|
95
95
|
end
|
|
96
96
|
|
|
97
|
-
# TODO: Remove this once all
|
|
97
|
+
# TODO: Remove this once all its references are removed
|
|
98
98
|
def implementing
|
|
99
99
|
Implementing
|
|
100
100
|
end
|
|
@@ -127,14 +127,13 @@ module Trailblazer
|
|
|
127
127
|
@_flat_activity = Activity.new(schema)
|
|
128
128
|
end
|
|
129
129
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
130
|
+
# FIXME: why is this in Testing? We don't need this anywhere but in this gem.
|
|
131
|
+
def nested_activity(flat_activity: bc, d_id: :D)
|
|
133
132
|
intermediate = Inter.new(
|
|
134
133
|
{
|
|
135
134
|
Inter::TaskRef("Start.default") => [Inter::Out(:success, :B)],
|
|
136
|
-
Inter::TaskRef(:B, more: true) => [Inter::Out(:success,
|
|
137
|
-
Inter::TaskRef(
|
|
135
|
+
Inter::TaskRef(:B, more: true) => [Inter::Out(:success, d_id)],
|
|
136
|
+
Inter::TaskRef(d_id) => [Inter::Out(:success, :E)],
|
|
138
137
|
Inter::TaskRef(:E) => [Inter::Out(:success, "End.success")],
|
|
139
138
|
Inter::TaskRef("End.success", stop_event: true) => [Inter::Out(:success, nil)]
|
|
140
139
|
},
|
|
@@ -145,14 +144,14 @@ module Trailblazer
|
|
|
145
144
|
implementation = {
|
|
146
145
|
"Start.default" => Schema::Implementation::Task(st = Implementing::Start, [Activity::Output(Activity::Right, :success)], []),
|
|
147
146
|
:B => Schema::Implementation::Task(b = Implementing.method(:b), [Activity::Output(Activity::Right, :success)], []),
|
|
148
|
-
|
|
147
|
+
d_id => Schema::Implementation::Task(flat_activity, [Activity::Output(Implementing::Success, :success)], []),
|
|
149
148
|
:E => Schema::Implementation::Task(e = Implementing.method(:f), [Activity::Output(Activity::Right, :success)], []),
|
|
150
149
|
"End.success" => Schema::Implementation::Task(_es = Implementing::Success, [Activity::Output(Implementing::Success, :success)], []), # DISCUSS: End has one Output, signal is itself?
|
|
151
150
|
}
|
|
152
151
|
|
|
153
152
|
schema = Inter.(intermediate, implementation)
|
|
154
153
|
|
|
155
|
-
|
|
154
|
+
Activity.new(schema)
|
|
156
155
|
end
|
|
157
156
|
|
|
158
157
|
alias_method :bc, :flat_activity
|
data/lib/trailblazer/activity.rb
CHANGED
|
@@ -10,7 +10,7 @@ module Trailblazer
|
|
|
10
10
|
def call(args, **circuit_options)
|
|
11
11
|
@schema[:circuit].(
|
|
12
12
|
args,
|
|
13
|
-
**
|
|
13
|
+
**circuit_options.merge(activity: self)
|
|
14
14
|
)
|
|
15
15
|
end
|
|
16
16
|
|
|
@@ -27,6 +27,12 @@ module Trailblazer
|
|
|
27
27
|
def inspect
|
|
28
28
|
%{#<Trailblazer::Activity:0x#{object_id}>}
|
|
29
29
|
end
|
|
30
|
+
|
|
31
|
+
# Canonical entry-point to invoke an {Activity} or Strategy such as {Activity::Railway}
|
|
32
|
+
# with its taskWrap.
|
|
33
|
+
def self.call(activity, ctx)
|
|
34
|
+
TaskWrap.invoke(activity, [ctx, {}])
|
|
35
|
+
end
|
|
30
36
|
end # Activity
|
|
31
37
|
end
|
|
32
38
|
|
|
@@ -35,12 +41,16 @@ require "trailblazer/activity/schema"
|
|
|
35
41
|
require "trailblazer/activity/schema/implementation"
|
|
36
42
|
require "trailblazer/activity/schema/intermediate"
|
|
37
43
|
require "trailblazer/activity/circuit"
|
|
44
|
+
require "trailblazer/activity/circuit/task_adapter"
|
|
38
45
|
require "trailblazer/activity/config"
|
|
39
46
|
require "trailblazer/activity/introspect"
|
|
47
|
+
require "trailblazer/activity/task_wrap/pipeline"
|
|
48
|
+
require "trailblazer/activity/task_wrap/call_task"
|
|
40
49
|
require "trailblazer/activity/task_wrap"
|
|
50
|
+
require "trailblazer/activity/task_wrap/runner"
|
|
51
|
+
require "trailblazer/activity/task_wrap/extension"
|
|
41
52
|
require "trailblazer/activity/adds"
|
|
42
|
-
require "trailblazer/activity/
|
|
43
|
-
|
|
53
|
+
require "trailblazer/activity/deprecate"
|
|
44
54
|
require "trailblazer/option"
|
|
45
55
|
require "trailblazer/context"
|
|
46
56
|
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: trailblazer-activity
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.15.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: 2022-
|
|
11
|
+
date: 2022-12-14 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: trailblazer-context
|
|
@@ -128,13 +128,14 @@ files:
|
|
|
128
128
|
- lib/trailblazer/activity.rb
|
|
129
129
|
- lib/trailblazer/activity/adds.rb
|
|
130
130
|
- lib/trailblazer/activity/circuit.rb
|
|
131
|
+
- lib/trailblazer/activity/circuit/task_adapter.rb
|
|
131
132
|
- lib/trailblazer/activity/config.rb
|
|
133
|
+
- lib/trailblazer/activity/deprecate.rb
|
|
132
134
|
- lib/trailblazer/activity/introspect.rb
|
|
133
135
|
- lib/trailblazer/activity/schema.rb
|
|
134
136
|
- lib/trailblazer/activity/schema/implementation.rb
|
|
135
137
|
- lib/trailblazer/activity/schema/intermediate.rb
|
|
136
138
|
- lib/trailblazer/activity/structures.rb
|
|
137
|
-
- lib/trailblazer/activity/task_builder.rb
|
|
138
139
|
- lib/trailblazer/activity/task_wrap.rb
|
|
139
140
|
- lib/trailblazer/activity/task_wrap/call_task.rb
|
|
140
141
|
- lib/trailblazer/activity/task_wrap/extension.rb
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
module Trailblazer
|
|
2
|
-
module Activity::TaskBuilder
|
|
3
|
-
# every step is wrapped by this proc/decider. this is executed in the circuit as the actual task.
|
|
4
|
-
# Step calls step.(options, **options, flow_options)
|
|
5
|
-
# Output signal binary: true=>Right, false=>Left.
|
|
6
|
-
# Passes through all subclasses of Direction.~~~~~~~~~~~~~~~~~
|
|
7
|
-
def self.Binary(user_proc)
|
|
8
|
-
Task.new(Trailblazer::Option(user_proc), user_proc)
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
# Translates the return value of the user step into a valid signal.
|
|
12
|
-
# Note that it passes through subclasses of {Signal}.
|
|
13
|
-
def self.binary_signal_for(result, on_true, on_false)
|
|
14
|
-
if result.is_a?(Class) && result < Activity::Signal
|
|
15
|
-
result
|
|
16
|
-
else
|
|
17
|
-
result ? on_true : on_false
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
# Wraps a {task} (that usually expects the task interface) into a circuit interface
|
|
22
|
-
# that can be used directly in a {Circuit}.
|
|
23
|
-
# We expect {task} to be exposing an {Option()} interface when calling it.
|
|
24
|
-
class Task
|
|
25
|
-
def initialize(task, user_proc)
|
|
26
|
-
@task = task
|
|
27
|
-
@user_proc = user_proc
|
|
28
|
-
freeze
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def call((ctx, flow_options), **circuit_options)
|
|
32
|
-
# Execute the user step with TRB's kw args.
|
|
33
|
-
# {@task} is/implements {Trailblazer::Option} interface.
|
|
34
|
-
result = call_option(@task, [ctx, flow_options], **circuit_options)
|
|
35
|
-
|
|
36
|
-
# Return an appropriate signal which direction to go next.
|
|
37
|
-
signal = Activity::TaskBuilder.binary_signal_for(result, Activity::Right, Activity::Left)
|
|
38
|
-
|
|
39
|
-
return signal, [ctx, flow_options]
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
# Invoke the original {user_proc} that is wrapped in an {Option()}.
|
|
43
|
-
private def call_option(task_with_option_interface, (ctx, _flow_options), **circuit_options)
|
|
44
|
-
task_with_option_interface.(ctx, keyword_arguments: ctx.to_hash, **circuit_options) # circuit_options contains :exec_context.
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def inspect # TODO: make me private!
|
|
48
|
-
%{#<Trailblazer::Activity::TaskBuilder::Task user_proc=#{Trailblazer::Activity::Introspect.render_task(@user_proc)}>}
|
|
49
|
-
end
|
|
50
|
-
alias_method :to_s, :inspect
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
end
|