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
@@ -5,142 +5,49 @@ module Trailblazer
|
|
5
5
|
# It abstracts internals about circuits and provides a convenient API to third-parties
|
6
6
|
# such as tracing, rendering an activity, or finding particular tasks.
|
7
7
|
module Introspect
|
8
|
-
#
|
9
|
-
|
10
|
-
#
|
11
|
-
# It is much simpler and faster than the Graph interface that might get (re)moved.
|
12
|
-
def self.TaskMap(activity)
|
8
|
+
# Public entry point for {Activity} instance introspection.
|
9
|
+
def self.Nodes(activity, task: nil, **options)
|
13
10
|
schema = activity.to_h
|
14
11
|
nodes = schema[:nodes]
|
15
12
|
|
16
|
-
|
17
|
-
|
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
|
13
|
+
return Nodes.find_by_id(nodes, options[:id]) if options.key?(:id)
|
14
|
+
return nodes.fetch(task) if task
|
15
|
+
nodes
|
25
16
|
end
|
26
17
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
def self.
|
31
|
-
|
32
|
-
end
|
33
|
-
|
34
|
-
def find_by_id(id)
|
35
|
-
tuple = find { |task, attrs| attrs[:id] == id } or return
|
18
|
+
module Nodes
|
19
|
+
# @private
|
20
|
+
# @return Attributes data structure
|
21
|
+
def self.find_by_id(nodes, id)
|
22
|
+
tuple = nodes.find { |task, attrs| attrs.id == id } or return
|
36
23
|
tuple[1]
|
37
24
|
end
|
38
25
|
end
|
39
26
|
|
40
|
-
# TODO: order of step/fail/pass in Node would be cool to have
|
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
|
-
#
|
49
|
-
# @private This API is still under construction.
|
50
|
-
class Graph
|
51
|
-
def initialize(activity)
|
52
|
-
@schema = activity.to_h or raise
|
53
|
-
@circuit = @schema[:circuit]
|
54
|
-
@map = @circuit.to_h[:map]
|
55
|
-
@configs = @schema[:nodes]
|
56
|
-
end
|
57
|
-
|
58
|
-
def find(id = nil, &block)
|
59
|
-
return find_by_id(id) unless block_given?
|
60
|
-
|
61
|
-
find_with_block(&block)
|
62
|
-
end
|
63
|
-
|
64
|
-
# TODO: convert to {#to_a}.
|
65
|
-
def collect(strategy: :circuit)
|
66
|
-
@map.keys.each_with_index.collect { |task, i| yield find_with_block { |node| node.task == task }, i }
|
67
|
-
end
|
68
|
-
|
69
|
-
def stop_events
|
70
|
-
@circuit.to_h[:end_events]
|
71
|
-
end
|
72
|
-
|
73
|
-
private
|
74
|
-
|
75
|
-
def find_by_id(id)
|
76
|
-
node = @configs.find { |_node| _node.id == id } or return
|
77
|
-
node_for(node)
|
78
|
-
end
|
79
|
-
|
80
|
-
def find_with_block
|
81
|
-
existing = @configs.find { |node| yield Node(node.task, node.id, node.outputs, node.data) } or return
|
82
|
-
|
83
|
-
node_for(existing)
|
84
|
-
end
|
85
|
-
|
86
|
-
# Build a {Graph::Node} with outputs etc.
|
87
|
-
def node_for(node_attributes)
|
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
|
-
)
|
95
|
-
end
|
96
|
-
|
97
|
-
def Node(*args)
|
98
|
-
Node.new(*args).freeze
|
99
|
-
end
|
100
|
-
|
101
|
-
Node = Struct.new(:task, :id, :outputs, :outgoings, :data)
|
102
|
-
Outgoing = Struct.new(:output, :task)
|
103
|
-
|
104
|
-
def outgoings_for(node)
|
105
|
-
outputs = node.outputs
|
106
|
-
connections = @map[node.task]
|
107
|
-
|
108
|
-
connections.collect do |signal, target|
|
109
|
-
output = outputs.find { |out| out.signal == signal }
|
110
|
-
Outgoing.new(output, target)
|
111
|
-
end
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
def self.Graph(*args)
|
116
|
-
Graph.new(*args)
|
117
|
-
end
|
118
|
-
|
119
27
|
# @private
|
120
28
|
def self.find_path(activity, segments)
|
121
|
-
raise ArgumentError.new(%
|
29
|
+
raise ArgumentError.new(%([Trailblazer] Please pass #{activity}.to_h[:activity] into #find_path.)) unless activity.is_a?(Trailblazer::Activity)
|
122
30
|
|
123
|
-
|
124
|
-
last_graph, last_activity = nil, TaskWrap.container_activity_for(activity) # needed for empty/root path
|
31
|
+
segments = [nil, *segments]
|
125
32
|
|
126
|
-
|
127
|
-
|
128
|
-
|
33
|
+
attributes = nil
|
34
|
+
last_activity = nil
|
35
|
+
activity = TaskWrap.container_activity_for(activity) # needed for empty/root path
|
129
36
|
|
37
|
+
segments.each do |segment|
|
38
|
+
attributes = Introspect.Nodes(activity, id: segment) or return nil
|
130
39
|
last_activity = activity
|
131
|
-
|
132
|
-
|
133
|
-
activity = task
|
40
|
+
activity = attributes.task
|
134
41
|
end
|
135
42
|
|
136
|
-
return
|
43
|
+
return attributes, last_activity
|
137
44
|
end
|
138
45
|
|
139
46
|
def self.render_task(proc)
|
140
47
|
if proc.is_a?(Method)
|
141
48
|
|
142
49
|
receiver = proc.receiver
|
143
|
-
receiver = receiver.is_a?(Class) ? (receiver.name || "#<Class:0x>") : (receiver.name || "#<Module:0x>") #"#<Class:0x>"
|
50
|
+
receiver = receiver.is_a?(Class) ? (receiver.name || "#<Class:0x>") : (receiver.name || "#<Module:0x>") # "#<Class:0x>"
|
144
51
|
|
145
52
|
return "#<Method: #{receiver}.#{proc.name}>"
|
146
53
|
elsif proc.is_a?(Symbol)
|
@@ -149,6 +56,13 @@ module Trailblazer
|
|
149
56
|
|
150
57
|
proc.inspect
|
151
58
|
end
|
59
|
+
|
60
|
+
# TODO: remove with 0.1.0.
|
61
|
+
def self.Graph(*args)
|
62
|
+
Deprecate.warn caller_locations[0], %(`Trailblazer::Activity::Introspect::Graph` is deprecated. Please use `Trailblazer::Developer::Introspect.Graph`)
|
63
|
+
|
64
|
+
Trailblazer::Developer::Introspect::Graph.new(*args)
|
65
|
+
end
|
152
66
|
end # Introspect
|
153
67
|
end
|
154
68
|
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module Trailblazer
|
2
|
+
class Activity
|
3
|
+
class Schema
|
4
|
+
class Intermediate
|
5
|
+
# @private This class might get removed in 0.17.0.
|
6
|
+
module Compiler
|
7
|
+
module_function
|
8
|
+
|
9
|
+
DEFAULT_CONFIG = {wrap_static: Hash.new(Activity::TaskWrap.initial_wrap_static.freeze)} # DISCUSS: this really doesn't have to be here, but works for now and we want it in 99%.
|
10
|
+
|
11
|
+
# Compiles a {Schema} instance from an {intermediate} structure and
|
12
|
+
# the {implementation} object references.
|
13
|
+
#
|
14
|
+
# Intermediate structure, Implementation, calls extensions, passes {config} # TODO
|
15
|
+
def call(intermediate, implementation, config_merge: {})
|
16
|
+
config = DEFAULT_CONFIG
|
17
|
+
.merge(config_merge)
|
18
|
+
.freeze
|
19
|
+
|
20
|
+
circuit, outputs, nodes, config = schema_components(intermediate, implementation, config)
|
21
|
+
|
22
|
+
Schema.new(circuit, outputs, nodes, config)
|
23
|
+
end
|
24
|
+
|
25
|
+
# From the intermediate "template" and the actual implementation, compile a {Circuit} instance.
|
26
|
+
def schema_components(intermediate, implementation, config)
|
27
|
+
wiring = {}
|
28
|
+
nodes_attributes = []
|
29
|
+
|
30
|
+
intermediate.wiring.each do |task_ref, outs|
|
31
|
+
id = task_ref.id
|
32
|
+
impl_task = implementation.fetch(id)
|
33
|
+
task = impl_task.circuit_task
|
34
|
+
outputs = impl_task.outputs
|
35
|
+
|
36
|
+
wiring[task] = connections_for(outs, outputs, implementation)
|
37
|
+
|
38
|
+
nodes_attributes << [
|
39
|
+
id, # id
|
40
|
+
task, # task
|
41
|
+
task_ref[:data], # TODO: allow adding data from implementation.
|
42
|
+
outputs # {Activity::Output}s
|
43
|
+
]
|
44
|
+
|
45
|
+
# Invoke each task's extensions (usually coming from the DSL user or some macro).
|
46
|
+
# We're expecting each {ext} to be a {TaskWrap::Extension::WrapStatic} instance.
|
47
|
+
config = invoke_extensions_for(config, impl_task, id)
|
48
|
+
end
|
49
|
+
|
50
|
+
start_id = intermediate.start_task_id
|
51
|
+
|
52
|
+
terminus_to_output = terminus_to_output(intermediate, implementation)
|
53
|
+
activity_outputs = terminus_to_output.values
|
54
|
+
|
55
|
+
circuit = Circuit.new(
|
56
|
+
wiring,
|
57
|
+
terminus_to_output.keys, # termini
|
58
|
+
start_task: implementation.fetch(start_id).circuit_task
|
59
|
+
)
|
60
|
+
|
61
|
+
return circuit,
|
62
|
+
activity_outputs,
|
63
|
+
Schema::Nodes(nodes_attributes),
|
64
|
+
config
|
65
|
+
end
|
66
|
+
|
67
|
+
# Compute the connections for {circuit_task}.
|
68
|
+
# For a terminus, this produces an empty hash.
|
69
|
+
def connections_for(intermediate_outs, task_outputs, implementation)
|
70
|
+
intermediate_outs.collect { |intermediate_out| # Intermediate::Out, it's abstract.
|
71
|
+
[
|
72
|
+
output_for_semantic(task_outputs, intermediate_out.semantic).signal,
|
73
|
+
implementation.fetch(intermediate_out.target).circuit_task # Find the implementation task, the Ruby code component for runtime.
|
74
|
+
]
|
75
|
+
}.to_h
|
76
|
+
end
|
77
|
+
|
78
|
+
def terminus_to_output(intermediate, implementation)
|
79
|
+
terminus_to_semantic = intermediate.stop_task_ids
|
80
|
+
|
81
|
+
terminus_to_semantic.collect do |id, semantic|
|
82
|
+
terminus_task = implementation.fetch(id).circuit_task
|
83
|
+
|
84
|
+
[
|
85
|
+
terminus_task,
|
86
|
+
Activity::Output(terminus_task, semantic) # The End instance is the signal.
|
87
|
+
]
|
88
|
+
end.to_h
|
89
|
+
end
|
90
|
+
|
91
|
+
# Run all Implementation::Task.extensions and return new {config}
|
92
|
+
def invoke_extensions_for(config, impl_task, id)
|
93
|
+
impl_task
|
94
|
+
.extensions
|
95
|
+
.inject(config) { |cfg, ext| ext.(config: cfg, id: id, task: impl_task) } # {ext} must return new config hash.
|
96
|
+
end
|
97
|
+
|
98
|
+
# In an array of {Activity::Output}, find the output matching {semantic}.
|
99
|
+
def output_for_semantic(outputs, semantic)
|
100
|
+
outputs.find { |out| out.semantic == semantic } or raise "`#{semantic}` not found in implementation"
|
101
|
+
end
|
102
|
+
end # Intermediate
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -1,10 +1,11 @@
|
|
1
1
|
class Trailblazer::Activity
|
2
2
|
class Schema
|
3
|
+
# @private This class might get removed in 0.17.0.
|
3
4
|
module Implementation
|
4
5
|
# Implementation structures
|
5
6
|
Task = Struct.new(:circuit_task, :outputs, :extensions)
|
6
7
|
|
7
|
-
def self.Task(task, outputs, extensions=[])
|
8
|
+
def self.Task(task, outputs, extensions = [])
|
8
9
|
Task.new(task, outputs, extensions)
|
9
10
|
end
|
10
11
|
end
|
@@ -2,97 +2,17 @@ class Trailblazer::Activity
|
|
2
2
|
class Schema
|
3
3
|
# An {Intermediate} structure defines the *structure* of the circuit. It usually
|
4
4
|
# comes from a DSL or a visual editor.
|
5
|
-
class
|
5
|
+
# @private This class might get removed in 0.17.0.
|
6
|
+
class Intermediate < Struct.new(:wiring, :stop_task_ids, :start_task_id)
|
6
7
|
TaskRef = Struct.new(:id, :data) # TODO: rename to NodeRef
|
7
8
|
Out = Struct.new(:semantic, :target)
|
8
9
|
|
9
|
-
def self.TaskRef(id, data={})
|
10
|
-
|
11
|
-
|
12
|
-
# Compiles a {Schema} instance from an {intermediate} structure and
|
13
|
-
# the {implementation} object references.
|
14
|
-
#
|
15
|
-
# Intermediate structure, Implementation, calls extensions, passes {config} # TODO
|
16
|
-
def self.call(intermediate, implementation, config_merge: {})
|
17
|
-
config_default = {wrap_static: Hash.new(TaskWrap.initial_wrap_static)} # DISCUSS: this really doesn't have to be here, but works for now and we want it in 99%.
|
18
|
-
config = config_default.merge(config_merge)
|
19
|
-
config.freeze
|
20
|
-
|
21
|
-
circuit = circuit(intermediate, implementation)
|
22
|
-
nodes = node_attributes(intermediate, implementation)
|
23
|
-
outputs = outputs(intermediate.stop_task_ids, nodes)
|
24
|
-
config = config(implementation, config: config)
|
25
|
-
|
26
|
-
Schema.new(circuit, outputs, nodes, config)
|
27
|
-
end
|
28
|
-
|
29
|
-
# From the intermediate "template" and the actual implementation, compile a {Circuit} instance.
|
30
|
-
def self.circuit(intermediate, implementation)
|
31
|
-
end_events = intermediate.stop_task_ids
|
32
|
-
|
33
|
-
wiring = Hash[
|
34
|
-
intermediate.wiring.collect do |task_ref, outs|
|
35
|
-
task = implementation.fetch(task_ref.id)
|
36
|
-
|
37
|
-
[
|
38
|
-
task.circuit_task,
|
39
|
-
end_events.include?(task_ref.id) ? {} : connections_for(outs, task.outputs, implementation)
|
40
|
-
]
|
41
|
-
end
|
42
|
-
]
|
43
|
-
|
44
|
-
Circuit.new(
|
45
|
-
wiring,
|
46
|
-
intermediate.stop_task_ids.collect { |id| implementation.fetch(id).circuit_task },
|
47
|
-
start_task: intermediate.start_task_ids.collect { |id| implementation.fetch(id).circuit_task }[0]
|
48
|
-
)
|
49
|
-
end
|
50
|
-
|
51
|
-
# Compute the connections for {circuit_task}.
|
52
|
-
def self.connections_for(outs, task_outputs, implementation)
|
53
|
-
Hash[
|
54
|
-
outs.collect { |required_out| # Intermediate::Out, it's abstract.
|
55
|
-
[
|
56
|
-
for_semantic(task_outputs, required_out.semantic).signal,
|
57
|
-
implementation.fetch(required_out.target).circuit_task
|
58
|
-
]
|
59
|
-
}.compact
|
60
|
-
]
|
10
|
+
def self.TaskRef(id, data = {})
|
11
|
+
TaskRef.new(id, data)
|
61
12
|
end
|
62
13
|
|
63
|
-
def self.
|
64
|
-
|
65
|
-
id = task_ref.id
|
66
|
-
implementation_task = implementation.fetch(id)
|
67
|
-
data = task_ref[:data] # TODO: allow adding data from implementation.
|
68
|
-
|
69
|
-
NodeAttributes.new(id, implementation_task.outputs, implementation_task.circuit_task, data)
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
# intermediate/implementation independent.
|
74
|
-
def self.outputs(stop_task_ids, nodes_attributes)
|
75
|
-
stop_task_ids.collect do |id|
|
76
|
-
# Grab the {outputs} of the stop nodes.
|
77
|
-
nodes_attributes.find { |node_attrs| id == node_attrs.id }.outputs
|
78
|
-
end.flatten(1)
|
79
|
-
end
|
80
|
-
|
81
|
-
# Invoke each task's extensions (usually coming from the DSL user or some macro).
|
82
|
-
# We're expecting each {ext} to be a {TaskWrap::Extension::WrapStatic} instance.
|
83
|
-
def self.config(implementation, config:)
|
84
|
-
implementation.each do |id, task|
|
85
|
-
task.extensions.each { |ext| config = ext.(config: config, id: id, task: task) } # DISCUSS: ext must return new {Config}.
|
86
|
-
end
|
87
|
-
|
88
|
-
config
|
89
|
-
end
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
# Apply to any array.
|
94
|
-
private_class_method def self.for_semantic(outputs, semantic)
|
95
|
-
outputs.find { |out| out.semantic == semantic } or raise "`#{semantic}` not found"
|
14
|
+
def self.Out(*args)
|
15
|
+
Out.new(*args)
|
96
16
|
end
|
97
17
|
end # Intermediate
|
98
18
|
end
|
@@ -1,15 +1,38 @@
|
|
1
1
|
module Trailblazer
|
2
2
|
class Activity
|
3
|
-
#
|
4
|
-
#
|
5
|
-
NodeAttributes = Struct.new(:id, :outputs, :task, :data) # TODO: rename to Schema::Task::Attributes.
|
6
|
-
|
3
|
+
# The idea with {:config} is to have a generic runtime store for feature fields
|
4
|
+
# like {:wrap_static} but also for flags, e.g. `each: true` from the Each() macro.
|
7
5
|
class Schema < Struct.new(:circuit, :outputs, :nodes, :config)
|
8
6
|
# {:nodes} is passed directly from {compile_activity}. We need to store this data here.
|
9
7
|
|
10
8
|
# @!method to_h()
|
11
9
|
# Returns a hash containing the schema's components.
|
12
10
|
|
11
|
+
class Nodes < Hash
|
12
|
+
# In Attributes we store data from Intermediate and Implementing compile-time.
|
13
|
+
# This would be lost otherwise.
|
14
|
+
Attributes = Struct.new(:id, :task, :data, :outputs)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Builder for {Schema::Nodes} datastructure.
|
18
|
+
#
|
19
|
+
# A {Nodes} instance is a hash of Attributes, keyed by task. It turned out that
|
20
|
+
# 90% of introspect lookups, we search for attributes for a particular *task*, not ID.
|
21
|
+
# That's why in 0.16.0 we changed this structure.5
|
22
|
+
#
|
23
|
+
# Nodes{#<task> => #<Nodes::Attributes id= task= data= outputs=>}
|
24
|
+
#
|
25
|
+
# @private Please use {Introspect.Nodes} for querying nodes.
|
26
|
+
def self.Nodes(nodes)
|
27
|
+
Nodes[
|
28
|
+
nodes.collect do |attrs|
|
29
|
+
[
|
30
|
+
attrs[1], # task
|
31
|
+
Nodes::Attributes.new(*attrs).freeze
|
32
|
+
]
|
33
|
+
end
|
34
|
+
].freeze
|
35
|
+
end
|
13
36
|
end # Schema
|
14
37
|
end
|
15
38
|
end
|
@@ -12,8 +12,8 @@ module Trailblazer
|
|
12
12
|
@options = options.merge(semantic: semantic)
|
13
13
|
end
|
14
14
|
|
15
|
-
def call(args, **
|
16
|
-
return self, args
|
15
|
+
def call(args, **)
|
16
|
+
return self, args
|
17
17
|
end
|
18
18
|
|
19
19
|
def to_h
|
@@ -21,21 +21,23 @@ module Trailblazer
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def to_s
|
24
|
-
%
|
24
|
+
%(#<#{self.class.name} #{@options.collect { |k, v| "#{k}=#{v.inspect}" }.join(" ")}>)
|
25
25
|
end
|
26
26
|
|
27
27
|
alias inspect to_s
|
28
28
|
end
|
29
29
|
|
30
30
|
class Start < End
|
31
|
-
def call(args, **
|
32
|
-
return Activity::Right, args
|
31
|
+
def call(args, **)
|
32
|
+
return Activity::Right, args
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
|
-
class Signal;
|
36
|
+
class Signal; end
|
37
|
+
|
37
38
|
class Right < Signal; end
|
38
|
-
|
39
|
+
|
40
|
+
class Left < Signal; end
|
39
41
|
|
40
42
|
# signal: actual signal emitted by the task
|
41
43
|
# color: the mapping, where this signal will travel to. This can be e.g. Left=>:success. The polarization when building the graph.
|
@@ -13,7 +13,10 @@ class Trailblazer::Activity
|
|
13
13
|
return_signal, return_args = task.(original_arguments, **original_circuit_options)
|
14
14
|
|
15
15
|
# DISCUSS: do we want original_args here to be passed on, or the "effective" return_args which are different to original_args now?
|
16
|
-
wrap_ctx = wrap_ctx.merge(
|
16
|
+
wrap_ctx = wrap_ctx.merge(
|
17
|
+
return_signal: return_signal,
|
18
|
+
return_args: return_args
|
19
|
+
)
|
17
20
|
|
18
21
|
return wrap_ctx, original_args
|
19
22
|
end
|
@@ -9,7 +9,7 @@ module Trailblazer
|
|
9
9
|
# |-- Call (call actual task) id: "task_wrap.call_task"
|
10
10
|
# |-- Trace.capture_return [optional]
|
11
11
|
# |-- Wrap::End
|
12
|
-
module TaskWrap
|
12
|
+
module TaskWrap
|
13
13
|
# inserts must be
|
14
14
|
# An {Extension} can be used for {:wrap_runtime}. It expects a collection of
|
15
15
|
# "friendly interface" arrays.
|
@@ -19,6 +19,9 @@ module Trailblazer
|
|
19
19
|
# If you want a {wrap_static} extension, wrap it using `Extension.WrapStatic.new`.
|
20
20
|
def self.Extension(*inserts, merge: nil)
|
21
21
|
if merge
|
22
|
+
Deprecate.warn caller_locations[0], "The :merge option for TaskWrap.Extension is deprecated and will be removed in 0.16.
|
23
|
+
Please refer to https://trailblazer.to/2.1/docs/activity.html#activity-taskwrap-static and have a great day."
|
24
|
+
|
22
25
|
return Extension::WrapStatic.new(extension: Extension.new(*merge))
|
23
26
|
# TODO: remove me once we drop the pre-friendly interface.
|
24
27
|
end
|
@@ -56,8 +59,8 @@ module Trailblazer
|
|
56
59
|
def deprecated_extension_for(extension_rows)
|
57
60
|
return extension_rows unless extension_rows.find { |ext| ext.is_a?(Array) }
|
58
61
|
|
59
|
-
warn
|
60
|
-
Please update to the new TaskWrap.Extension() API
|
62
|
+
Deprecate.warn caller_locations[3], "You are using the old API for taskWrap extensions.
|
63
|
+
Please update to the new TaskWrap.Extension() API."
|
61
64
|
|
62
65
|
extension_rows.collect do |ary|
|
63
66
|
{
|
@@ -67,6 +70,13 @@ Please update to the new TaskWrap.Extension() API: # FIXME !!!!!"
|
|
67
70
|
end
|
68
71
|
end
|
69
72
|
|
73
|
+
# Create extensions from the friendly interface that can alter the wrap_static
|
74
|
+
# of a step in an activity. The returned extensionn can be passed directly via {:extensions}
|
75
|
+
# to the compiler, or when using the `#step` DSL.
|
76
|
+
def self.WrapStatic(*inserts)
|
77
|
+
WrapStatic.new(extension: TaskWrap.Extension(*inserts))
|
78
|
+
end
|
79
|
+
|
70
80
|
# Extension are used at compile-time with {wrap_static}, mostly with the {dsl} gem.
|
71
81
|
# {WrapStatic} extensions are called for setup through {Intermediate.config} at compile-time.
|
72
82
|
# Each extension alters the activity's wrap_static taskWrap.
|
@@ -76,14 +86,20 @@ Please update to the new TaskWrap.Extension() API: # FIXME !!!!!"
|
|
76
86
|
end
|
77
87
|
|
78
88
|
def call(config:, task:, **)
|
79
|
-
#
|
80
|
-
|
81
|
-
before_pipe = Config.get(config, :wrap_static, task.circuit_task)
|
89
|
+
# DISCUSS: does it make sense to test the immutable behavior here? What would be the worst outcome?
|
90
|
+
task = task.circuit_task # Receives {Schema::Implementation::Task}. DISCUSS: why?
|
82
91
|
|
83
|
-
|
92
|
+
# Add the extension's task(s) to the activity's {:wrap_static} taskWrap
|
93
|
+
# which is stored in the {:config} field.
|
94
|
+
wrap_static = config[:wrap_static] # the activity's {wrap_static}.
|
95
|
+
task_wrap = wrap_static[task] # the "original" taskWrap for {task}.
|
96
|
+
|
97
|
+
# Overwrite the original task_wrap:
|
98
|
+
wrap_static = wrap_static.merge(task => @extension.(task_wrap))
|
99
|
+
|
100
|
+
config.merge(wrap_static: wrap_static) # Return new config hash. This needs to be immutable code!
|
84
101
|
end
|
85
102
|
end # WrapStatic
|
86
|
-
|
87
103
|
end # Extension
|
88
104
|
end # TaskWrap
|
89
105
|
end
|
@@ -29,7 +29,7 @@ class Trailblazer::Activity
|
|
29
29
|
insert_before: :Prepend,
|
30
30
|
insert_after: :Append,
|
31
31
|
append: :Append,
|
32
|
-
prepend: :Prepend
|
32
|
+
prepend: :Prepend
|
33
33
|
}.fetch(name)
|
34
34
|
|
35
35
|
warn "[Trailblazer] Using `Trailblazer::Activity::TaskWrap::Pipeline.method(:#{name})` is deprecated.
|
@@ -10,7 +10,7 @@ class Trailblazer::Activity
|
|
10
10
|
# @api private
|
11
11
|
# @interface Runner
|
12
12
|
def self.call(task, args, **circuit_options)
|
13
|
-
wrap_ctx = {
|
13
|
+
wrap_ctx = {task: task}
|
14
14
|
|
15
15
|
# this pipeline is "wrapped around" the actual `task`.
|
16
16
|
task_wrap_pipeline = merge_static_with_runtime(task, **circuit_options) || raise
|
@@ -28,14 +28,13 @@ class Trailblazer::Activity
|
|
28
28
|
return wrap_ctx[:return_signal], wrap_ctx[:return_args]
|
29
29
|
end
|
30
30
|
|
31
|
-
|
32
31
|
# Compute the task's wrap by applying alterations both static and from runtime.
|
33
32
|
#
|
34
33
|
# NOTE: this is for performance reasons: we could have only one hash containing everything but that'd mean
|
35
34
|
# unnecessary computations at `call`-time since steps might not even be executed.
|
36
35
|
# TODO: make this faster.
|
37
|
-
private_class_method def self.merge_static_with_runtime(task, wrap_runtime:, **circuit_options)
|
38
|
-
static_wrap = TaskWrap.wrap_static_for(task,
|
36
|
+
private_class_method def self.merge_static_with_runtime(task, wrap_runtime:, activity:, **circuit_options)
|
37
|
+
static_wrap = TaskWrap.wrap_static_for(task, activity) # find static wrap for this specific task [, or default wrap activity].
|
39
38
|
|
40
39
|
# Apply runtime alterations.
|
41
40
|
# Grab the additional wirings for the particular `task` from `wrap_runtime`.
|
@@ -44,9 +43,11 @@ class Trailblazer::Activity
|
|
44
43
|
end # Runner
|
45
44
|
|
46
45
|
# Retrieve the static wrap config from {activity}.
|
47
|
-
|
48
|
-
|
49
|
-
|
46
|
+
# @private
|
47
|
+
def self.wrap_static_for(task, activity)
|
48
|
+
activity.to_h
|
49
|
+
.fetch(:config)
|
50
|
+
.fetch(:wrap_static)[task] # the {wrap_static} for {task}.
|
50
51
|
end
|
51
52
|
end
|
52
53
|
end
|
@@ -21,7 +21,7 @@ module Trailblazer
|
|
21
21
|
circuit_options = circuit_options.merge(
|
22
22
|
runner: TaskWrap::Runner,
|
23
23
|
wrap_runtime: wrap_runtime,
|
24
|
-
activity: container_activity
|
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 =
|
@@ -43,10 +43,13 @@ module Trailblazer
|
|
43
43
|
#
|
44
44
|
# DISCUSS: we could cache that on Strategy/Operation level.
|
45
45
|
# merging the **config hash is 1/4 slower than before.
|
46
|
-
def container_activity_for(activity, wrap_static: initial_wrap_static, **config)
|
46
|
+
def container_activity_for(activity, wrap_static: initial_wrap_static, id: nil, **config)
|
47
47
|
{
|
48
|
-
|
49
|
-
|
48
|
+
config: {
|
49
|
+
wrap_static: {activity => wrap_static},
|
50
|
+
**config
|
51
|
+
},
|
52
|
+
nodes: Schema.Nodes([[id, activity]])
|
50
53
|
}
|
51
54
|
end
|
52
55
|
|
@@ -54,4 +57,3 @@ module Trailblazer
|
|
54
57
|
end # TaskWrap
|
55
58
|
end
|
56
59
|
end
|
57
|
-
|