sevenscale-adhearsion 0.7.1000
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.
- data/CHANGELOG +3 -0
- data/LICENSE +456 -0
- data/Manifest.txt +149 -0
- data/README.txt +6 -0
- data/Rakefile +48 -0
- data/ahn_generators/component/USAGE +5 -0
- data/ahn_generators/component/component_generator.rb +57 -0
- data/ahn_generators/component/templates/configuration.rb +0 -0
- data/ahn_generators/component/templates/lib/lib.rb.erb +3 -0
- data/ahn_generators/component/templates/test/test.rb.erb +12 -0
- data/ahn_generators/component/templates/test/test_helper.rb +14 -0
- data/app_generators/ahn/USAGE +5 -0
- data/app_generators/ahn/ahn_generator.rb +76 -0
- data/app_generators/ahn/templates/.ahnrc +12 -0
- data/app_generators/ahn/templates/README +8 -0
- data/app_generators/ahn/templates/Rakefile +3 -0
- data/app_generators/ahn/templates/components/simon_game/configuration.rb +0 -0
- data/app_generators/ahn/templates/components/simon_game/lib/simon_game.rb +61 -0
- data/app_generators/ahn/templates/components/simon_game/test/test_helper.rb +14 -0
- data/app_generators/ahn/templates/components/simon_game/test/test_simon_game.rb +31 -0
- data/app_generators/ahn/templates/config/startup.rb +53 -0
- data/app_generators/ahn/templates/dialplan.rb +4 -0
- data/bin/ahn +28 -0
- data/bin/ahnctl +68 -0
- data/bin/jahn +32 -0
- data/lib/adhearsion/blank_slate.rb +5 -0
- data/lib/adhearsion/cli.rb +106 -0
- data/lib/adhearsion/component_manager.rb +277 -0
- data/lib/adhearsion/core_extensions/all.rb +9 -0
- data/lib/adhearsion/core_extensions/array.rb +0 -0
- data/lib/adhearsion/core_extensions/custom_daemonizer.rb +45 -0
- data/lib/adhearsion/core_extensions/global.rb +1 -0
- data/lib/adhearsion/core_extensions/guid.rb +5 -0
- data/lib/adhearsion/core_extensions/hash.rb +0 -0
- data/lib/adhearsion/core_extensions/metaprogramming.rb +17 -0
- data/lib/adhearsion/core_extensions/numeric.rb +4 -0
- data/lib/adhearsion/core_extensions/proc.rb +0 -0
- data/lib/adhearsion/core_extensions/pseudo_uuid.rb +11 -0
- data/lib/adhearsion/core_extensions/publishable.rb +73 -0
- data/lib/adhearsion/core_extensions/relationship_properties.rb +40 -0
- data/lib/adhearsion/core_extensions/string.rb +26 -0
- data/lib/adhearsion/core_extensions/thread.rb +13 -0
- data/lib/adhearsion/core_extensions/thread_safety.rb +7 -0
- data/lib/adhearsion/core_extensions/time.rb +0 -0
- data/lib/adhearsion/distributed/gateways/dbus_gateway.rb +0 -0
- data/lib/adhearsion/distributed/gateways/osa_gateway.rb +0 -0
- data/lib/adhearsion/distributed/gateways/rest_gateway.rb +9 -0
- data/lib/adhearsion/distributed/gateways/soap_gateway.rb +9 -0
- data/lib/adhearsion/distributed/gateways/xmlrpc_gateway.rb +9 -0
- data/lib/adhearsion/distributed/peer_finder.rb +0 -0
- data/lib/adhearsion/distributed/remote_cli.rb +0 -0
- data/lib/adhearsion/hooks.rb +57 -0
- data/lib/adhearsion/host_definitions.rb +63 -0
- data/lib/adhearsion/initializer/asterisk.rb +59 -0
- data/lib/adhearsion/initializer/configuration.rb +202 -0
- data/lib/adhearsion/initializer/database.rb +92 -0
- data/lib/adhearsion/initializer/drb.rb +25 -0
- data/lib/adhearsion/initializer/freeswitch.rb +22 -0
- data/lib/adhearsion/initializer/paths.rb +55 -0
- data/lib/adhearsion/initializer/rails.rb +40 -0
- data/lib/adhearsion/initializer.rb +217 -0
- data/lib/adhearsion/logging.rb +92 -0
- data/lib/adhearsion/services/scheduler.rb +5 -0
- data/lib/adhearsion/tasks/database.rb +5 -0
- data/lib/adhearsion/tasks/generating.rb +20 -0
- data/lib/adhearsion/tasks/lint.rb +4 -0
- data/lib/adhearsion/tasks/testing.rb +37 -0
- data/lib/adhearsion/tasks.rb +15 -0
- data/lib/adhearsion/version.rb +9 -0
- data/lib/adhearsion/voip/asterisk/agi_server.rb +78 -0
- data/lib/adhearsion/voip/asterisk/ami/actions.rb +238 -0
- data/lib/adhearsion/voip/asterisk/ami/machine.rb +871 -0
- data/lib/adhearsion/voip/asterisk/ami/machine.rl +109 -0
- data/lib/adhearsion/voip/asterisk/ami/parser.rb +262 -0
- data/lib/adhearsion/voip/asterisk/ami.rb +147 -0
- data/lib/adhearsion/voip/asterisk/commands.rb +1186 -0
- data/lib/adhearsion/voip/asterisk/config_generators/agents.conf.rb +140 -0
- data/lib/adhearsion/voip/asterisk/config_generators/config_generator.rb +101 -0
- data/lib/adhearsion/voip/asterisk/config_generators/queues.conf.rb +250 -0
- data/lib/adhearsion/voip/asterisk/config_generators/voicemail.conf.rb +240 -0
- data/lib/adhearsion/voip/asterisk/config_manager.rb +71 -0
- data/lib/adhearsion/voip/asterisk/special_dial_plan_managers.rb +80 -0
- data/lib/adhearsion/voip/asterisk.rb +4 -0
- data/lib/adhearsion/voip/call.rb +402 -0
- data/lib/adhearsion/voip/call_routing.rb +64 -0
- data/lib/adhearsion/voip/commands.rb +9 -0
- data/lib/adhearsion/voip/constants.rb +39 -0
- data/lib/adhearsion/voip/conveniences.rb +18 -0
- data/lib/adhearsion/voip/dial_plan.rb +205 -0
- data/lib/adhearsion/voip/dsl/dialing_dsl/dialing_dsl_monkey_patches.rb +37 -0
- data/lib/adhearsion/voip/dsl/dialing_dsl.rb +151 -0
- data/lib/adhearsion/voip/dsl/dialplan/control_passing_exception.rb +27 -0
- data/lib/adhearsion/voip/dsl/dialplan/dispatcher.rb +124 -0
- data/lib/adhearsion/voip/dsl/dialplan/parser.rb +75 -0
- data/lib/adhearsion/voip/dsl/dialplan/thread_mixin.rb +16 -0
- data/lib/adhearsion/voip/dsl/numerical_string.rb +117 -0
- data/lib/adhearsion/voip/freeswitch/basic_connection_manager.rb +48 -0
- data/lib/adhearsion/voip/freeswitch/event_handler.rb +58 -0
- data/lib/adhearsion/voip/freeswitch/freeswitch_dialplan_command_factory.rb +129 -0
- data/lib/adhearsion/voip/freeswitch/inbound_connection_manager.rb +38 -0
- data/lib/adhearsion/voip/freeswitch/oes_server.rb +195 -0
- data/lib/adhearsion/voip/menu_state_machine/calculated_match.rb +80 -0
- data/lib/adhearsion/voip/menu_state_machine/matchers.rb +123 -0
- data/lib/adhearsion/voip/menu_state_machine/menu_builder.rb +58 -0
- data/lib/adhearsion/voip/menu_state_machine/menu_class.rb +149 -0
- data/lib/adhearsion.rb +31 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/spec/fixtures/dialplan.rb +3 -0
- data/spec/initializer/test_configuration.rb +267 -0
- data/spec/initializer/test_loading.rb +162 -0
- data/spec/initializer/test_paths.rb +43 -0
- data/spec/silence.rb +10 -0
- data/spec/test_ahn_command.rb +149 -0
- data/spec/test_code_quality.rb +87 -0
- data/spec/test_component_manager.rb +97 -0
- data/spec/test_constants.rb +8 -0
- data/spec/test_drb.rb +104 -0
- data/spec/test_helper.rb +94 -0
- data/spec/test_hooks.rb +37 -0
- data/spec/test_host_definitions.rb +79 -0
- data/spec/test_initialization.rb +105 -0
- data/spec/test_logging.rb +80 -0
- data/spec/test_relationship_properties.rb +54 -0
- data/spec/voip/asterisk/ami_response_definitions.rb +23 -0
- data/spec/voip/asterisk/config_file_generators/test_agents.rb +253 -0
- data/spec/voip/asterisk/config_file_generators/test_queues.rb +325 -0
- data/spec/voip/asterisk/config_file_generators/test_voicemail.rb +306 -0
- data/spec/voip/asterisk/menu_command/test_calculated_match.rb +111 -0
- data/spec/voip/asterisk/menu_command/test_matchers.rb +98 -0
- data/spec/voip/asterisk/mock_ami_server.rb +176 -0
- data/spec/voip/asterisk/test_agi_server.rb +453 -0
- data/spec/voip/asterisk/test_ami.rb +227 -0
- data/spec/voip/asterisk/test_commands.rb +2006 -0
- data/spec/voip/asterisk/test_config_manager.rb +129 -0
- data/spec/voip/dsl/dispatcher_spec_helper.rb +45 -0
- data/spec/voip/dsl/test_dialing_dsl.rb +268 -0
- data/spec/voip/dsl/test_dispatcher.rb +82 -0
- data/spec/voip/dsl/test_parser.rb +87 -0
- data/spec/voip/freeswitch/test_basic_connection_manager.rb +39 -0
- data/spec/voip/freeswitch/test_inbound_connection_manager.rb +39 -0
- data/spec/voip/freeswitch/test_oes_server.rb +9 -0
- data/spec/voip/test_call_routing.rb +127 -0
- data/spec/voip/test_dialplan_manager.rb +372 -0
- data/spec/voip/test_numerical_string.rb +48 -0
- data/spec/voip/test_phone_number.rb +36 -0
- data/test/test_ahn_generator.rb +59 -0
- data/test/test_component_generator.rb +52 -0
- data/test/test_generator_helper.rb +20 -0
- metadata +254 -0
@@ -0,0 +1,277 @@
|
|
1
|
+
module Adhearsion
|
2
|
+
|
3
|
+
module Components
|
4
|
+
class Manager
|
5
|
+
attr_reader :active_components, :host_information, :started_components, :components_with_call_context
|
6
|
+
def initialize
|
7
|
+
@active_components = {}
|
8
|
+
@started_components = []
|
9
|
+
@components_with_call_context = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def [](component_name)
|
13
|
+
active_components[component_name].component_class.instance
|
14
|
+
end
|
15
|
+
|
16
|
+
def component(component_name)
|
17
|
+
active_components[component_name]
|
18
|
+
end
|
19
|
+
|
20
|
+
def has_component?(component_name)
|
21
|
+
active_components.has_key?(component_name)
|
22
|
+
end
|
23
|
+
|
24
|
+
def component_gems
|
25
|
+
@repository ||= RubygemsRepository.new
|
26
|
+
@repository.adhearsion_gems
|
27
|
+
end
|
28
|
+
|
29
|
+
def load
|
30
|
+
return unless File.exist?(AHN_ROOT.component_path)
|
31
|
+
component_directories = Dir.glob(File.join(AHN_ROOT.component_path, "*"))
|
32
|
+
component_directories.each do |component_directory|
|
33
|
+
component_name = File.basename(component_directory).to_sym
|
34
|
+
@active_components[component_name] = Component.new(self, component_name, component_directory)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def start
|
39
|
+
@active_components.keys.each do |name|
|
40
|
+
@active_components[name].start
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def stop
|
45
|
+
@started_components.reverse.each do |name|
|
46
|
+
@active_components[name].stop
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class RubygemsRepository
|
51
|
+
def initialize
|
52
|
+
require 'rubygems'
|
53
|
+
Gem.manage_gems
|
54
|
+
end
|
55
|
+
|
56
|
+
def adhearsion_gems
|
57
|
+
gems = {}
|
58
|
+
Gem.source_index.each {|name, spec| gems[spec.name] = spec.full_gem_path if spec.requirements.include?("adhearsion")}
|
59
|
+
gems
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
ClassToGetCallContext = Struct.new(:component_class, :instance_variable)
|
65
|
+
class ClassToGetCallContext
|
66
|
+
def instantiate_with_call_context(call_context, *args, &block)
|
67
|
+
component = component_class.allocate
|
68
|
+
component.instance_variable_set(("@"+instance_variable.to_s).to_sym, call_context)
|
69
|
+
component.send(:initialize, *args, &block)
|
70
|
+
component
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
#component behavior is shared across components
|
76
|
+
module Behavior
|
77
|
+
def self.included(component_class)
|
78
|
+
component_class.extend(ClassMethods)
|
79
|
+
end
|
80
|
+
|
81
|
+
def component_name
|
82
|
+
Component.name
|
83
|
+
end
|
84
|
+
|
85
|
+
def component_description
|
86
|
+
Configuration.description || Component.name
|
87
|
+
end
|
88
|
+
|
89
|
+
module ClassMethods
|
90
|
+
def add_call_context(params = {:as => :call_context})
|
91
|
+
attr_reader params[:as]
|
92
|
+
ComponentManager.components_with_call_context[name] = ClassToGetCallContext.new(self, params[:as])
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
class Component
|
98
|
+
class << self
|
99
|
+
def prepare_component_class(component_module, name)
|
100
|
+
component_class_name = name.to_s.camelize
|
101
|
+
component_module.module_eval(<<-EVAL, __FILE__, __LINE__)
|
102
|
+
class #{component_class_name}
|
103
|
+
def self.name
|
104
|
+
'#{component_class_name}'
|
105
|
+
end
|
106
|
+
include Adhearsion::Components::Behavior
|
107
|
+
end
|
108
|
+
EVAL
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
attr_reader :manager, :name, :path, :component_module, :component_class
|
113
|
+
def initialize(manager, name, path)
|
114
|
+
@manager = manager
|
115
|
+
@name = name
|
116
|
+
@path = path
|
117
|
+
unless File.exist?(main_file_name)
|
118
|
+
gem_path = @manager.component_gems[@name.to_s]
|
119
|
+
raise "The component '#{@name}' does not have the main file: #{main_file_name}" unless gem_path
|
120
|
+
@path = gem_path
|
121
|
+
end
|
122
|
+
@started = false
|
123
|
+
end
|
124
|
+
|
125
|
+
def start
|
126
|
+
return if @started
|
127
|
+
manager.started_components << @name
|
128
|
+
@started = true
|
129
|
+
@component_module = ComponentModule.new(self) do |component_module|
|
130
|
+
Component.prepare_component_class(component_module, @name)
|
131
|
+
component_module.load_configuration_file
|
132
|
+
end
|
133
|
+
@component_module.require(File.join("lib", @name.to_s))
|
134
|
+
end
|
135
|
+
|
136
|
+
def configuration
|
137
|
+
@component_module.const_get(:Configuration)
|
138
|
+
end
|
139
|
+
|
140
|
+
def stop
|
141
|
+
#@component_class.unload if @component_class && @component_class.respond_to?(:unload)
|
142
|
+
end
|
143
|
+
|
144
|
+
def configuration_file
|
145
|
+
File.join(path, configuration_file_name)
|
146
|
+
end
|
147
|
+
|
148
|
+
private
|
149
|
+
|
150
|
+
def main_file_name
|
151
|
+
File.join(@path, "lib", @name.to_s+".rb")
|
152
|
+
end
|
153
|
+
|
154
|
+
def configuration_file_name
|
155
|
+
"configuration.rb"
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
class ComponentModule < Module
|
160
|
+
# The file with which the Script was instantiated.
|
161
|
+
attr_reader :main_file
|
162
|
+
|
163
|
+
# The directory in which main_file is located, and relative to which
|
164
|
+
# #load searches for files before falling back to Kernel#load.
|
165
|
+
attr_reader :dir
|
166
|
+
|
167
|
+
# A hash that maps <tt>filename=>true</tt> for each file that has been
|
168
|
+
# required locally by the script. This has the same semantics as <tt>$"</tt>,
|
169
|
+
# alias <tt>$LOADED_FEATURES</tt>, except that it is local to this script.
|
170
|
+
attr_reader :loaded_features
|
171
|
+
|
172
|
+
class << self
|
173
|
+
alias load new
|
174
|
+
end
|
175
|
+
|
176
|
+
# Creates new Script, and loads _main_file_ in the scope of the Script. If a
|
177
|
+
# block is given, the script is passed to it before loading from the file, and
|
178
|
+
# constants can be defined as inputs to the script.
|
179
|
+
attr_reader :component
|
180
|
+
def initialize(component) # :yields: self
|
181
|
+
extend ComponentModuleMethods
|
182
|
+
@component = component
|
183
|
+
@loaded_features = {}
|
184
|
+
|
185
|
+
const_set :Component, component
|
186
|
+
const_set :Configuration, OpenStruct.new(:description => nil)
|
187
|
+
|
188
|
+
yield self if block_given?
|
189
|
+
end
|
190
|
+
|
191
|
+
def load_configuration_file
|
192
|
+
load_in_module(component.configuration_file)
|
193
|
+
end
|
194
|
+
|
195
|
+
# Loads _file_ into this Script. Searches relative to the local dir, that is,
|
196
|
+
# the dir of the file given in the original call to
|
197
|
+
# <tt>Script.load(file)</tt>, loads the file, if found, into this Script's
|
198
|
+
# scope, and returns true. If the file is not found, falls back to
|
199
|
+
# <tt>Kernel.load</tt>, which searches on <tt>$LOAD_PATH</tt>, loads the file,
|
200
|
+
# if found, into global scope, and returns true. Otherwise, raises
|
201
|
+
# <tt>LoadError</tt>.
|
202
|
+
#
|
203
|
+
# The _wrap_ argument is passed to <tt>Kernel.load</tt> in the fallback case,
|
204
|
+
# when the file is not found locally.
|
205
|
+
#
|
206
|
+
# Typically called from within the main file to load additional sub files, or
|
207
|
+
# from those sub files.
|
208
|
+
|
209
|
+
def load(file, wrap = false)
|
210
|
+
load_in_module(File.join(component.path, file))
|
211
|
+
true
|
212
|
+
rescue MissingFile
|
213
|
+
super
|
214
|
+
end
|
215
|
+
|
216
|
+
# Analogous to <tt>Kernel#require</tt>. First tries the local dir, then falls
|
217
|
+
# back to <tt>Kernel#require</tt>. Will load a given _feature_ only once.
|
218
|
+
#
|
219
|
+
# Note that extensions (*.so, *.dll) can be required in the global scope, as
|
220
|
+
# usual, but not in the local scope. (This is not much of a limitation in
|
221
|
+
# practice--you wouldn't want to load an extension more than once.) This
|
222
|
+
# implementation falls back to <tt>Kernel#require</tt> when the argument is an
|
223
|
+
# extension or is not found locally.
|
224
|
+
|
225
|
+
def require(feature)
|
226
|
+
unless @loaded_features[feature]
|
227
|
+
@loaded_features[feature] = true
|
228
|
+
file = feature
|
229
|
+
file += ".rb" unless /\.rb$/ =~ file
|
230
|
+
load_in_module(File.join(component.path, file))
|
231
|
+
end
|
232
|
+
rescue MissingFile
|
233
|
+
@loaded_features[feature] = false
|
234
|
+
super
|
235
|
+
end
|
236
|
+
|
237
|
+
# Raised by #load_in_module, caught by #load and #require.
|
238
|
+
class MissingFile < LoadError; end
|
239
|
+
|
240
|
+
# Loads _file_ in this module's context. Note that <tt>\_\_FILE\_\_</tt> and
|
241
|
+
# <tt>\_\_LINE\_\_</tt> work correctly in _file_.
|
242
|
+
# Called by #load and #require; not normally called directly.
|
243
|
+
|
244
|
+
def load_in_module(file)
|
245
|
+
module_eval(File.read(file), File.expand_path(file))
|
246
|
+
rescue Errno::ENOENT => e
|
247
|
+
if /#{file}$/ =~ e.message
|
248
|
+
raise MissingFile, e.message
|
249
|
+
else
|
250
|
+
raise
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
def to_s
|
255
|
+
"#<#{self.class}:#{File.basename(component.path)}>"
|
256
|
+
end
|
257
|
+
|
258
|
+
module ComponentModuleMethods
|
259
|
+
# This is so that <tt>def meth...</tt> behaves like in Ruby's top-level
|
260
|
+
# context. The implementation simply calls
|
261
|
+
# <tt>Module#module_function(name)</tt>.
|
262
|
+
def method_added(name) # :nodoc:
|
263
|
+
module_function(name)
|
264
|
+
end
|
265
|
+
|
266
|
+
def start_component_after(*others)
|
267
|
+
others.each do |component_name|
|
268
|
+
component.manager.active_components[component_name].start
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
ComponentManager = Components::Manager.new unless defined? ComponentManager
|
277
|
+
end
|
File without changes
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# This is largely based on the Daemonize library by Travis Whitton and
|
2
|
+
# Judson Lester. http://grub.ath.cx/daemonize. I cleaned it up a bit to
|
3
|
+
# meet Adhearsion's quality standards.
|
4
|
+
module Adhearsion
|
5
|
+
module CustomDaemonizer
|
6
|
+
|
7
|
+
# Try to fork if at all possible retrying every 5 sec if the
|
8
|
+
# maximum process limit for the system has been reached
|
9
|
+
def safefork
|
10
|
+
begin
|
11
|
+
pid = fork
|
12
|
+
return pid if pid
|
13
|
+
rescue Errno::EWOULDBLOCK
|
14
|
+
sleep 5
|
15
|
+
retry
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# This method causes the current running process to become a daemon
|
20
|
+
def daemonize(log_file='/dev/null')
|
21
|
+
oldmode = 0
|
22
|
+
srand # Split rand streams between spawning and daemonized process
|
23
|
+
safefork and exit # Fork and exit from the parent
|
24
|
+
|
25
|
+
# Detach from the controlling terminal
|
26
|
+
unless sess_id = Process.setsid
|
27
|
+
raise 'Cannot detach from controlled terminal'
|
28
|
+
end
|
29
|
+
|
30
|
+
# Prevent the possibility of acquiring a controlling terminal
|
31
|
+
if oldmode.zero?
|
32
|
+
trap 'SIGHUP', 'IGNORE'
|
33
|
+
exit if pid = safefork
|
34
|
+
end
|
35
|
+
|
36
|
+
Dir.chdir "/" # Release old working directory
|
37
|
+
File.umask 0000 # Ensure sensible umask
|
38
|
+
|
39
|
+
STDIN.reopen "/dev/null"
|
40
|
+
STDOUT.reopen '/dev/null', "a"
|
41
|
+
STDERR.reopen log_file
|
42
|
+
return oldmode ? sess_id : 0
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
Infinity = 1.0/0.0
|
File without changes
|
File without changes
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Adhearsion
|
2
|
+
class DrbDoor
|
3
|
+
|
4
|
+
include Singleton
|
5
|
+
|
6
|
+
def add(interface, name, meth)
|
7
|
+
@interfaces ||= {}
|
8
|
+
@interfaces[interface] ||= returning(Object.new) { |obj| obj.metaclass.send(:attr_accessor, :__methods) }
|
9
|
+
obj = @interfaces[interface]
|
10
|
+
obj.__methods ||= {}
|
11
|
+
obj.__methods[name] = meth
|
12
|
+
obj.instance_eval(<<-STR, __FILE__, __LINE__)
|
13
|
+
def #{name}(*args, &block)
|
14
|
+
begin
|
15
|
+
__methods["#{name}"].call(*args, &block)
|
16
|
+
rescue => exception
|
17
|
+
raise RuntimeError, exception.message, exception.backtrace
|
18
|
+
end
|
19
|
+
end
|
20
|
+
STR
|
21
|
+
end
|
22
|
+
|
23
|
+
def method_missing(name, *args, &block)
|
24
|
+
return Module.const_get(name) if name.to_s =~ /^[A-Z]/
|
25
|
+
super unless @interfaces && @interfaces.has_key?(name.to_s)
|
26
|
+
@interfaces[name.to_s]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
module Publishable
|
31
|
+
def self.included(base)
|
32
|
+
base.send(:alias_method_chain, :initialize, :publishable)
|
33
|
+
base.extend(ClassMethods)
|
34
|
+
end
|
35
|
+
|
36
|
+
def initialize_with_publishable(*args, &block)
|
37
|
+
initialize_without_publishable(*args, &block)
|
38
|
+
self.class.published_instance_methods.each do |(sym, interface)|
|
39
|
+
DrbDoor.instance.add(interface, sym.to_s, self.method(sym))
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
module ClassMethods
|
44
|
+
attr_reader :interface
|
45
|
+
attr_reader :published_instance_methods
|
46
|
+
|
47
|
+
def publish(options={}, &block)
|
48
|
+
@interface = options.delete(:through).to_s || self.to_s
|
49
|
+
begin
|
50
|
+
@capture = true
|
51
|
+
yield
|
52
|
+
ensure
|
53
|
+
@capture = false
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def method_added(sym)
|
58
|
+
return if not @capture
|
59
|
+
if sym.to_s !~ /method_added/
|
60
|
+
@published_instance_methods ||= []
|
61
|
+
@published_instance_methods << [sym, @interface]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def singleton_method_added(sym)
|
66
|
+
return if not @capture
|
67
|
+
if sym.to_s !~ /method_added/
|
68
|
+
DrbDoor.instance.add(@interface, sym.to_s, method(sym.to_s))
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
class Module
|
2
|
+
|
3
|
+
# In OOP, relationships between classes should be treated as *properties* of those classes. Often, in a complex OO
|
4
|
+
# architecture, you'll end up with many relationships that intermingle in monolithic ways, blunting the effectiveness of
|
5
|
+
# subclassing.
|
6
|
+
#
|
7
|
+
# For example, say you have an Automobile class which, in its constructor, instantiates a new Battery class and performs
|
8
|
+
# some operations on it such as calling an install() method. Let's also assume the Automobile class exposes a repair()
|
9
|
+
# method which uses a class-level method of Battery to diagnose your own instance of Battery. If the result of the
|
10
|
+
# diagnosis shows that the Battery is bad, the Automobile will instantiate a new Battery object and replace the old battery
|
11
|
+
# with the new one.
|
12
|
+
#
|
13
|
+
# Now, what if you wish to create a new Automobile derived from existing technology: a HybridAutomobile subclass. For this
|
14
|
+
# particular HybridAutomobile class, let's simply say the only difference between it and its parent is which kind of
|
15
|
+
# Battery it uses -- it requires its own special subclass of Battery. With Automobile's current implementation, its
|
16
|
+
# references to which Battery it instantiates and uses are embedded in the immutable method defintions. This
|
17
|
+
# HybridAutomobile needs to override which Battery its superclass' methods use and nothing else.
|
18
|
+
#
|
19
|
+
# For this reason, the Battery class which Automobile uses is semantically a property which others may want to override.
|
20
|
+
# In OOP theory, we define overridable properties in the form of methods and override those methods in the subclasses.
|
21
|
+
#
|
22
|
+
# This method exposes one method which creates human-readable semantics to defining these relationships as properties. It's
|
23
|
+
# used as follows:
|
24
|
+
#
|
25
|
+
# class Automobile
|
26
|
+
# relationship :battery => Battery
|
27
|
+
# relationship :chassis => Chassis
|
28
|
+
# # Other properties and instance methods here....
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# class HybridAutomobile < Automobile
|
32
|
+
# relationship :battery => HybridBattery
|
33
|
+
# end
|
34
|
+
def relationships(relationship_mapping)
|
35
|
+
relationship_mapping.each_pair do |class_name, class_object|
|
36
|
+
define_method(class_name) { class_object }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class String
|
2
|
+
|
3
|
+
def unindent
|
4
|
+
gsub(/^\s*/,'')
|
5
|
+
end
|
6
|
+
|
7
|
+
def unindent!
|
8
|
+
gsub!(/^\s*/,'')
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.random_char
|
12
|
+
case random_digit = rand(62)
|
13
|
+
when 0...10 : random_digit.to_s
|
14
|
+
when 10...36 : (random_digit + 55).chr
|
15
|
+
when 36...62 : (random_digit + 61).chr
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.random(length_of_string=8)
|
20
|
+
Array.new(length_of_string) { random_char }.join
|
21
|
+
end
|
22
|
+
|
23
|
+
def nameify() downcase.gsub(/[^\w]/, '') end
|
24
|
+
def nameify!() replace nameify end
|
25
|
+
|
26
|
+
end
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Adhearsion
|
2
|
+
|
3
|
+
|
4
|
+
module Hooks
|
5
|
+
|
6
|
+
class GenericHook
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@hooks = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def create_hook(&block)
|
13
|
+
@hooks.synchronize do
|
14
|
+
@hooks << block
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# TODO: This is hardly thread safe!
|
19
|
+
def trigger_hooks
|
20
|
+
@hooks.each &:call
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
class HookWithArguments < GenericHook
|
26
|
+
def trigger_hooks(*args)
|
27
|
+
@hooks.each { |hook| hook.call(*args) }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
ThreadsJoinedAfterInitialized = GenericHook.new
|
32
|
+
|
33
|
+
OnFailedCall = HookWithArguments.new
|
34
|
+
OnHungupCall = HookWithArguments.new
|
35
|
+
AfterInitialized = GenericHook.new
|
36
|
+
BeforeCall = GenericHook.new
|
37
|
+
AfterCall = GenericHook.new
|
38
|
+
TearDown = GenericHook.new
|
39
|
+
|
40
|
+
class << TearDown
|
41
|
+
def aliases
|
42
|
+
[:before_shutdown]
|
43
|
+
end
|
44
|
+
|
45
|
+
def catch_termination_signals
|
46
|
+
%w'INT TERM'.each do |sig|
|
47
|
+
trap sig do
|
48
|
+
ahn_log "Shutting down gracefully at #{Time.now}."
|
49
|
+
Adhearsion::Hooks::TearDown.trigger_hooks
|
50
|
+
exit
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|