ntl-orchestra 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/Gemfile +11 -0
- data/LICENSE.txt +22 -0
- data/README.md +539 -0
- data/Rakefile +21 -0
- data/bin/rake +16 -0
- data/lib/orchestra/conductor.rb +119 -0
- data/lib/orchestra/configuration.rb +12 -0
- data/lib/orchestra/dsl/nodes.rb +72 -0
- data/lib/orchestra/dsl/object_adapter.rb +134 -0
- data/lib/orchestra/dsl/operations.rb +108 -0
- data/lib/orchestra/errors.rb +44 -0
- data/lib/orchestra/node/output.rb +61 -0
- data/lib/orchestra/node.rb +130 -0
- data/lib/orchestra/operation.rb +49 -0
- data/lib/orchestra/performance.rb +137 -0
- data/lib/orchestra/recording.rb +83 -0
- data/lib/orchestra/run_list.rb +171 -0
- data/lib/orchestra/thread_pool.rb +163 -0
- data/lib/orchestra/util.rb +98 -0
- data/lib/orchestra/version.rb +3 -0
- data/lib/orchestra.rb +35 -0
- data/orchestra.gemspec +26 -0
- data/test/examples/fizz_buzz.rb +32 -0
- data/test/examples/invitation_service.rb +118 -0
- data/test/integration/multithreading_test.rb +38 -0
- data/test/integration/recording_telemetry_test.rb +86 -0
- data/test/integration/replayable_operation_test.rb +53 -0
- data/test/lib/console.rb +103 -0
- data/test/lib/test_runner.rb +19 -0
- data/test/support/telemetry_recorder.rb +49 -0
- data/test/test_helper.rb +16 -0
- data/test/unit/conductor_test.rb +25 -0
- data/test/unit/dsl_test.rb +122 -0
- data/test/unit/node_test.rb +122 -0
- data/test/unit/object_adapter_test.rb +100 -0
- data/test/unit/operation_test.rb +224 -0
- data/test/unit/run_list_test.rb +131 -0
- data/test/unit/thread_pool_test.rb +105 -0
- data/test/unit/util_test.rb +20 -0
- data/tmp/.keep +0 -0
- metadata +159 -0
@@ -0,0 +1,119 @@
|
|
1
|
+
module Orchestra
|
2
|
+
class Conductor
|
3
|
+
attr :observers, :services, :thread_pool
|
4
|
+
|
5
|
+
def initialize services = {}
|
6
|
+
@services = services
|
7
|
+
@thread_pool = ThreadPool.new
|
8
|
+
@observers = Set.new
|
9
|
+
self.thread_count = Configuration.thread_count
|
10
|
+
end
|
11
|
+
|
12
|
+
def perform operation, input = {}
|
13
|
+
operation.perform self, input do |performance|
|
14
|
+
copy_observers performance
|
15
|
+
yield performance if block_given?
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def record *args
|
20
|
+
recording = Recording.new
|
21
|
+
add_observer recording
|
22
|
+
perform *args do |performance|
|
23
|
+
performance.add_observer recording
|
24
|
+
end
|
25
|
+
recording
|
26
|
+
ensure
|
27
|
+
delete_observer recording
|
28
|
+
end
|
29
|
+
|
30
|
+
def add_observer observer
|
31
|
+
observers << observer
|
32
|
+
end
|
33
|
+
|
34
|
+
def delete_observer observer
|
35
|
+
observers.delete observer
|
36
|
+
end
|
37
|
+
|
38
|
+
def copy_observers observable
|
39
|
+
add_observer = observable.method :add_observer
|
40
|
+
observers.each &add_observer
|
41
|
+
end
|
42
|
+
|
43
|
+
def build_registry observable
|
44
|
+
hsh = { :conductor => self }
|
45
|
+
services.each_with_object hsh do |(service_name, _), hsh|
|
46
|
+
service = resolve_service observable, service_name
|
47
|
+
hsh[service_name] = service if service
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def resolve_service observable, service_name
|
52
|
+
return nil unless services.has_key? service_name
|
53
|
+
service = Util.to_lazy_thunk services[service_name]
|
54
|
+
recording = ServiceRecorder.new observable, service_name
|
55
|
+
recording.wrap service.call self
|
56
|
+
end
|
57
|
+
|
58
|
+
def thread_count
|
59
|
+
@thread_pool.count
|
60
|
+
end
|
61
|
+
|
62
|
+
def thread_count= new_count
|
63
|
+
@thread_pool.count = new_count
|
64
|
+
end
|
65
|
+
|
66
|
+
class ServiceRecorder
|
67
|
+
attr :observable, :service_name
|
68
|
+
|
69
|
+
def initialize observable, service_name
|
70
|
+
@observable = observable
|
71
|
+
@service_name = service_name
|
72
|
+
@record = []
|
73
|
+
end
|
74
|
+
|
75
|
+
def << record
|
76
|
+
observable.changed
|
77
|
+
observable.notify_observers :service_accessed, service_name, record
|
78
|
+
@record << record
|
79
|
+
end
|
80
|
+
|
81
|
+
def each &block
|
82
|
+
@record.each &block
|
83
|
+
end
|
84
|
+
|
85
|
+
def wrap raw_service
|
86
|
+
Wrapper.new raw_service, self
|
87
|
+
end
|
88
|
+
|
89
|
+
class Wrapper < Delegator
|
90
|
+
attr_accessor :service
|
91
|
+
alias_method :__getobj__, :service
|
92
|
+
alias_method :__setobj__, :service=
|
93
|
+
|
94
|
+
def initialize service, recording
|
95
|
+
super service
|
96
|
+
@recording = recording
|
97
|
+
end
|
98
|
+
|
99
|
+
def kind_of? klass
|
100
|
+
super or service.kind_of? klass
|
101
|
+
end
|
102
|
+
|
103
|
+
def method_missing meth, *args
|
104
|
+
super.tap do |result|
|
105
|
+
@recording << {
|
106
|
+
:method => meth.to_s,
|
107
|
+
:input => args,
|
108
|
+
:output => result,
|
109
|
+
}
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def inspect
|
114
|
+
"#<#{self.class.name} service=#{service.inspect}>"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Orchestra
|
2
|
+
module DSL
|
3
|
+
module Nodes
|
4
|
+
class Builder
|
5
|
+
attr_accessor :collection, :perform_block
|
6
|
+
|
7
|
+
attr :defaults, :dependencies, :provisions
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@defaults = {}
|
11
|
+
@dependencies = []
|
12
|
+
@provisions = []
|
13
|
+
end
|
14
|
+
|
15
|
+
def build_node
|
16
|
+
Node::InlineNode.new(
|
17
|
+
:collection => collection,
|
18
|
+
:defaults => defaults,
|
19
|
+
:dependencies => dependencies,
|
20
|
+
:perform_block => perform_block,
|
21
|
+
:provides => provisions,
|
22
|
+
)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Context < BasicObject
|
27
|
+
def self.evaluate builder, &block
|
28
|
+
context = new builder
|
29
|
+
context.instance_eval &block
|
30
|
+
end
|
31
|
+
|
32
|
+
attr :collection, :perform
|
33
|
+
|
34
|
+
def initialize builder
|
35
|
+
@builder = builder
|
36
|
+
end
|
37
|
+
|
38
|
+
def depends_on *dependencies
|
39
|
+
defaults, dependencies = Util.extract_hash dependencies
|
40
|
+
@builder.dependencies.concat dependencies
|
41
|
+
defaults.each do |key, default|
|
42
|
+
@builder.dependencies << key
|
43
|
+
@builder.defaults[key] = Util.to_lazy_thunk default
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def modifies provision, args = {}
|
48
|
+
collection, _ = Util.extract_key_args args, :collection => false
|
49
|
+
if collection
|
50
|
+
iterates_over provision
|
51
|
+
else
|
52
|
+
depends_on provision
|
53
|
+
end
|
54
|
+
provides provision
|
55
|
+
end
|
56
|
+
|
57
|
+
def provides *provisions
|
58
|
+
@builder.provisions.concat provisions
|
59
|
+
end
|
60
|
+
|
61
|
+
def perform &block
|
62
|
+
@builder.perform_block = block
|
63
|
+
end
|
64
|
+
|
65
|
+
def iterates_over dependency
|
66
|
+
@builder.dependencies << dependency
|
67
|
+
@builder.collection = dependency
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
module Orchestra
|
2
|
+
module DSL
|
3
|
+
class ObjectAdapter
|
4
|
+
def self.build_node object, args = {}
|
5
|
+
method_name = args.delete :method do :perform end
|
6
|
+
collection = args.delete :iterates_over
|
7
|
+
adapter_type = determine_type object, method_name
|
8
|
+
adapter = adapter_type.new object, method_name, collection
|
9
|
+
NodeFactory.build adapter, args
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.determine_type object, method_name
|
13
|
+
if object.public_methods.include? method_name
|
14
|
+
SingletonAdapter
|
15
|
+
elsif object.kind_of? Class
|
16
|
+
ClassAdapter
|
17
|
+
else
|
18
|
+
SingletonAdapter
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
attr :collection, :object, :method_name
|
23
|
+
|
24
|
+
def initialize object, method_name, collection
|
25
|
+
@collection = collection
|
26
|
+
@method_name = method_name || :perform
|
27
|
+
@object = object
|
28
|
+
end
|
29
|
+
|
30
|
+
def build_context state
|
31
|
+
ExecutionContext.new self, state
|
32
|
+
end
|
33
|
+
|
34
|
+
def collection?
|
35
|
+
@collection ? true : false
|
36
|
+
end
|
37
|
+
|
38
|
+
def context_class
|
39
|
+
@context_class ||= Node.build_execution_context_class dependencies
|
40
|
+
end
|
41
|
+
|
42
|
+
def dependencies
|
43
|
+
[collection, *object_method.dependencies].compact
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class SingletonAdapter < ObjectAdapter
|
48
|
+
def validate!
|
49
|
+
unless object.methods.include? method_name
|
50
|
+
raise NotImplementedError,
|
51
|
+
"#{object} does not implement method `#{method_name}'"
|
52
|
+
end
|
53
|
+
if collection?
|
54
|
+
raise ArgumentError,
|
55
|
+
"#{object} is a singleton; cannot iterate over collection #{collection.inspect}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def perform state
|
60
|
+
deps = object_method.dependencies
|
61
|
+
input = state.select do |key, _| deps.include? key end
|
62
|
+
Invokr.invoke :method => method_name, :on => object, :with => input
|
63
|
+
end
|
64
|
+
|
65
|
+
def object_method
|
66
|
+
Invokr.query_method object.method method_name
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class ClassAdapter < ObjectAdapter
|
71
|
+
def validate!
|
72
|
+
return if object.instance_methods.include? method_name
|
73
|
+
raise NotImplementedError,
|
74
|
+
"#{object} does not implement instance method `#{method_name}'"
|
75
|
+
end
|
76
|
+
|
77
|
+
def perform state, maybe_item = nil
|
78
|
+
instance = Invokr.inject object, :using => state
|
79
|
+
args = [method_name]
|
80
|
+
args << maybe_item if collection?
|
81
|
+
instance.public_send *args
|
82
|
+
end
|
83
|
+
|
84
|
+
def object_method
|
85
|
+
Invokr.query_method object.instance_method :initialize
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
class NodeFactory
|
90
|
+
def self.build *args
|
91
|
+
instance = new *args
|
92
|
+
instance.build_node
|
93
|
+
end
|
94
|
+
|
95
|
+
attr :adapter, :compact, :provides, :thread_count
|
96
|
+
|
97
|
+
def initialize adapter, args = {}
|
98
|
+
@adapter = adapter
|
99
|
+
@provides, @compact, @thread_count = Util.extract_key_args args,
|
100
|
+
:provides => nil, :compact => false, :thread_count => nil
|
101
|
+
end
|
102
|
+
|
103
|
+
def build_node
|
104
|
+
adapter.validate!
|
105
|
+
Node::DelegateNode.new adapter, build_node_args
|
106
|
+
end
|
107
|
+
|
108
|
+
def build_node_args
|
109
|
+
hsh = {
|
110
|
+
:dependencies => adapter.dependencies,
|
111
|
+
:provides => Array(provides),
|
112
|
+
}
|
113
|
+
hsh[:collection] = adapter.collection if adapter.collection?
|
114
|
+
hsh
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
class ExecutionContext
|
119
|
+
def initialize adapter, state
|
120
|
+
@__adapter__ = adapter
|
121
|
+
@__state__ = state
|
122
|
+
return unless adapter.collection?
|
123
|
+
self.singleton_class.send :define_method, :fetch_collection do
|
124
|
+
@__state__.fetch adapter.collection
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def perform *args
|
129
|
+
@__adapter__.perform @__state__, *args
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module Orchestra
|
2
|
+
module DSL
|
3
|
+
module Operations
|
4
|
+
class Builder
|
5
|
+
attr_writer :command, :result
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@nodes = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def build_operation
|
12
|
+
raise ArgumentError, "Must supply a result" if @result.nil?
|
13
|
+
raise ArgumentError, "Must supply at least one node" if @nodes.empty?
|
14
|
+
Operation.new(
|
15
|
+
:command => @command,
|
16
|
+
:nodes => @nodes,
|
17
|
+
:result => @result,
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
def add_node name_or_object, args = {}, &block
|
22
|
+
name, node = case name_or_object
|
23
|
+
when nil then build_anonymous_node block
|
24
|
+
when Operation then build_embedded_operation_node name_or_object
|
25
|
+
when ::String, ::Symbol then build_inline_node name_or_object, block
|
26
|
+
else build_object_node name_or_object, args
|
27
|
+
end
|
28
|
+
node.provisions << name.to_sym if node.provisions.empty?
|
29
|
+
set_node name.to_sym, node
|
30
|
+
end
|
31
|
+
|
32
|
+
def set_node name, node
|
33
|
+
if @nodes.has_key? name
|
34
|
+
raise ArgumentError, "There are duplicate nodes named #{name.inspect}"
|
35
|
+
end
|
36
|
+
@nodes[name] = node
|
37
|
+
node.freeze
|
38
|
+
end
|
39
|
+
|
40
|
+
def build_anonymous_node block
|
41
|
+
node = Node::InlineNode.build &block
|
42
|
+
unless node.provisions.size == 1
|
43
|
+
raise ArgumentError, "Could not infer name for node from a provision"
|
44
|
+
end
|
45
|
+
name = node.provisions.fetch 0
|
46
|
+
[name, node]
|
47
|
+
end
|
48
|
+
|
49
|
+
def build_embedded_operation_node operation
|
50
|
+
name = object_name operation
|
51
|
+
[name || operation.result, operation]
|
52
|
+
end
|
53
|
+
|
54
|
+
def build_inline_node name, block
|
55
|
+
node = Node::InlineNode.build &block
|
56
|
+
[name, node]
|
57
|
+
end
|
58
|
+
|
59
|
+
def build_object_node object, args
|
60
|
+
name = object_name object
|
61
|
+
node = ObjectAdapter.build_node object, args
|
62
|
+
[name, node]
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def object_name object
|
68
|
+
object.name and Util.to_snake_case Util.demodulize object.name
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
class Context < BasicObject
|
73
|
+
def self.evaluate builder, &block
|
74
|
+
context = new builder
|
75
|
+
context.instance_eval &block
|
76
|
+
end
|
77
|
+
|
78
|
+
attr :nodes
|
79
|
+
|
80
|
+
def initialize builder
|
81
|
+
@builder = builder
|
82
|
+
end
|
83
|
+
|
84
|
+
def node *args, &block
|
85
|
+
@builder.add_node *args, &block
|
86
|
+
nil
|
87
|
+
end
|
88
|
+
|
89
|
+
def result= result
|
90
|
+
@builder.result = result
|
91
|
+
nil
|
92
|
+
end
|
93
|
+
|
94
|
+
def result name = nil, &block
|
95
|
+
node = @builder.add_node name, &block
|
96
|
+
name ||= node.provisions.fetch 0
|
97
|
+
self.result = name
|
98
|
+
end
|
99
|
+
|
100
|
+
def finally name = :__finally__, &block
|
101
|
+
@builder.add_node name, &block
|
102
|
+
@builder.command = true
|
103
|
+
self.result = name
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Orchestra
|
2
|
+
class Error < StandardError
|
3
|
+
def list_out list
|
4
|
+
list = list.map &:inspect
|
5
|
+
return list.fetch 0 if list.size == 1
|
6
|
+
list.fetch 0
|
7
|
+
second_to_last, last = list.slice! -2..-1
|
8
|
+
str = list.join ', '
|
9
|
+
str << ', ' unless str.empty?
|
10
|
+
str << "#{second_to_last} and #{last}"
|
11
|
+
str
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class MissingProvisionError < Error
|
16
|
+
def initialize missing_provisions
|
17
|
+
@missing_provisions = missing_provisions
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_s
|
21
|
+
"failed to supply output: #{list_out @missing_provisions}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class CircularDependencyError < Error
|
26
|
+
def to_s
|
27
|
+
"Circular dependency detected! Check your dependencies/provides"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class MissingInputError < Error
|
32
|
+
def initialize missing_input
|
33
|
+
@missing_input = missing_input
|
34
|
+
end
|
35
|
+
|
36
|
+
def count
|
37
|
+
@missing_input.count
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_s
|
41
|
+
"Missing input#{'s' unless count == 1} #{list_out @missing_input}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Orchestra
|
2
|
+
class Node
|
3
|
+
class Output
|
4
|
+
attr :hsh, :node, :raw
|
5
|
+
|
6
|
+
def self.process node, raw
|
7
|
+
instance = new node, raw
|
8
|
+
instance.massage
|
9
|
+
instance.hsh
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize node, raw
|
13
|
+
@node = node
|
14
|
+
@raw = raw
|
15
|
+
end
|
16
|
+
|
17
|
+
def provisions
|
18
|
+
node.provisions
|
19
|
+
end
|
20
|
+
|
21
|
+
def collection?
|
22
|
+
node.collection?
|
23
|
+
end
|
24
|
+
|
25
|
+
def massage
|
26
|
+
@raw.compact! if collection?
|
27
|
+
@hsh = coerce_to_hash
|
28
|
+
prune
|
29
|
+
ensure_all_provisions_supplied!
|
30
|
+
end
|
31
|
+
|
32
|
+
def coerce_to_hash
|
33
|
+
return Hash(raw) unless provisions.size == 1
|
34
|
+
return raw if all_provisions_supplied? raw if raw.kind_of? Hash
|
35
|
+
raise MissingProvisionError.new provisions if raw.nil?
|
36
|
+
{ provisions.first => raw }
|
37
|
+
end
|
38
|
+
|
39
|
+
def all_provisions_supplied? hsh = @hsh
|
40
|
+
provisions.all? &included_in_output(hsh)
|
41
|
+
end
|
42
|
+
|
43
|
+
def missing_provisions
|
44
|
+
provisions.reject &included_in_output
|
45
|
+
end
|
46
|
+
|
47
|
+
def included_in_output hsh = @hsh
|
48
|
+
hsh.keys.method :include?
|
49
|
+
end
|
50
|
+
|
51
|
+
def prune
|
52
|
+
hsh.select! do |key, _| provisions.include? key end
|
53
|
+
end
|
54
|
+
|
55
|
+
def ensure_all_provisions_supplied!
|
56
|
+
return if all_provisions_supplied?
|
57
|
+
raise MissingProvisionError.new missing_provisions
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
module Orchestra
|
2
|
+
class Node
|
3
|
+
autoload :Output, "orchestra/node/output"
|
4
|
+
|
5
|
+
attr :collection, :dependencies, :provisions
|
6
|
+
|
7
|
+
def initialize args = {}
|
8
|
+
@provisions,
|
9
|
+
@collection,
|
10
|
+
@dependencies = Util.extract_key_args(
|
11
|
+
args,
|
12
|
+
:provides => [],
|
13
|
+
:collection => nil,
|
14
|
+
:dependencies => [],
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
def required_dependencies
|
19
|
+
dependencies - optional_dependencies
|
20
|
+
end
|
21
|
+
|
22
|
+
def optional_dependencies
|
23
|
+
defaults.keys
|
24
|
+
end
|
25
|
+
|
26
|
+
def collection?
|
27
|
+
collection ? true : false
|
28
|
+
end
|
29
|
+
|
30
|
+
def perform input = {}
|
31
|
+
performance = Performance.new Conductor.new, {}, input
|
32
|
+
Performance::Movement.perform self, performance
|
33
|
+
end
|
34
|
+
|
35
|
+
def process raw_output
|
36
|
+
Output.process self, raw_output
|
37
|
+
end
|
38
|
+
|
39
|
+
class DelegateNode < Node
|
40
|
+
attr :adapter
|
41
|
+
|
42
|
+
def initialize adapter, args = {}
|
43
|
+
@adapter = adapter
|
44
|
+
super args
|
45
|
+
end
|
46
|
+
|
47
|
+
def build_context input
|
48
|
+
adapter.build_context input
|
49
|
+
end
|
50
|
+
|
51
|
+
def optional_dependencies
|
52
|
+
adapter.object_method.optional_dependencies
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class InlineNode < Node
|
57
|
+
def self.build &block
|
58
|
+
builder = DSL::Nodes::Builder.new
|
59
|
+
DSL::Nodes::Context.evaluate builder, &block
|
60
|
+
builder.build_node
|
61
|
+
end
|
62
|
+
|
63
|
+
attr :context_class, :defaults, :perform_block
|
64
|
+
|
65
|
+
def initialize args = {}
|
66
|
+
@defaults = args.delete :defaults do {} end
|
67
|
+
@perform_block = args.fetch :perform_block
|
68
|
+
args.delete :perform_block
|
69
|
+
super args
|
70
|
+
@context_class = build_execution_context_class
|
71
|
+
validate!
|
72
|
+
end
|
73
|
+
|
74
|
+
def validate!
|
75
|
+
unless perform_block
|
76
|
+
raise ArgumentError, "expected inline node to define a perform block"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def build_execution_context_class
|
81
|
+
context = Class.new ExecutionContext
|
82
|
+
context.class_exec dependencies, collection do |deps, collection|
|
83
|
+
deps.each do |dep| define_dependency dep end
|
84
|
+
alias_method :fetch_collection, collection if collection
|
85
|
+
end
|
86
|
+
context
|
87
|
+
end
|
88
|
+
|
89
|
+
def build_context input
|
90
|
+
state = apply_defaults input
|
91
|
+
execution_context = context_class.new state, perform_block
|
92
|
+
end
|
93
|
+
|
94
|
+
def apply_defaults input
|
95
|
+
defaults.each do |key, thunk|
|
96
|
+
next if input.has_key? key
|
97
|
+
input[key] = thunk.call
|
98
|
+
end
|
99
|
+
input
|
100
|
+
end
|
101
|
+
|
102
|
+
def optional_dependencies
|
103
|
+
defaults.keys
|
104
|
+
end
|
105
|
+
|
106
|
+
class ExecutionContext
|
107
|
+
def self.define_dependency dep
|
108
|
+
define_method dep do
|
109
|
+
ivar = "@#{dep}"
|
110
|
+
return instance_variable_get ivar if instance_variable_defined? ivar
|
111
|
+
instance_variable_set ivar, @__state__[dep]
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def initialize state, perform_block
|
116
|
+
@__perform_block__ = perform_block
|
117
|
+
@__state__ = state
|
118
|
+
end
|
119
|
+
|
120
|
+
def perform item = nil
|
121
|
+
if @__perform_block__.arity == 0
|
122
|
+
instance_exec &@__perform_block__
|
123
|
+
else
|
124
|
+
instance_exec item, &@__perform_block__
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|