require 'thread' require 'sync' module DSLKit require 'dslkit/version' # This module contains some handy methods to deal with eigenclasses. Those # are also known as virtual classes, singleton classes, metaclasses, plus all # the other names Matz doesn't like enough to actually accept one of the # names. # # The module can be included into other modules/classes to make the methods available. module Eigenclass # Returns the eigenclass of this object. def eigenclass class << self; self; end end # Evaluates the _block_ in context of the eigenclass of this object. def eigenclass_eval(&block) eigenclass.instance_eval(&block) end end module ClassMethod include Eigenclass # Define a class method named _name_ using _block_. To be able to take # blocks as arguments in the given _block_ Ruby 1.9 is required. def class_define_method(name, &block) eigenclass_eval { define_method(name, &block) } end # Define reader and writer attribute methods for all *ids. def class_attr_accessor(*ids) eigenclass_eval { attr_accessor(*ids) } end # Define reader attribute methods for all *ids. def class_attr_reader(*ids) eigenclass_eval { attr_reader(*ids) } end # Define writer attribute methods for all *ids. def class_attr_writer(*ids) eigenclass_eval { attr_writer(*ids) } end # I boycott attr! end module ThreadLocal @@mutex = Mutex.new @@cleanup = lambda do |my_object_id| my_id = "__thread_local_#{my_object_id}__" @@mutex.synchronize do for t in Thread.list t[my_id] = nil if t[my_id] end end end # Define a thread local variable named _name_ in this module/class. If the # value _value_ is given, it is used to initialize the variable. def thread_local(name, default_value = nil) is_a?(Module) or raise TypeError, "receiver has to be a Module" name = name.to_s my_id = "__thread_local_#{__id__}__" ObjectSpace.define_finalizer(self, @@cleanup) define_method(name) do Thread.current[my_id] ||= {} Thread.current[my_id][name] end define_method("#{name}=") do |value| Thread.current[my_id] ||= {} Thread.current[my_id][name] = value end if default_value Thread.current[my_id] = {} Thread.current[my_id][name] = default_value end self end # Define a thread local variable for the current instance with name _name_. # If the value _value_ is given, it is used to initialize the variable. def instance_thread_local(name, value = nil) sc = class << self extend DSLKit::ThreadLocal self end sc.thread_local name, value self end end module ThreadGlobal # Define a thread global variable named _name_ in this module/class. If the # value _value_ is given, it is used to initialize the variable. def thread_global(name, default_value = nil) is_a?(Module) or raise TypeError, "receiver has to be a Module" name = name.to_s var_name = "@__#{name}_#{__id__.abs}__" lock = Mutex.new modul = self define_method(name) do lock.synchronize { modul.instance_variable_get var_name } end define_method(name + "=") do |value| lock.synchronize { modul.instance_variable_set var_name, value } end modul.instance_variable_set var_name, default_value if default_value self end # Define a thread global variable for the current instance with name # _name_. If the value _value_ is given, it is used to initialize the # variable. def instance_thread_global(name, value = nil) sc = class << self extend DSLKit::ThreadGlobal self end sc.thread_global name, value self end end module InstanceExec unless (Object.instance_method(:instance_exec) rescue nil) class << self attr_accessor :pool attr_accessor :count end self.count = 0 self.pool = [] # This is a pure ruby implementation of Ruby 1.9's instance_exec method. It # executes _block_ in the context of this object while parsing *args into # the block. def instance_exec(*args, &block) instance = self id = instance_exec_fetch_symbol InstanceExec.module_eval do begin define_method id, block instance.__send__ id, *args ensure remove_method id if method_defined?(id) end end ensure InstanceExec.pool << id end private @@mutex = Mutex.new # Fetch a symbol from a pool in thread save way. If no more symbols are # available create a new one, that will be pushed into the pool later. def instance_exec_fetch_symbol @@mutex.synchronize do if InstanceExec.pool.empty? InstanceExec.count += 1 symbol = :"__instance_exec_#{InstanceExec.count}__" else symbol = InstanceExec.pool.shift end return symbol end end end end module Interpreter include InstanceExec # Interpret the string _source_ as a body of a block, while passing # *args into the block. # # A small example explains how the method is supposed to be used and how # the *args can be fetched: # # class A # include DSLKit::Interpreter # def c # 3 # end # end # # A.new.interpret('|a,b| a + b + c', 1, 2) # => 6 # # To use a specified binding see #interpret_with_binding. def interpret(source, *args) interpret_with_binding(source, binding, *args) end # Interpret the string _source_ as a body of a block, while passing # *args into the block and using _my_binding_ for evaluation. # # A small example: # # class A # include DSLKit::Interpreter # def c # 3 # end # def foo # b = 2 # interpret_with_binding('|a| a + b + c', binding, 1) # => 6 # end # end # A.new.foo # => 6 # # See also #interpret. def interpret_with_binding(source, my_binding, *args) path = '(interpret)' if source.respond_to? :to_io path = source.path if source.respond_to? :path source = source.to_io.read end block = lambda { |*a| eval("lambda { #{source} }", my_binding, path).call(*a) } instance_exec(*args, &block) end end # This module contains the _constant_ method. For small example of its usage # see the documentation of the DSLAccessor module. module Constant # Create a constant named _name_, that refers to value _value_. _value is # frozen, if this is possible. If you want to modify/exchange a value use # DSLAccessor#dsl_reader/DSLAccessor#dsl_accessor instead. def constant(name, value = name) value = value.freeze rescue value define_method(name) { value } end end # The DSLAccessor module contains some methods, that can be used to make # simple accessors for a DSL. # # # class CoffeeMaker # extend DSLKit::Constant # # constant :on # constant :off # # extend DSLKit::DSLAccessor # # dsl_accessor(:state) { off } # Note: the off constant from above is used # # dsl_accessor :allowed_states, :on, :off # # def process # allowed_states.include?(state) or fail "Explode!!!" # if state == on # puts "Make coffee." # else # puts "Idle..." # end # end # end # # cm = CoffeeMaker.new # cm.instance_eval do # state # => :off # state on # state # => :on # process # => outputs "Make coffee." # end # # Note that DSLKit::SymbolMaker is an alternative for DSLKit::Constant in # this example. On the other hand SymbolMaker can make debugging more # difficult. module DSLAccessor # This method creates a dsl accessor named _name_. If nothing else is given # as argument it defaults to nil. If *default is given as a single # value it is used as a default value, if more than one value is given the # _default_ array is used as the default value. If no default value but a # block _block_ is given as an argument, the block is executed everytime # the accessor is read in the context of the current instance. # # After setting up the accessor, the set or default value can be retrieved # by calling the method +name+. To set a value one can call name # :foo to set the attribute value to :foo or # name(:foo, :bar) to set it to [ :foo, :bar ]. def dsl_accessor(name, *default, &block) variable = "@#{name}" define_method(name) do |*args| if args.empty? result = instance_variable_get(variable) if result.nil? result = if default.empty? block && instance_eval(&block) elsif default.size == 1 default.first else default end instance_variable_set(variable, result) result else result end else instance_variable_set(variable, args.size == 1 ? args.first : args) end end end # This method creates a dsl reader accessor, that behaves exactly like a # #dsl_accessor but can only be read not set. def dsl_reader(name, *default, &block) variable = "@#{name}" define_method(name) do |*args| if args.empty? result = instance_variable_get(variable) if result.nil? if default.empty? block && instance_eval(&block) elsif default.size == 1 default.first else default end else result end else raise ArgumentError, "wrong number of arguments (#{args.size} for 0)" end end end end # This module can be included in another module/class. It generates a symbol # for every missing method that was called in the context of this # module/class. module SymbolMaker # Returns a symbol (_id_) for every missing method named _id_. def method_missing(id, *args) if args.empty? id else super end end end # This module can be used to extend another module/class. It generates # symbols for every missing constant under the namespace of this # module/class. module ConstantMaker # Returns a symbol (_id_) for every missing constant named _id_. def const_missing(id) id end end module BlankSlate # Creates an anonymous blank slate class, that only responds to the methods # *ids. ids can be Symbols, Strings, and Regexps that have to match # the method name with #===. def self.with(*ids) ids = ids.map { |id| Regexp === id ? id : id.to_s } klass = Class.new klass.instance_eval do instance_methods.each do |m| m = m.to_s undef_method m unless m =~ /^(__|object_id)/ or ids.any? { |i| i === m } end end klass end end # See examples/recipe.rb and examples/recipe2.rb how this works at the # moment. module Deflect # The basic Deflect exception class DeflectError < StandardError; end class << self extend DSLKit::ThreadLocal # A thread local variable, that holds a DeflectorCollection instance for # the current thread. thread_local :deflecting end # A deflector is called with a _class_, a method _id_, and its # *args. class Deflector < Proc; end # This class implements a collection of deflectors, to make them available # by emulating Ruby's message dispatch. class DeflectorCollection def initialize @classes = {} end # Add a new deflector _deflector_ for class _klass_ and method name _id_, # and return self. # def add(klass, id, deflector) k = @classes[klass] k = @classes[klass] = {} unless k k[id.to_s] = deflector self end # Return true if messages are deflected for class _klass_ and method name # _id_, otherwise return false. def member?(klass, id) !!(k = @classes[klass] and k.key?(id.to_s)) end # Delete the deflecotor class _klass_ and method name _id_. Returns the # deflector if any was found, otherwise returns true. def delete(klass, id) if k = @classes[klass] d = k.delete id.to_s @classes.delete klass if k.empty? d end end # Try to find a deflector for class _klass_ and method _id_ and return # it. If none was found, return nil instead. def find(klass, id) klass.ancestors.find do |k| if d = @classes[k] and d = d[id.to_s] return d end end end end @@sync = Sync.new # Start deflecting method calls named _id_ to the _from_ class using the # Deflector instance deflector. def deflect_start(from, id, deflector) @@sync.synchronize do Deflect.deflecting ||= DeflectorCollection.new Deflect.deflecting.member?(from, id) and raise DeflectError, "#{from}##{id} is already deflected" Deflect.deflecting.add(from, id, deflector) from.class_eval do define_method(id) do |*args| if Deflect.deflecting and d = Deflect.deflecting.find(self.class, id) d.call(self, id, *args) else super(*args) end end end end end # Return true if method _id_ is deflected from class _from_, otherwise # return false. def self.deflect?(from, id) Deflect.deflecting && Deflect.deflecting.member?(from, id) end # Return true if method _id_ is deflected from class _from_, otherwise # return false. def deflect?(from, id) Deflect.deflect?(from, id) end # Start deflecting method calls named _id_ to the _from_ class using the # Deflector instance deflector. After that yield to the given block and # stop deflecting again. def deflect(from, id, deflector) @@sync.synchronize do begin deflect_start(from, id, deflector) yield ensure deflect_stop(from, id) end end end # Stop deflection method calls named _id_ to class _from_. def deflect_stop(from, id) @@sync.synchronize do Deflect.deflecting.delete(from, id) or raise DeflectError, "#{from}##{id} is not deflected from" from.instance_eval { remove_method id } end end end # This module can be included into modules/classes to make the delegate # method available. module Delegate # A method to easily delegate methods to an object, stored in an # instance variable or returned by a method call. # # It's used like this: # class A # delegate :method_here, :@obj, :method_there # end # or: # class A # delegate :method_here, :method_call, :method_there # end # # _other_method_name_ defaults to method_name, if it wasn't given. def delegate(method_name, obj, other_method_name = method_name) raise ArgumentError, "obj wasn't defined" unless obj =begin 1.9 only: define_method(method_name) do |*args, &block| instance_variable_get(obj).__send__(other_method_name, *args, &block) end =end obj = obj.to_s if obj[0] == ?@ class_eval <<-EOS def #{method_name}(*args, &block) instance_variable_get('#{obj}').__send__( '#{other_method_name}', *args, &block) end EOS else class_eval <<-EOS def #{method_name}(*args, &block) __send__('#{obj}').__send__( '#{other_method_name}', *args, &block) end EOS end end end # This module includes the block_self module_function. module BlockSelf module_function # This method returns the receiver _self_ of the context in which _block_ # was created. def block_self(&block) eval 'self', block.__send__(:binding) end end # This module contains a configurable method missing delegator and can be # mixed into a module/class. module MethodMissingDelegator # Including this module in your classes makes an _initialize_ method # available, whose first argument is used as method_missing_delegator # attribute. If a superior _initialize_ method was defined it is called # with all arguments but the first. module DelegatorModule include DSLKit::MethodMissingDelegator def initialize(delegator, *a, &b) self.method_missing_delegator = delegator super(*a, &b) if defined? super end end # This class includes DelegatorModule and can be used as a superclass # instead of including DelegatorModule. class DelegatorClass include DelegatorModule end # This object will be the receiver of all missing method calls, if it has a # value other than nil. attr_accessor :method_missing_delegator # Delegates all missing method calls to _method_missing_delegator_ if this # attribute has been set. Otherwise it will call super. def method_missing(id, *a, &b) unless method_missing_delegator.nil? method_missing_delegator.__send__(id, *a, &b) else super end end end module ParameterizedModule # Pass _args_ and _block_ to configure the module and then return it after # calling the parameterize method has been called with these arguments. The # _parameterize_ method should return a configured module. def parameterize_for(*args, &block) respond_to?(:parameterize) ? parameterize(*args, &block) : self end end module FromModule include ParameterizedModule alias from parameterize_for def parameterize(opts = {}) modul = opts[:module] or raise ArgumentError, 'option :module is required' import_methods = Array(opts[:methods]) result = modul.dup remove_methods = modul.instance_methods.map(&:to_sym) - import_methods.map(&:to_sym) remove_methods.each do |m| begin result.__send__ :remove_method, m rescue NameError end end result end end end