trailblazer-activity 0.14.0 → 0.15.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 +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
|