trailblazer-activity 0.2.1 → 0.3.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 +4 -0
- data/NOTES_ +36 -0
- data/README.md +7 -7
- data/Rakefile +1 -1
- data/lib/trailblazer/activity.rb +137 -96
- data/lib/trailblazer/activity/heritage.rb +30 -0
- data/lib/trailblazer/activity/introspection.rb +105 -0
- data/lib/trailblazer/activity/magnetic.rb +47 -0
- data/lib/trailblazer/activity/magnetic/builder.rb +161 -0
- data/lib/trailblazer/activity/magnetic/builder/block.rb +37 -0
- data/lib/trailblazer/activity/magnetic/builder/fast_track.rb +141 -0
- data/lib/trailblazer/activity/magnetic/builder/path.rb +98 -0
- data/lib/trailblazer/activity/magnetic/builder/railway.rb +123 -0
- data/lib/trailblazer/activity/magnetic/dsl.rb +90 -0
- data/lib/trailblazer/activity/magnetic/dsl/alterations.rb +44 -0
- data/lib/trailblazer/activity/magnetic/dsl/plus_poles.rb +59 -0
- data/lib/trailblazer/activity/magnetic/finalizer.rb +55 -0
- data/lib/trailblazer/activity/magnetic/generate.rb +62 -0
- data/lib/trailblazer/activity/present.rb +12 -19
- data/lib/trailblazer/activity/process.rb +16 -0
- data/lib/trailblazer/activity/schema/dependencies.rb +41 -0
- data/lib/trailblazer/activity/schema/sequence.rb +46 -0
- data/lib/trailblazer/activity/structures.rb +41 -0
- data/lib/trailblazer/activity/subprocess.rb +9 -1
- data/lib/trailblazer/activity/trace.rb +25 -16
- data/lib/trailblazer/activity/version.rb +1 -1
- data/lib/trailblazer/activity/wrap.rb +4 -13
- data/lib/trailblazer/circuit.rb +4 -35
- data/lib/trailblazer/wrap/call_task.rb +2 -2
- data/lib/trailblazer/wrap/runner.rb +7 -1
- data/lib/trailblazer/wrap/trace.rb +6 -5
- metadata +21 -10
- data/lib/trailblazer/activity/graph.rb +0 -157
- data/lib/trailblazer/circuit/testing.rb +0 -58
- data/lib/trailblazer/container_chain.rb +0 -45
- data/lib/trailblazer/context.rb +0 -68
- data/lib/trailblazer/option.rb +0 -78
- data/lib/trailblazer/wrap/inject.rb +0 -32
- data/lib/trailblazer/wrap/variable_mapping.rb +0 -92
@@ -1,157 +0,0 @@
|
|
1
|
-
module Trailblazer
|
2
|
-
# Note that Graph is a superset of a real directed graph. For instance, it might contain detached nodes.
|
3
|
-
# == Design
|
4
|
-
# * This class is designed to maintain a graph while building up a circuit step-wise.
|
5
|
-
# * It can be imperformant as this all happens at compile-time.
|
6
|
-
module Activity::Graph
|
7
|
-
# Task => { name: "Nested{Task}", type: :subprocess, boundary_events: { Circuit::Left => {} } }
|
8
|
-
|
9
|
-
# Edge keeps references to its peer nodes via the `:source` and `:target` options.
|
10
|
-
class Edge
|
11
|
-
def initialize(data)
|
12
|
-
@data = data.freeze
|
13
|
-
end
|
14
|
-
|
15
|
-
def [](key)
|
16
|
-
@data[key]
|
17
|
-
end
|
18
|
-
|
19
|
-
def to_h
|
20
|
-
@data.to_h
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
# Node does only save meta data, and has *no* references to edges.
|
25
|
-
class Node < Edge
|
26
|
-
end
|
27
|
-
|
28
|
-
class Start < Node
|
29
|
-
def initialize(data)
|
30
|
-
yield self, data if block_given?
|
31
|
-
super
|
32
|
-
end
|
33
|
-
|
34
|
-
# Builds a node from the provided `:target` arguments and attaches it via `:edge` to `:source`.
|
35
|
-
# @param target: Array wrapped, options
|
36
|
-
def attach!(target:raise, edge:raise, source:self)
|
37
|
-
target = add!(target)
|
38
|
-
|
39
|
-
connect!(target: target, edge: edge, source: source)
|
40
|
-
end
|
41
|
-
|
42
|
-
def connect!(target:raise, edge:raise, source:self)
|
43
|
-
target = target.kind_of?(Node) ? target : (find_all(target)[0] || raise( "#{target} not found")) # FIXME: only needed for recompile_activity.
|
44
|
-
source = source.kind_of?(Node) ? source : (find_all(source)[0] || raise( "#{source} not found"))
|
45
|
-
|
46
|
-
connect_for!(source, edge, target)
|
47
|
-
end
|
48
|
-
|
49
|
-
def insert_before!(old_node, node:raise, outgoing:nil, incoming:raise)
|
50
|
-
old_node = find_all(old_node)[0] || raise( "#{old_node} not found") unless old_node.kind_of?(Node) # FIXME: do we really need this?
|
51
|
-
new_node = add!(node)
|
52
|
-
|
53
|
-
incoming_edges = predecessors(old_node)
|
54
|
-
rewired_edges = incoming_edges.find_all { |(node, edge)| incoming.(edge) }
|
55
|
-
|
56
|
-
# rewire old_task's predecessors to new_task.
|
57
|
-
rewired_edges.each { |left_node, edge| reconnect!(left_node, edge, new_node) }
|
58
|
-
|
59
|
-
# connect new_task --> old_task.
|
60
|
-
if outgoing
|
61
|
-
node, edge = connect_for!(new_node, outgoing, old_node)
|
62
|
-
end
|
63
|
-
|
64
|
-
return new_node
|
65
|
-
end
|
66
|
-
|
67
|
-
def find_all(id=nil, &block)
|
68
|
-
nodes = self[:graph].keys + self[:graph].values.collect(&:values).flatten
|
69
|
-
nodes = nodes.uniq
|
70
|
-
|
71
|
-
nodes.find_all(& block || ->(node) { node[:id] == id })
|
72
|
-
end
|
73
|
-
|
74
|
-
def predecessors(target_node)
|
75
|
-
self[:graph].each_with_object([]) do |(node, connections), ary|
|
76
|
-
connections.each { |edge, target| target == target_node && ary << [node, edge] }
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
def successors(node)
|
81
|
-
( self[:graph][node] || [] ).collect { |edge, target| [target, edge] }
|
82
|
-
end
|
83
|
-
|
84
|
-
def to_h(include_leafs:true)
|
85
|
-
hash = ::Hash[
|
86
|
-
self[:graph].collect do |node, connections|
|
87
|
-
connections = connections.collect { |edge, node| [ edge[:_wrapped], node[:_wrapped] ] }
|
88
|
-
|
89
|
-
[ node[:_wrapped], ::Hash[connections] ]
|
90
|
-
end
|
91
|
-
]
|
92
|
-
|
93
|
-
if include_leafs == false
|
94
|
-
hash = hash.select { |node, connections| connections.any? }
|
95
|
-
end
|
96
|
-
|
97
|
-
hash
|
98
|
-
end
|
99
|
-
|
100
|
-
# @private
|
101
|
-
def Node(wrapped, id:raise("No ID was provided for #{wrapped}"), **options)
|
102
|
-
Node.new( options.merge( id: id, _wrapped: wrapped ) )
|
103
|
-
end
|
104
|
-
|
105
|
-
private
|
106
|
-
|
107
|
-
# Single entry point for adding nodes and edges to the graph.
|
108
|
-
# @private
|
109
|
-
# @return target Node
|
110
|
-
# @return edge Edge the edge created connecting source and target.
|
111
|
-
def connect_for!(source, edge_args, target)
|
112
|
-
edge = Edge(source, edge_args, target)
|
113
|
-
|
114
|
-
self[:graph][source][edge] = target
|
115
|
-
|
116
|
-
return target, edge
|
117
|
-
end
|
118
|
-
|
119
|
-
# Removes edge.
|
120
|
-
# @private
|
121
|
-
def unconnect!(node, edge)
|
122
|
-
self[:graph][node].delete(edge)
|
123
|
-
end
|
124
|
-
|
125
|
-
# @private
|
126
|
-
# Create a Node and add it to the graph, without connecting it.
|
127
|
-
def add!(node_args)
|
128
|
-
new_node = Node(*node_args)
|
129
|
-
|
130
|
-
raise IllegalNodeError.new("The ID `#{new_node[:id]}` has been added before.") if find_all( new_node[:id] ).any?
|
131
|
-
|
132
|
-
self[:graph][new_node] = {}
|
133
|
-
new_node
|
134
|
-
end
|
135
|
-
|
136
|
-
# @private
|
137
|
-
def reconnect!(left_node, edge, new_node)
|
138
|
-
unconnect!(left_node, edge) # dump the old edge.
|
139
|
-
connect_for!(left_node, [ edge[:_wrapped], edge.to_h ], new_node)
|
140
|
-
end
|
141
|
-
|
142
|
-
# @private
|
143
|
-
def Edge(source, (wrapped, options), target) # FIXME: test required id. test source and target
|
144
|
-
id = "#{source[:id]}-#{wrapped}-#{target[:id]}"
|
145
|
-
edge = Edge.new(options.merge( _wrapped: wrapped, id: id, source: source, target: target ))
|
146
|
-
end
|
147
|
-
end
|
148
|
-
|
149
|
-
def self.Start(wrapped, graph:{}, **data, &block)
|
150
|
-
block ||= ->(node, data) { data[:graph][node] = {} }
|
151
|
-
Start.new( { _wrapped: wrapped, graph: graph }.merge(data), &block )
|
152
|
-
end
|
153
|
-
|
154
|
-
class IllegalNodeError < RuntimeError
|
155
|
-
end
|
156
|
-
end # Graph
|
157
|
-
end
|
@@ -1,58 +0,0 @@
|
|
1
|
-
# TODO: remove or move.
|
2
|
-
|
3
|
-
module MiniTest::Assertions
|
4
|
-
def assert_activity_inspect(text, subject)
|
5
|
-
Trailblazer::Circuit::ActivityInspect(subject).must_equal text
|
6
|
-
end
|
7
|
-
|
8
|
-
def assert_event_inspect(text, subject)
|
9
|
-
Trailblazer::Circuit::EndInspect(subject).must_equal(text)
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
|
14
|
-
Trailblazer::Activity.infect_an_assertion :assert_activity_inspect, :must_inspect
|
15
|
-
Trailblazer::Circuit::End.infect_an_assertion :assert_event_inspect, :must_inspect_end_fixme
|
16
|
-
|
17
|
-
class Trailblazer::Circuit
|
18
|
-
def self.EndInspect(event)
|
19
|
-
event.instance_eval { "#<#{self.class.to_s.split("::").last}: #{@name} #{@options}>" }
|
20
|
-
end
|
21
|
-
|
22
|
-
def self.ActivityInspect(activity, strip: ["AlterTest::"])
|
23
|
-
strip += ["Trailblazer::Circuit::"]
|
24
|
-
stripped = ->(target) { strip_for(target, strip) }
|
25
|
-
|
26
|
-
map, _ = activity.circuit.to_fields
|
27
|
-
|
28
|
-
content = map.collect do |task, connections|
|
29
|
-
bla =
|
30
|
-
connections.collect do |direction, target|
|
31
|
-
target_str = target.kind_of?(End) ? EndInspect(target) : stripped.(target)
|
32
|
-
"#{stripped.(direction)}=>#{target_str}"
|
33
|
-
end.join(", ")
|
34
|
-
task_str = task.kind_of?(End) ? EndInspect(task) : stripped.(task)
|
35
|
-
"#{task_str}=>{#{bla}}"
|
36
|
-
end.join(", ")
|
37
|
-
"{#{content}}"
|
38
|
-
end
|
39
|
-
|
40
|
-
def self.strip_for(target, strings)
|
41
|
-
strings.each { |stripped| target = target.to_s.gsub(stripped, "") }
|
42
|
-
target
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
module Trailblazer::Activity::Inspect
|
47
|
-
# The "matcher":
|
48
|
-
# Finds out appropriate serializer and calls it.
|
49
|
-
def self.call(inspected)
|
50
|
-
Instance(inspected)
|
51
|
-
end
|
52
|
-
|
53
|
-
# Serializes an object instance with hex IDs.
|
54
|
-
# <Trailblazer::Circuit::Start: @name=:default, @options={}>
|
55
|
-
def self.Instance(object)
|
56
|
-
object.inspect.gsub(/0x\w+/, "")
|
57
|
-
end
|
58
|
-
end
|
@@ -1,45 +0,0 @@
|
|
1
|
-
# @private
|
2
|
-
class Trailblazer::Context::ContainerChain # used to be called Resolver.
|
3
|
-
# Keeps a list of containers. When looking up a key/value, containers are traversed in
|
4
|
-
# the order they were added until key is found.
|
5
|
-
#
|
6
|
-
# Required Container interface: `#key?`, `#[]`.
|
7
|
-
#
|
8
|
-
# @note ContainerChain is an immutable data structure, it does not support writing.
|
9
|
-
# @param containers Array of <Container> objects (splatted)
|
10
|
-
def initialize(containers, to_hash: nil)
|
11
|
-
@containers = containers
|
12
|
-
@to_hash = to_hash
|
13
|
-
end
|
14
|
-
|
15
|
-
# @param name Symbol or String to lookup a value stored in one of the containers.
|
16
|
-
def [](name)
|
17
|
-
self.class.find(@containers, name)
|
18
|
-
end
|
19
|
-
|
20
|
-
# @private
|
21
|
-
def key?(name)
|
22
|
-
@containers.find { |container| container.key?(name) }
|
23
|
-
end
|
24
|
-
|
25
|
-
def self.find(containers, name)
|
26
|
-
containers.find { |container| container.key?(name) && (return container[name]) }
|
27
|
-
end
|
28
|
-
|
29
|
-
def keys
|
30
|
-
@containers.collect(&:keys).flatten
|
31
|
-
end
|
32
|
-
|
33
|
-
# @private
|
34
|
-
def to_hash
|
35
|
-
return @to_hash.(@containers) if @to_hash # FIXME: introduce pattern matching so we can have different "transformers" for each container type.
|
36
|
-
@containers.each_with_object({}) { |container, hash| hash.merge!(container.to_hash) }
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
# alternative implementation:
|
41
|
-
# containers.reverse.each do |container| @mutable_options.merge!(container) end
|
42
|
-
#
|
43
|
-
# benchmark, merging in #initialize vs. this resolver.
|
44
|
-
# merge 39.678k (± 9.1%) i/s - 198.700k in 5.056653s
|
45
|
-
# resolver 68.928k (± 6.4%) i/s - 342.836k in 5.001610s
|
data/lib/trailblazer/context.rb
DELETED
@@ -1,68 +0,0 @@
|
|
1
|
-
# TODO: mark/make all but mutable_options as frozen.
|
2
|
-
# The idea of Skill is to have a generic, ordered read/write interface that
|
3
|
-
# collects mutable runtime-computed data while providing access to compile-time
|
4
|
-
# information.
|
5
|
-
# The runtime-data takes precedence over the class data.
|
6
|
-
module Trailblazer
|
7
|
-
# Holds local options (aka `mutable_options`) and "original" options from the "outer"
|
8
|
-
# activity (aka wrapped_options).
|
9
|
-
|
10
|
-
# only public creator: Build
|
11
|
-
class Context # :data object:
|
12
|
-
def initialize(wrapped_options, mutable_options)
|
13
|
-
@wrapped_options, @mutable_options = wrapped_options, mutable_options
|
14
|
-
end
|
15
|
-
|
16
|
-
def [](name)
|
17
|
-
ContainerChain.find( [@mutable_options, @wrapped_options], name )
|
18
|
-
end
|
19
|
-
|
20
|
-
def key?(name)
|
21
|
-
@mutable_options.key?(name) || @wrapped_options.key?(name)
|
22
|
-
end
|
23
|
-
|
24
|
-
def []=(name, value)
|
25
|
-
@mutable_options[name] = value
|
26
|
-
end
|
27
|
-
|
28
|
-
def merge(hash)
|
29
|
-
original, mutable_options = decompose
|
30
|
-
|
31
|
-
ctx = Trailblazer::Context( original, mutable_options.merge(hash) )
|
32
|
-
end
|
33
|
-
|
34
|
-
# Return the Context's two components. Used when computing the new output for
|
35
|
-
# the next activity.
|
36
|
-
def decompose
|
37
|
-
[ @wrapped_options, @mutable_options ]
|
38
|
-
end
|
39
|
-
|
40
|
-
def key?(name)
|
41
|
-
ContainerChain.find( [@mutable_options, @wrapped_options], name )
|
42
|
-
end
|
43
|
-
|
44
|
-
|
45
|
-
def keys
|
46
|
-
@mutable_options.keys + @wrapped_options.keys # FIXME.
|
47
|
-
end
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
# TODO: maybe we shouldn't allow to_hash from context?
|
52
|
-
# TODO: massive performance bottleneck. also, we could already "know" here what keys the
|
53
|
-
# transformation wants.
|
54
|
-
# FIXME: ToKeywordArguments()
|
55
|
-
def to_hash
|
56
|
-
{}.tap do |hash|
|
57
|
-
# the "key" here is to call to_hash on all containers.
|
58
|
-
[ @wrapped_options.to_hash, @mutable_options.to_hash ].each do |options|
|
59
|
-
options.each { |k, v| hash[k.to_sym] = v }
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
def self.Context(wrapped_options, mutable_options={})
|
66
|
-
Context.new(wrapped_options, mutable_options)
|
67
|
-
end
|
68
|
-
end # Trailblazer
|
data/lib/trailblazer/option.rb
DELETED
@@ -1,78 +0,0 @@
|
|
1
|
-
module Trailblazer
|
2
|
-
# @note This might go to trailblazer-args along with `Context` at some point.
|
3
|
-
def self.Option(proc)
|
4
|
-
Option.build(Option, proc)
|
5
|
-
end
|
6
|
-
|
7
|
-
class Option
|
8
|
-
# Generic builder for a callable "option".
|
9
|
-
# @param call_implementation [Class, Module] implements the process of calling the proc
|
10
|
-
# while passing arguments/options to it in a specific style (e.g. kw args, step interface).
|
11
|
-
# @return [Proc] when called, this proc will evaluate its option (at run-time).
|
12
|
-
def self.build(call_implementation, proc)
|
13
|
-
if proc.is_a? Symbol
|
14
|
-
->(*args) { call_implementation.evaluate_method(proc, *args) }
|
15
|
-
else
|
16
|
-
->(*args) { call_implementation.evaluate_callable(proc, *args) }
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
# A call implementation invoking `proc.(*args)` and plainly forwarding all arguments.
|
21
|
-
# Override this for your own step strategy (see KW#call!).
|
22
|
-
# @private
|
23
|
-
def self.call!(proc, *args)
|
24
|
-
proc.(*args)
|
25
|
-
end
|
26
|
-
|
27
|
-
# Note that both #evaluate_callable and #evaluate_method drop most of the args.
|
28
|
-
# If you need those, override this class.
|
29
|
-
# @private
|
30
|
-
def self.evaluate_callable(proc, *args, **flow_options)
|
31
|
-
call!(proc, *args)
|
32
|
-
end
|
33
|
-
|
34
|
-
# Make the context's instance method a "lambda" and reuse #call!.
|
35
|
-
# @private
|
36
|
-
def self.evaluate_method(proc, *args, exec_context:raise, **flow_options)
|
37
|
-
call!(exec_context.method(proc), *args)
|
38
|
-
end
|
39
|
-
|
40
|
-
# Returns a {Proc} that, when called, invokes the `proc` argument with keyword arguments.
|
41
|
-
# This is known as "step (call) interface".
|
42
|
-
#
|
43
|
-
# This is commonly used by `Operation::step` to wrap the argument and make it
|
44
|
-
# callable in the circuit.
|
45
|
-
#
|
46
|
-
# my_proc = ->(options, **kws) { options["i got called"] = true }
|
47
|
-
# task = Trailblazer::Option::KW(my_proc)
|
48
|
-
# task.(options = {})
|
49
|
-
# options["i got called"] #=> true
|
50
|
-
#
|
51
|
-
# Alternatively, you can pass a symbol and an `:exec_context`.
|
52
|
-
#
|
53
|
-
# my_proc = :some_method
|
54
|
-
# task = Trailblazer::Option::KW(my_proc)
|
55
|
-
#
|
56
|
-
# class A
|
57
|
-
# def some_method(options, **kws)
|
58
|
-
# options["i got called"] = true
|
59
|
-
# end
|
60
|
-
# end
|
61
|
-
#
|
62
|
-
# task.(options = {}, exec_context: A.new)
|
63
|
-
# options["i got called"] #=> true
|
64
|
-
def self.KW(proc)
|
65
|
-
Option.build(KW, proc)
|
66
|
-
end
|
67
|
-
|
68
|
-
# TODO: It would be cool if call! was typed and had `options SymbolizedHash` or something.
|
69
|
-
class KW < Option
|
70
|
-
# A different call implementation that calls `proc` with a "step interface".
|
71
|
-
# your_code.(options, **options)
|
72
|
-
# @private
|
73
|
-
def self.call!(proc, options, *)
|
74
|
-
proc.(options, **options.to_hash) # Step interface: (options, **)
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
@@ -1,32 +0,0 @@
|
|
1
|
-
class Trailblazer::Activity
|
2
|
-
module Wrap
|
3
|
-
module Inject
|
4
|
-
# Returns an Alteration wirings that, when applied, inserts the {ReverseMergeDefaults} task
|
5
|
-
# before the {Wrap::Call} task. This is meant for macros and steps that accept a dependency
|
6
|
-
# injection but need a default parameter to be set if not injected.
|
7
|
-
# @returns Alteration
|
8
|
-
def self.Defaults(default_dependencies)
|
9
|
-
[
|
10
|
-
[ :insert_before!, "task_wrap.call_task", node: [ ReverseMergeDefaults.new( default_dependencies ), id: "ReverseMergeDefaults#{default_dependencies}" ], incoming: Proc.new{ true }, outgoing: [ Trailblazer::Circuit::Right, {} ] ]
|
11
|
-
]
|
12
|
-
end
|
13
|
-
|
14
|
-
# @api private
|
15
|
-
# @returns Task
|
16
|
-
# @param Hash list of key/value that should be set if not already assigned/set before (or injected from the outside).
|
17
|
-
class ReverseMergeDefaults
|
18
|
-
def initialize(defaults)
|
19
|
-
@defaults = defaults
|
20
|
-
end
|
21
|
-
|
22
|
-
def call((wrap_ctx, original_args), **circuit_options)
|
23
|
-
ctx = original_args[0][0]
|
24
|
-
|
25
|
-
@defaults.each { |k, v| ctx[k] ||= v }
|
26
|
-
|
27
|
-
[ Trailblazer::Circuit::Right, [wrap_ctx, original_args] ]
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end # Inject
|
31
|
-
end
|
32
|
-
end
|
@@ -1,92 +0,0 @@
|
|
1
|
-
class Trailblazer::Activity
|
2
|
-
module Wrap
|
3
|
-
# TaskWrap step to compute the incoming {Context} for the wrapped task.
|
4
|
-
# This allows renaming, filtering, hiding, of the options passed into the wrapped task.
|
5
|
-
#
|
6
|
-
# Both Input and Output are typically to be added before and after task_wrap.call_task.
|
7
|
-
#
|
8
|
-
# @note Assumption: we always have :input _and_ :output, where :input produces a Context and :output decomposes it.
|
9
|
-
class Input
|
10
|
-
def initialize(filter)
|
11
|
-
@filter = Trailblazer::Option(filter)
|
12
|
-
end
|
13
|
-
|
14
|
-
# `original_args` are the actual args passed to the wrapped task: [ [options, ..], circuit_options ]
|
15
|
-
#
|
16
|
-
def call((wrap_ctx, original_args), **circuit_options)
|
17
|
-
# let user compute new ctx for the wrapped task.
|
18
|
-
input_ctx = apply_filter(*original_args)
|
19
|
-
|
20
|
-
# TODO: make this unnecessary.
|
21
|
-
# wrap user's hash in Context if it's not one, already (in case user used options.merge).
|
22
|
-
# DISCUSS: should we restrict user to .merge and options.Context?
|
23
|
-
input_ctx = Trailblazer.Context({}, input_ctx) unless input_ctx.instance_of?(Trailblazer::Context)
|
24
|
-
|
25
|
-
wrap_ctx = wrap_ctx.merge( vm_original_args: original_args )
|
26
|
-
|
27
|
-
# decompose the original_args since we want to modify them.
|
28
|
-
(original_ctx, original_flow_options), original_circuit_options = original_args
|
29
|
-
|
30
|
-
# instead of the original Context, pass on the filtered `input_ctx` in the wrap.
|
31
|
-
return Trailblazer::Circuit::Right, [ wrap_ctx, [[input_ctx, original_flow_options], original_circuit_options] ]
|
32
|
-
end
|
33
|
-
|
34
|
-
private
|
35
|
-
|
36
|
-
def apply_filter((original_ctx, original_flow_options), original_circuit_options)
|
37
|
-
@filter.( original_ctx, **original_circuit_options )
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
# TaskWrap step to compute the outgoing {Context} from the wrapped task.
|
42
|
-
# This allows renaming, filtering, hiding, of the options returned from the wrapped task.
|
43
|
-
class Output
|
44
|
-
def initialize(filter, strategy=CopyMutableToOriginal)
|
45
|
-
@filter = Trailblazer::Option(filter)
|
46
|
-
@strategy = strategy
|
47
|
-
end
|
48
|
-
|
49
|
-
# Runs the user filter and replaces the ctx in `wrap_ctx[:result_args]` with the filtered one.
|
50
|
-
def call((wrap_ctx, original_args), **circuit_options)
|
51
|
-
(original_ctx, original_flow_options), original_circuit_options = original_args
|
52
|
-
|
53
|
-
returned_ctx, _ = wrap_ctx[:result_args] # this is the context returned from `call`ing the task.
|
54
|
-
|
55
|
-
# returned_ctx is the Context object from the nested operation. In <=2.1, this might be a completely different one
|
56
|
-
# than "ours" we created in Input. We now need to compile a list of all added values. This is time-intensive and should
|
57
|
-
# be optimized by removing as many Context creations as possible (e.g. the one adding self[] stuff in Operation.__call__).
|
58
|
-
_, mutable_data = returned_ctx.decompose # FIXME: this is a weak assumption. What if the task returns a deeply nested Context?
|
59
|
-
|
60
|
-
# let user compute the output.
|
61
|
-
output = apply_filter(mutable_data, original_flow_options, original_circuit_options)
|
62
|
-
|
63
|
-
original_ctx = wrap_ctx[:vm_original_args][0][0]
|
64
|
-
|
65
|
-
new_ctx = @strategy.( original_ctx, output ) # here, we compute the "new" options {Context}.
|
66
|
-
|
67
|
-
wrap_ctx = wrap_ctx.merge( result_args: [new_ctx, original_flow_options] )
|
68
|
-
|
69
|
-
# and then pass on the "new" context.
|
70
|
-
return Trailblazer::Circuit::Right, [ wrap_ctx, original_args ]
|
71
|
-
end
|
72
|
-
|
73
|
-
private
|
74
|
-
|
75
|
-
# @note API not stable
|
76
|
-
def apply_filter(mutable_data, original_flow_options, original_circuit_options)
|
77
|
-
@filter.(mutable_data, **original_circuit_options)
|
78
|
-
end
|
79
|
-
|
80
|
-
# "merge" Strategy
|
81
|
-
class CopyMutableToOriginal
|
82
|
-
# @param original Context
|
83
|
-
# @param options Context The object returned from a (nested) {Activity}.
|
84
|
-
def self.call(original, mutable)
|
85
|
-
mutable.each { |k,v| original[k] = v }
|
86
|
-
|
87
|
-
original
|
88
|
-
end
|
89
|
-
end
|
90
|
-
end
|
91
|
-
end # Wrap
|
92
|
-
end
|