ntl-orchestra 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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
|