adding new ceedling test project
This commit is contained in:
+403
@@ -0,0 +1,403 @@
|
||||
require 'diy/factory.rb'
|
||||
require 'yaml'
|
||||
require 'set'
|
||||
|
||||
module DIY #:nodoc:#
|
||||
VERSION = '1.1.2'
|
||||
class Context
|
||||
|
||||
class << self
|
||||
# Enable / disable automatic requiring of libraries. Default: true
|
||||
attr_accessor :auto_require
|
||||
end
|
||||
@auto_require = true
|
||||
|
||||
# Accepts a Hash defining the object context (usually loaded from objects.yml), and an additional
|
||||
# Hash containing objects to inject into the context.
|
||||
def initialize(context_hash, extra_inputs={})
|
||||
raise "Nil context hash" unless context_hash
|
||||
raise "Need a hash" unless context_hash.kind_of?(Hash)
|
||||
[ "[]", "keys" ].each do |mname|
|
||||
unless extra_inputs.respond_to?(mname)
|
||||
raise "Extra inputs must respond to hash-like [] operator and methods #keys and #each"
|
||||
end
|
||||
end
|
||||
|
||||
# store extra inputs
|
||||
if extra_inputs.kind_of?(Hash)
|
||||
@extra_inputs= {}
|
||||
extra_inputs.each { |k,v| @extra_inputs[k.to_s] = v } # smooth out the names
|
||||
else
|
||||
@extra_inputs = extra_inputs
|
||||
end
|
||||
|
||||
collect_object_and_subcontext_defs context_hash
|
||||
|
||||
# init the cache
|
||||
@cache = {}
|
||||
@cache['this_context'] = self
|
||||
end
|
||||
|
||||
|
||||
# Convenience: create a new DIY::Context by loading from a String (or open file handle.)
|
||||
def self.from_yaml(io_or_string, extra_inputs={})
|
||||
raise "nil input to YAML" unless io_or_string
|
||||
Context.new(YAML.load(io_or_string), extra_inputs)
|
||||
end
|
||||
|
||||
# Convenience: create a new DIY::Context by loading from the named file.
|
||||
def self.from_file(fname, extra_inputs={})
|
||||
raise "nil file name" unless fname
|
||||
self.from_yaml(File.read(fname), extra_inputs)
|
||||
end
|
||||
|
||||
# Return a reference to the object named. If necessary, the object will
|
||||
# be instantiated on first use. If the object is non-singleton, a new
|
||||
# object will be produced each time.
|
||||
def get_object(obj_name)
|
||||
key = obj_name.to_s
|
||||
obj = @cache[key]
|
||||
unless obj
|
||||
if extra_inputs_has(key)
|
||||
obj = @extra_inputs[key]
|
||||
else
|
||||
case @defs[key]
|
||||
when MethodDef
|
||||
obj = construct_method(key)
|
||||
when FactoryDef
|
||||
obj = construct_factory(key)
|
||||
@cache[key] = obj
|
||||
else
|
||||
obj = construct_object(key)
|
||||
@cache[key] = obj if @defs[key].singleton?
|
||||
end
|
||||
end
|
||||
end
|
||||
obj
|
||||
end
|
||||
alias :[] :get_object
|
||||
|
||||
# Inject a named object into the Context. This must be done before the Context has instantiated the
|
||||
# object in question.
|
||||
def set_object(obj_name,obj)
|
||||
key = obj_name.to_s
|
||||
raise "object '#{key}' already exists in context" if @cache.keys.include?(key)
|
||||
@cache[key] = obj
|
||||
end
|
||||
alias :[]= :set_object
|
||||
|
||||
# Provide a listing of object names
|
||||
def keys
|
||||
(@defs.keys.to_set + @extra_inputs.keys.to_set).to_a
|
||||
end
|
||||
|
||||
# Instantiate and yield the named subcontext
|
||||
def within(sub_context_name)
|
||||
# Find the subcontext definitaion:
|
||||
context_def = @sub_context_defs[sub_context_name.to_s]
|
||||
raise "No sub-context named #{sub_context_name}" unless context_def
|
||||
# Instantiate a new context using self as parent:
|
||||
context = Context.new( context_def, self )
|
||||
|
||||
yield context
|
||||
end
|
||||
|
||||
# Returns true if the context contains an object with the given name
|
||||
def contains_object(obj_name)
|
||||
key = obj_name.to_s
|
||||
@defs.keys.member?(key) or extra_inputs_has(key)
|
||||
end
|
||||
|
||||
# Every top level object in the Context is instantiated. This is especially useful for
|
||||
# systems that have "floating observers"... objects that are never directly accessed, who
|
||||
# would thus never be instantiated by coincedence. This does not build any subcontexts
|
||||
# that may exist.
|
||||
def build_everything
|
||||
@defs.keys.each { |k| self[k] }
|
||||
end
|
||||
alias :build_all :build_everything
|
||||
alias :preinstantiate_singletons :build_everything
|
||||
|
||||
private
|
||||
|
||||
def collect_object_and_subcontext_defs(context_hash)
|
||||
@defs = {}
|
||||
@sub_context_defs = {}
|
||||
get_defs_from context_hash
|
||||
end
|
||||
|
||||
def get_defs_from(hash, namespace=nil)
|
||||
hash.each do |name,info|
|
||||
# we modify the info hash below so it's important to have a new
|
||||
# instance to play with
|
||||
info = info.dup if info
|
||||
|
||||
# see if we are building a factory
|
||||
if info and info.has_key?('builds')
|
||||
unless info.has_key?('auto_require')
|
||||
info['auto_require'] = self.class.auto_require
|
||||
end
|
||||
|
||||
if namespace
|
||||
info['builds'] = namespace.build_classname(info['builds'])
|
||||
end
|
||||
@defs[name] = FactoryDef.new({:name => name,
|
||||
:target => info['builds'],
|
||||
:library => info['library'],
|
||||
:auto_require => info['auto_require']})
|
||||
next
|
||||
end
|
||||
|
||||
name = name.to_s
|
||||
case name
|
||||
when /^\+/
|
||||
# subcontext
|
||||
@sub_context_defs[name.gsub(/^\+/,'')] = info
|
||||
|
||||
when /^using_namespace/
|
||||
# namespace: use a module(s) prefix for the classname of contained object defs
|
||||
# NOTE: namespacing is NOT scope... it's just a convenient way to setup class names for a group of objects.
|
||||
get_defs_from info, parse_namespace(name)
|
||||
when /^method\s/
|
||||
key_name = name.gsub(/^method\s/, "")
|
||||
@defs[key_name] = MethodDef.new(:name => key_name,
|
||||
:object => info['object'],
|
||||
:method => info['method'],
|
||||
:attach => info['attach'])
|
||||
else
|
||||
# Normal object def
|
||||
info ||= {}
|
||||
if extra_inputs_has(name)
|
||||
raise ConstructionError.new(name, "Object definition conflicts with parent context")
|
||||
end
|
||||
unless info.has_key?('auto_require')
|
||||
info['auto_require'] = self.class.auto_require
|
||||
end
|
||||
if namespace
|
||||
if info['class']
|
||||
info['class'] = namespace.build_classname(info['class'])
|
||||
else
|
||||
info['class'] = namespace.build_classname(name)
|
||||
end
|
||||
end
|
||||
|
||||
@defs[name] = ObjectDef.new(:name => name, :info => info)
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def construct_method(key)
|
||||
method_definition = @defs[key]
|
||||
object = get_object(method_definition.object)
|
||||
method = object.method(method_definition.method)
|
||||
|
||||
unless method_definition.attach.nil?
|
||||
instance_var_name = "@__diy_#{method_definition.object}"
|
||||
|
||||
method_definition.attach.each do |object_key|
|
||||
get_object(object_key).instance_eval do
|
||||
instance_variable_set(instance_var_name, object)
|
||||
eval %|def #{key}(*args)
|
||||
#{instance_var_name}.#{method_definition.method}(*args)
|
||||
end|
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return method
|
||||
rescue Exception => oops
|
||||
build_and_raise_construction_error(key, oops)
|
||||
end
|
||||
|
||||
def construct_object(key)
|
||||
# Find the object definition
|
||||
obj_def = @defs[key]
|
||||
raise "No object definition for '#{key}'" unless obj_def
|
||||
# If object def mentions a library, load it
|
||||
require obj_def.library if obj_def.library
|
||||
|
||||
# Resolve all components for the object
|
||||
arg_hash = {}
|
||||
obj_def.components.each do |name,value|
|
||||
case value
|
||||
when Lookup
|
||||
arg_hash[name.to_sym] = get_object(value.name)
|
||||
when StringValue
|
||||
arg_hash[name.to_sym] = value.literal_value
|
||||
else
|
||||
raise "Cannot cope with component definition '#{value.inspect}'"
|
||||
end
|
||||
end
|
||||
# Get a reference to the class for the object
|
||||
big_c = get_class_for_name_with_module_delimeters(obj_def.class_name)
|
||||
# Make and return the instance
|
||||
if obj_def.use_class_directly?
|
||||
return big_c
|
||||
elsif arg_hash.keys.size > 0
|
||||
return big_c.new(arg_hash)
|
||||
else
|
||||
return big_c.new
|
||||
end
|
||||
rescue Exception => oops
|
||||
build_and_raise_construction_error(key, oops)
|
||||
end
|
||||
|
||||
def build_and_raise_construction_error(key, oops)
|
||||
cerr = ConstructionError.new(key,oops)
|
||||
cerr.set_backtrace(oops.backtrace)
|
||||
raise cerr
|
||||
end
|
||||
|
||||
def get_class_for_name_with_module_delimeters(class_name)
|
||||
class_name.split(/::/).inject(Object) do |mod,const_name| mod.const_get(const_name) end
|
||||
end
|
||||
|
||||
def extra_inputs_has(key)
|
||||
if key.nil? or key.strip == ''
|
||||
raise ArgumentError.new("Cannot lookup objects with nil keys")
|
||||
end
|
||||
@extra_inputs.keys.member?(key) or @extra_inputs.keys.member?(key.to_sym)
|
||||
end
|
||||
|
||||
def parse_namespace(str)
|
||||
Namespace.new(str)
|
||||
end
|
||||
end
|
||||
|
||||
class Namespace #:nodoc:#
|
||||
def initialize(str)
|
||||
# 'using_namespace Animal Reptile'
|
||||
parts = str.split(/\s+/)
|
||||
raise "Namespace definitions must begin with 'using_namespace'" unless parts[0] == 'using_namespace'
|
||||
parts.shift
|
||||
|
||||
if parts.length > 0 and parts[0] =~ /::/
|
||||
parts = parts[0].split(/::/)
|
||||
end
|
||||
|
||||
raise NamespaceError, "Namespace needs to indicate a module" if parts.empty?
|
||||
|
||||
@module_nest = parts
|
||||
end
|
||||
|
||||
def build_classname(name)
|
||||
[ @module_nest, Infl.camelize(name) ].flatten.join("::")
|
||||
end
|
||||
end
|
||||
|
||||
class Lookup #:nodoc:
|
||||
attr_reader :name
|
||||
def initialize(obj_name)
|
||||
@name = obj_name
|
||||
end
|
||||
end
|
||||
|
||||
class MethodDef #:nodoc:
|
||||
attr_accessor :name, :object, :method, :attach
|
||||
|
||||
def initialize(opts)
|
||||
@name, @object, @method, @attach = opts[:name], opts[:object], opts[:method], opts[:attach]
|
||||
end
|
||||
end
|
||||
|
||||
class ObjectDef #:nodoc:
|
||||
attr_accessor :name, :class_name, :library, :components
|
||||
def initialize(opts)
|
||||
name = opts[:name]
|
||||
raise "Can't make an ObjectDef without a name" if name.nil?
|
||||
|
||||
info = opts[:info] || {}
|
||||
info = info.clone
|
||||
|
||||
@components = {}
|
||||
|
||||
# Object name
|
||||
@name = name
|
||||
|
||||
# Class name
|
||||
@class_name = info.delete 'class'
|
||||
@class_name ||= info.delete 'type'
|
||||
@class_name ||= Infl.camelize(@name)
|
||||
|
||||
# Auto Require
|
||||
@auto_require = info.delete 'auto_require'
|
||||
|
||||
# Library
|
||||
@library = info.delete 'library'
|
||||
@library ||= info.delete 'lib'
|
||||
@library ||= Infl.underscore(@class_name) if @auto_require
|
||||
|
||||
# Use Class Directly
|
||||
@use_class_directly = info.delete 'use_class_directly'
|
||||
|
||||
# Auto-compose
|
||||
compose = info.delete 'compose'
|
||||
if compose
|
||||
case compose
|
||||
when Array
|
||||
auto_names = compose.map { |x| x.to_s }
|
||||
when String
|
||||
auto_names = compose.split(',').map { |x| x.to_s.strip }
|
||||
when Symbol
|
||||
auto_names = [ compose.to_s ]
|
||||
else
|
||||
raise "Cannot auto compose object #{@name}, bad 'compose' format: #{compose.inspect}"
|
||||
end
|
||||
end
|
||||
auto_names ||= []
|
||||
auto_names.each do |cname|
|
||||
@components[cname] = Lookup.new(cname)
|
||||
end
|
||||
|
||||
# Singleton status
|
||||
if info['singleton'].nil?
|
||||
@singleton = true
|
||||
else
|
||||
@singleton = info['singleton']
|
||||
end
|
||||
info.delete 'singleton'
|
||||
|
||||
# Remaining keys
|
||||
info.each do |key,val|
|
||||
@components[key.to_s] = Lookup.new(val.to_s)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def singleton?
|
||||
@singleton
|
||||
end
|
||||
|
||||
def use_class_directly?
|
||||
@use_class_directly == true
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class ConstructionError < RuntimeError #:nodoc:#
|
||||
def initialize(object_name, cause=nil)
|
||||
object_name = object_name
|
||||
cause = cause
|
||||
m = "Failed to construct '#{object_name}'"
|
||||
if cause
|
||||
m << "\n ...caused by:\n >>> #{cause}"
|
||||
end
|
||||
super m
|
||||
end
|
||||
end
|
||||
|
||||
class NamespaceError < RuntimeError #:nodoc:#
|
||||
end
|
||||
|
||||
module Infl #:nodoc:#
|
||||
# Ganked this from Inflector:
|
||||
def self.camelize(lower_case_and_underscored_word)
|
||||
lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
|
||||
end
|
||||
# Ganked this from Inflector:
|
||||
def self.underscore(camel_cased_word)
|
||||
camel_cased_word.to_s.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z])/,'\1_\2').gsub(/([a-z\d])([A-Z])/,'\1_\2').downcase
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,36 @@
|
||||
module DIY #:nodoc:#
|
||||
class FactoryDef #:nodoc:
|
||||
attr_accessor :name, :target, :class_name, :library
|
||||
|
||||
def initialize(opts)
|
||||
@name, @target, @library, @auto_require =
|
||||
opts[:name], opts[:target], opts[:library], opts[:auto_require]
|
||||
|
||||
@class_name = Infl.camelize(@target)
|
||||
@library ||= Infl.underscore(@class_name) if @auto_require
|
||||
end
|
||||
end
|
||||
|
||||
class Context
|
||||
def construct_factory(key)
|
||||
factory_def = @defs[key]
|
||||
# puts "requiring #{factory_def.library}"
|
||||
require factory_def.library if factory_def.library
|
||||
|
||||
big_c = get_class_for_name_with_module_delimeters(factory_def.class_name)
|
||||
|
||||
FactoryFactory.new(big_c)
|
||||
end
|
||||
end
|
||||
|
||||
class FactoryFactory
|
||||
def initialize(clazz)
|
||||
@class_to_create = clazz
|
||||
end
|
||||
|
||||
def create(*args)
|
||||
@class_to_create.new(*args)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user