lib/adhearsion/component_manager.rb in eric-adhearsion-0.7.999 vs lib/adhearsion/component_manager.rb in eric-adhearsion-0.8.0
- old
+ new
@@ -1,277 +1,208 @@
module Adhearsion
-
module Components
- class Manager
- attr_reader :active_components, :host_information, :started_components, :components_with_call_context
- def initialize
- @active_components = {}
- @started_components = []
- @components_with_call_context = {}
- end
-
- def [](component_name)
- active_components[component_name].component_class.instance
- end
- def component(component_name)
- active_components[component_name]
- end
+ mattr_accessor :component_manager
- def has_component?(component_name)
- active_components.has_key?(component_name)
- end
-
- def component_gems
- @repository ||= RubygemsRepository.new
- @repository.adhearsion_gems
- end
+ class ConfigurationError < Exception; end
- def load
- return unless File.exist?(AHN_ROOT.component_path)
- component_directories = Dir.glob(File.join(AHN_ROOT.component_path, "*"))
- component_directories.each do |component_directory|
- component_name = File.basename(component_directory).to_sym
- @active_components[component_name] = Component.new(self, component_name, component_directory)
+ class ComponentManager
+
+ class << self
+
+ def scopes_valid?(*scopes)
+ unrecognized_scopes = (scopes.flatten - SCOPE_NAMES).map(&:inspect)
+ raise ArgumentError, "Unrecognized scopes #{unrecognized_scopes.to_sentence}" if unrecognized_scopes.any?
+ true
end
+
end
-
- def start
- @active_components.keys.each do |name|
- @active_components[name].start
+
+ SCOPE_NAMES = [:dialplan, :events, :generators, :rpc, :global]
+ DEFAULT_CONFIG_NAME = "config.yml"
+
+ attr_reader :scopes, :lazy_config_loader
+ def initialize(path_to_container_directory)
+ @path_to_container_directory = path_to_container_directory
+ @scopes = SCOPE_NAMES.inject({}) do |scopes, name|
+ scopes[name] = Module.new
+ scopes
end
+ @lazy_config_loader = LazyConfigLoader.new(self)
end
-
- def stop
- @started_components.reverse.each do |name|
- @active_components[name].stop
- end
- end
-
- class RubygemsRepository
- def initialize
- require 'rubygems'
- Gem.manage_gems
- end
-
- def adhearsion_gems
- gems = {}
- Gem.source_index.each {|name, spec| gems[spec.name] = spec.full_gem_path if spec.requirements.include?("adhearsion")}
- gems
- end
- end
- end
-
- ClassToGetCallContext = Struct.new(:component_class, :instance_variable)
- class ClassToGetCallContext
- def instantiate_with_call_context(call_context, *args, &block)
- component = component_class.allocate
- component.instance_variable_set(("@"+instance_variable.to_s).to_sym, call_context)
- component.send(:initialize, *args, &block)
- component
- end
- end
-
-
- #component behavior is shared across components
- module Behavior
- def self.included(component_class)
- component_class.extend(ClassMethods)
- end
- def component_name
- Component.name
+ ##
+ # Includes the anonymous Module created for the :global scope in Object, making its methods globally accessible.
+ #
+ def globalize_global_scope!
+ Object.send :include, @scopes[:global]
end
-
- def component_description
- Configuration.description || Component.name
- end
- module ClassMethods
- def add_call_context(params = {:as => :call_context})
- attr_reader params[:as]
- ComponentManager.components_with_call_context[name] = ClassToGetCallContext.new(self, params[:as])
+ def load_components
+ components = Dir.glob(File.join(@path_to_container_directory + "/*")).select do |path|
+ File.directory?(path)
end
- end
- end
-
- class Component
- class << self
- def prepare_component_class(component_module, name)
- component_class_name = name.to_s.camelize
- component_module.module_eval(<<-EVAL, __FILE__, __LINE__)
- class #{component_class_name}
- def self.name
- '#{component_class_name}'
- end
- include Adhearsion::Components::Behavior
- end
- EVAL
+ components.map! { |path| File.basename path }
+ components.each do |component|
+ next if component == "disabled"
+ component_file = File.join(@path_to_container_directory, component, component + ".rb")
+ if File.exists? component_file
+ load_file component_file
+ else
+ ahn_log.warn "Component directory does not contain a matching .rb file! Was expecting #{component_file.inspect}"
+ end
end
- end
- attr_reader :manager, :name, :path, :component_module, :component_class
- def initialize(manager, name, path)
- @manager = manager
- @name = name
- @path = path
- unless File.exist?(main_file_name)
- gem_path = @manager.component_gems[@name.to_s]
- raise "The component '#{@name}' does not have the main file: #{main_file_name}" unless gem_path
- @path = gem_path
- end
- @started = false
end
- def start
- return if @started
- manager.started_components << @name
- @started = true
- @component_module = ComponentModule.new(self) do |component_module|
- Component.prepare_component_class(component_module, @name)
- component_module.load_configuration_file
+ ##
+ # Loads the configuration file for a given component name.
+ #
+ # @return [Hash] The loaded YAML for the given component name. An empty Hash if no YAML file exists.
+ #
+ def configuration_for_component_named(component_name)
+ component_dir = File.join(@path_to_container_directory, component_name)
+ config_file = File.join component_dir, DEFAULT_CONFIG_NAME
+ if File.exists?(config_file)
+ YAML.load_file config_file
+ else
+ return {}
end
- @component_module.require(File.join("lib", @name.to_s))
end
- def configuration
- @component_module.const_get(:Configuration)
+ def extend_object_with(object, *scopes)
+ raise ArgumentError, "Must supply at least one scope!" if scopes.empty?
+
+ self.class.scopes_valid? scopes
+
+ scopes.each do |scope|
+ methods = @scopes[scope]
+ if object.kind_of?(Module)
+ object.send :include, methods
+ else
+ object.extend methods
+ end
+ end
+ object
end
-
- def stop
- #@component_class.unload if @component_class && @component_class.respond_to?(:unload)
+
+ def load_code(code)
+ load_container ComponentDefinitionContainer.load_code(code)
end
-
- def configuration_file
- File.join(path, configuration_file_name)
+
+ def load_file(filename)
+ load_container ComponentDefinitionContainer.load_file(filename)
end
- private
+ protected
- def main_file_name
- File.join(@path, "lib", @name.to_s+".rb")
+ def load_container(container)
+ container.constants.each do |constant_name|
+ constant_value = container.const_get(constant_name)
+ Object.const_set(constant_name, constant_value)
end
-
- def configuration_file_name
- "configuration.rb"
+ metadata = container.metaclass.send(:instance_variable_get, :@metadata)
+ metadata[:initialization_block].call if metadata[:initialization_block]
+
+ self.class.scopes_valid? metadata[:scopes].keys
+
+ metadata[:scopes].each_pair do |scope, method_definition_blocks|
+ method_definition_blocks.each do |method_definition_block|
+ @scopes[scope].module_eval(&method_definition_block)
+ end
end
- end
-
- class ComponentModule < Module
- # The file with which the Script was instantiated.
- attr_reader :main_file
-
- # The directory in which main_file is located, and relative to which
- # #load searches for files before falling back to Kernel#load.
- attr_reader :dir
-
- # A hash that maps <tt>filename=>true</tt> for each file that has been
- # required locally by the script. This has the same semantics as <tt>$"</tt>,
- # alias <tt>$LOADED_FEATURES</tt>, except that it is local to this script.
- attr_reader :loaded_features
-
- class << self
- alias load new
+ container
end
- # Creates new Script, and loads _main_file_ in the scope of the Script. If a
- # block is given, the script is passed to it before loading from the file, and
- # constants can be defined as inputs to the script.
- attr_reader :component
- def initialize(component) # :yields: self
- extend ComponentModuleMethods
- @component = component
- @loaded_features = {}
-
- const_set :Component, component
- const_set :Configuration, OpenStruct.new(:description => nil)
-
- yield self if block_given?
- end
-
- def load_configuration_file
- load_in_module(component.configuration_file)
- end
-
- # Loads _file_ into this Script. Searches relative to the local dir, that is,
- # the dir of the file given in the original call to
- # <tt>Script.load(file)</tt>, loads the file, if found, into this Script's
- # scope, and returns true. If the file is not found, falls back to
- # <tt>Kernel.load</tt>, which searches on <tt>$LOAD_PATH</tt>, loads the file,
- # if found, into global scope, and returns true. Otherwise, raises
- # <tt>LoadError</tt>.
- #
- # The _wrap_ argument is passed to <tt>Kernel.load</tt> in the fallback case,
- # when the file is not found locally.
- #
- # Typically called from within the main file to load additional sub files, or
- # from those sub files.
-
- def load(file, wrap = false)
- load_in_module(File.join(component.path, file))
- true
- rescue MissingFile
- super
- end
-
- # Analogous to <tt>Kernel#require</tt>. First tries the local dir, then falls
- # back to <tt>Kernel#require</tt>. Will load a given _feature_ only once.
- #
- # Note that extensions (*.so, *.dll) can be required in the global scope, as
- # usual, but not in the local scope. (This is not much of a limitation in
- # practice--you wouldn't want to load an extension more than once.) This
- # implementation falls back to <tt>Kernel#require</tt> when the argument is an
- # extension or is not found locally.
-
- def require(feature)
- unless @loaded_features[feature]
- @loaded_features[feature] = true
- file = feature
- file += ".rb" unless /\.rb$/ =~ file
- load_in_module(File.join(component.path, file))
+ class ComponentDefinitionContainer < Module
+
+ class << self
+ def load_code(code)
+ returning(new) do |instance|
+ instance.module_eval code
+ end
+ end
+
+ def load_file(filename)
+ returning(new) do |instance|
+ instance.module_eval File.read(filename), filename
+ end
+ end
end
- rescue MissingFile
- @loaded_features[feature] = false
- super
+
+ def initialize(&block)
+ # Hide our instance variables in the singleton class
+ metadata = {}
+ metaclass.send(:instance_variable_set, :@metadata, metadata)
+
+ metadata[:scopes] = ComponentManager::SCOPE_NAMES.inject({}) do |scopes, name|
+ scopes[name] = []
+ scopes
+ end
+
+ super
+
+ meta_def(:initialize) { raise "This object has already been instantiated. Are you sure you didn't mean initialization()?" }
+ end
+
+ def methods_for(*scopes, &block)
+ raise ArgumentError if scopes.empty?
+
+ ComponentManager.scopes_valid? scopes
+
+ metadata = metaclass.send(:instance_variable_get, :@metadata)
+ scopes.each { |scope| metadata[:scopes][scope] << block }
+ end
+
+ def initialization(&block)
+ # Raise an exception if the initialization block has already been set
+ metadata = metaclass.send(:instance_variable_get, :@metadata)
+ if metadata[:initialization_block]
+ raise "You should only have one initialization() block!"
+ else
+ metadata[:initialization_block] = block
+ end
+ end
+ alias initialisation initialization
+
+ protected
+
+ class << self
+ def self.method_added(method_name)
+ @methods ||= []
+ @methods << method_name
+ end
+ end
+
end
-
- # Raised by #load_in_module, caught by #load and #require.
- class MissingFile < LoadError; end
-
- # Loads _file_ in this module's context. Note that <tt>\_\_FILE\_\_</tt> and
- # <tt>\_\_LINE\_\_</tt> work correctly in _file_.
- # Called by #load and #require; not normally called directly.
-
- def load_in_module(file)
- module_eval(File.read(file), File.expand_path(file))
- rescue Errno::ENOENT => e
- if /#{file}$/ =~ e.message
- raise MissingFile, e.message
- else
- raise
+
+ class ComponentMethodDefinitionContainer < Module
+ class << self
+ def method_added(method_name)
+ @methods ||= []
+ @methods << method_name
+ end
end
+
+ attr_reader :scopes
+ def initialize(*scopes, &block)
+ @scopes = []
+ super(&block)
+ end
+
end
-
- def to_s
- "#<#{self.class}:#{File.basename(component.path)}>"
- end
-
- module ComponentModuleMethods
- # This is so that <tt>def meth...</tt> behaves like in Ruby's top-level
- # context. The implementation simply calls
- # <tt>Module#module_function(name)</tt>.
- def method_added(name) # :nodoc:
- module_function(name)
+
+ class LazyConfigLoader
+ def initialize(component_manager)
+ @component_manager = component_manager
end
-
- def start_component_after(*others)
- others.each do |component_name|
- component.manager.active_components[component_name].start
- end
+
+ def method_missing(component_name)
+ config = @component_manager.configuration_for_component_named(component_name.to_s)
+ (class << self; self; end).send(:define_method, component_name) { config }
+ config
end
-
end
+
end
end
-
- ComponentManager = Components::Manager.new unless defined? ComponentManager
end