From b30561ca49ed3ce6f924ff0a78341d8c66c77c34 Mon Sep 17 00:00:00 2001 From: Kane Baccigalupi Date: Sun, 22 Nov 2009 18:21:14 -0800 Subject: [PATCH] temp add in of active supports experimental callbacks ... until Rails 3 is out --- lib/aqua/support/active_support/array.rb | 27 + lib/aqua/support/active_support/callbacks.rb | 564 +++++++++++++++++++ lib/aqua/support/active_support/class.rb | 228 ++++++++ lib/aqua/support/active_support/concern.rb | 29 + lib/aqua/support/active_support/kernel.rb | 61 ++ lib/aqua/support/active_support/object.rb | 49 ++ 6 files changed, 958 insertions(+) create mode 100644 lib/aqua/support/active_support/array.rb create mode 100644 lib/aqua/support/active_support/callbacks.rb create mode 100644 lib/aqua/support/active_support/class.rb create mode 100644 lib/aqua/support/active_support/concern.rb create mode 100644 lib/aqua/support/active_support/kernel.rb create mode 100644 lib/aqua/support/active_support/object.rb diff --git a/lib/aqua/support/active_support/array.rb b/lib/aqua/support/active_support/array.rb new file mode 100644 index 0000000..07c059c --- /dev/null +++ b/lib/aqua/support/active_support/array.rb @@ -0,0 +1,27 @@ +class Array + # Wraps the object in an Array unless it's an Array. Converts the + # object to an Array using #to_ary if it implements that. + def self.wrap(object) + if object.nil? + [] + elsif object.respond_to?(:to_ary) + object.to_ary + else + [object] + end + end + + # Extracts options from a set of arguments. Removes and returns the last + # element in the array if it's a hash, otherwise returns a blank hash. + # + # def options(*args) + # args.extract_options! + # end + # + # options(1, 2) # => {} + # options(1, 2, :a => :b) # => {:a=>:b} + def extract_options! + last.is_a?(::Hash) ? pop : {} + end + +end diff --git a/lib/aqua/support/active_support/callbacks.rb b/lib/aqua/support/active_support/callbacks.rb new file mode 100644 index 0000000..9f43ab8 --- /dev/null +++ b/lib/aqua/support/active_support/callbacks.rb @@ -0,0 +1,564 @@ +# This is stolen from the experimental branch of Rails 3. When the callbacks in the released +# version are like this then the library will require active_support/callbacks, and be done! +require File.dirname(__FILE__) + '/array' +require File.dirname(__FILE__) + '/class' +require File.dirname(__FILE__) + '/kernel' +require File.dirname(__FILE__) + '/concern' + +module ActiveSupport + # Callbacks are hooks into the lifecycle of an object that allow you to trigger logic + # before or after an alteration of the object state. + # + # Mixing in this module allows you to define callbacks in your class. + # + # Example: + # class Storage + # include ActiveSupport::Callbacks + # + # define_callbacks :save + # end + # + # class ConfigStorage < Storage + # set_callback :save, :before, :saving_message + # def saving_message + # puts "saving..." + # end + # + # set_callback :save, :after do |object| + # puts "saved" + # end + # + # def save + # run_callbacks :save do + # puts "- save" + # end + # end + # end + # + # config = ConfigStorage.new + # config.save + # + # Output: + # saving... + # - save + # saved + # + # Callbacks from parent classes are inherited. + # + # Example: + # class Storage + # include ActiveSupport::Callbacks + # + # define_callbacks :save + # + # set_callback :save, :before, :prepare + # def prepare + # puts "preparing save" + # end + # end + # + # class ConfigStorage < Storage + # set_callback :save, :before, :saving_message + # def saving_message + # puts "saving..." + # end + # + # set_callback :save, :after do |object| + # puts "saved" + # end + # + # def save + # run_callbacks :save do + # puts "- save" + # end + # end + # end + # + # config = ConfigStorage.new + # config.save + # + # Output: + # preparing save + # saving... + # - save + # saved + # + module Callbacks + extend Concern + + def run_callbacks(kind, *args, &block) + send("_run_#{kind}_callbacks", *args, &block) + end + + class Callback + @@_callback_sequence = 0 + + attr_accessor :chain, :filter, :kind, :options, :per_key, :klass + + def initialize(chain, filter, kind, options, klass) + @chain, @kind, @klass = chain, kind, klass + normalize_options!(options) + + @per_key = options.delete(:per_key) + @raw_filter, @options = filter, options + @filter = _compile_filter(filter) + @compiled_options = _compile_options(options) + @callback_id = next_id + + _compile_per_key_options + end + + def clone(chain, klass) + obj = super() + obj.chain = chain + obj.klass = klass + obj.per_key = @per_key.dup + obj.options = @options.dup + obj.per_key[:if] = @per_key[:if].dup + obj.per_key[:unless] = @per_key[:unless].dup + obj.options[:if] = @options[:if].dup + obj.options[:unless] = @options[:unless].dup + obj + end + + def normalize_options!(options) + options[:if] = Array.wrap(options[:if]) + options[:unless] = Array.wrap(options[:unless]) + + options[:per_key] ||= {} + options[:per_key][:if] = Array.wrap(options[:per_key][:if]) + options[:per_key][:unless] = Array.wrap(options[:per_key][:unless]) + end + + def name + chain.name + end + + def next_id + @@_callback_sequence += 1 + end + + def matches?(_kind, _filter) + @kind == _kind && @filter == _filter + end + + def _update_filter(filter_options, new_options) + filter_options[:if].push(new_options[:unless]) if new_options.key?(:unless) + filter_options[:unless].push(new_options[:if]) if new_options.key?(:if) + end + + def recompile!(_options, _per_key) + _update_filter(self.options, _options) + _update_filter(self.per_key, _per_key) + + @callback_id = next_id + @filter = _compile_filter(@raw_filter) + @compiled_options = _compile_options(@options) + _compile_per_key_options + end + + def _compile_per_key_options + key_options = _compile_options(@per_key) + + @klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 + def _one_time_conditions_valid_#{@callback_id}? + true #{key_options[0]} + end + RUBY_EVAL + end + + # This will supply contents for before and around filters, and no + # contents for after filters (for the forward pass). + def start(key=nil, object=nil) + return if key && !object.send("_one_time_conditions_valid_#{@callback_id}?") + + # options[0] is the compiled form of supplied conditions + # options[1] is the "end" for the conditional + # + if @kind == :before || @kind == :around + if @kind == :before + # if condition # before_save :filter_name, :if => :condition + # filter_name + # end + filter = <<-RUBY_EVAL + unless halted + result = #{@filter} + halted = (#{chain.config[:terminator]}) + end + RUBY_EVAL + + [@compiled_options[0], filter, @compiled_options[1]].compact.join("\n") + else + # Compile around filters with conditions into proxy methods + # that contain the conditions. + # + # For `around_save :filter_name, :if => :condition': + # + # def _conditional_callback_save_17 + # if condition + # filter_name do + # yield self + # end + # else + # yield self + # end + # end + # + name = "_conditional_callback_#{@kind}_#{next_id}" + txt, line = <<-RUBY_EVAL, __LINE__ + 1 + def #{name}(halted) + #{@compiled_options[0] || "if true"} && !halted + #{@filter} do + yield self + end + else + yield self + end + end + RUBY_EVAL + @klass.class_eval(txt, __FILE__, line) + "#{name}(halted) do" + end + end + end + + # This will supply contents for around and after filters, but not + # before filters (for the backward pass). + def end(key=nil, object=nil) + return if key && !object.send("_one_time_conditions_valid_#{@callback_id}?") + + if @kind == :around || @kind == :after + # if condition # after_save :filter_name, :if => :condition + # filter_name + # end + if @kind == :after + [@compiled_options[0], @filter, @compiled_options[1]].compact.join("\n") + else + "end" + end + end + end + + private + + # Options support the same options as filters themselves (and support + # symbols, string, procs, and objects), so compile a conditional + # expression based on the options + def _compile_options(options) + return [] if options[:if].empty? && options[:unless].empty? + + conditions = [] + + unless options[:if].empty? + conditions << Array.wrap(_compile_filter(options[:if])) + end + + unless options[:unless].empty? + conditions << Array.wrap(_compile_filter(options[:unless])).map {|f| "!#{f}"} + end + + ["if #{conditions.flatten.join(" && ")}", "end"] + end + + # Filters support: + # + # Arrays:: Used in conditions. This is used to specify + # multiple conditions. Used internally to + # merge conditions from skip_* filters + # Symbols:: A method to call + # Strings:: Some content to evaluate + # Procs:: A proc to call with the object + # Objects:: An object with a before_foo method on it to call + # + # All of these objects are compiled into methods and handled + # the same after this point: + # + # Arrays:: Merged together into a single filter + # Symbols:: Already methods + # Strings:: class_eval'ed into methods + # Procs:: define_method'ed into methods + # Objects:: + # a method is created that calls the before_foo method + # on the object. + # + def _compile_filter(filter) + method_name = "_callback_#{@kind}_#{next_id}" + case filter + when Array + filter.map {|f| _compile_filter(f)} + when Symbol + filter + when String + "(#{filter})" + when Proc + @klass.send(:define_method, method_name, &filter) + return method_name if filter.arity <= 0 + + method_name << (filter.arity == 1 ? "(self)" : " self, Proc.new ") + else + @klass.send(:define_method, "#{method_name}_object") { filter } + + _normalize_legacy_filter(kind, filter) + scopes = Array.wrap(chain.config[:scope]) + method_to_call = scopes.map{ |s| s.is_a?(Symbol) ? send(s) : s }.join("_") + + @klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 + def #{method_name}(&blk) + #{method_name}_object.send(:#{method_to_call}, self, &blk) + end + RUBY_EVAL + + method_name + end + end + + def _normalize_legacy_filter(kind, filter) + if !filter.respond_to?(kind) && filter.respond_to?(:filter) + filter.metaclass.class_eval( + "def #{kind}(context, &block) filter(context, &block) end", + __FILE__, __LINE__ - 1) + elsif filter.respond_to?(:before) && filter.respond_to?(:after) && kind == :around + def filter.around(context) + should_continue = before(context) + yield if should_continue + after(context) + end + end + end + end + + # An Array with a compile method + class CallbackChain < Array + attr_reader :name, :config + + def initialize(name, config) + @name = name + @config = { + :terminator => "false", + :rescuable => false, + :scope => [ :kind ] + }.merge(config) + end + + def compile(key=nil, object=nil) + method = [] + method << "value = nil" + method << "halted = false" + + each do |callback| + method << callback.start(key, object) + end + + if config[:rescuable] + method << "rescued_error = nil" + method << "begin" + end + + method << "value = yield if block_given? && !halted" + + if config[:rescuable] + method << "rescue Exception => e" + method << "rescued_error = e" + method << "end" + end + + reverse_each do |callback| + method << callback.end(key, object) + end + + method << "raise rescued_error if rescued_error" if config[:rescuable] + method << "halted ? false : (block_given? ? value : true)" + method.compact.join("\n") + end + + def clone(klass) + chain = CallbackChain.new(@name, @config.dup) + callbacks = map { |c| c.clone(chain, klass) } + chain.push(*callbacks) + end + end + + module ClassMethods + # Make the run_callbacks :save method. The generated method takes + # a block that it'll yield to. It'll call the before and around filters + # in order, yield the block, and then run the after filters. + # + # run_callbacks :save do + # save + # end + # + # The run_callbacks :save method can optionally take a key, which + # will be used to compile an optimized callback method for each + # key. See #define_callbacks for more information. + # + def __define_runner(symbol) #:nodoc: + body = send("_#{symbol}_callbacks").compile(nil) + + body, line = <<-RUBY_EVAL, __LINE__ + def _run_#{symbol}_callbacks(key = nil, &blk) + if key + name = "_run__\#{self.class.name.hash.abs}__#{symbol}__\#{key.hash.abs}__callbacks" + + unless respond_to?(name) + self.class.__create_keyed_callback(name, :#{symbol}, self, &blk) + end + + send(name, &blk) + else + #{body} + end + end + private :_run_#{symbol}_callbacks + RUBY_EVAL + + silence_warnings do + undef_method "_run_#{symbol}_callbacks" if method_defined?("_run_#{symbol}_callbacks") + class_eval body, __FILE__, line + end + end + + # This is called the first time a callback is called with a particular + # key. It creates a new callback method for the key, calculating + # which callbacks can be omitted because of per_key conditions. + # + def __create_keyed_callback(name, kind, object, &blk) #:nodoc: + @_keyed_callbacks ||= {} + @_keyed_callbacks[name] ||= begin + str = send("_#{kind}_callbacks").compile(name, object) + class_eval "def #{name}() #{str} end", __FILE__, __LINE__ + true + end + end + + # This is used internally to append, prepend and skip callbacks to the + # CallbackChain. + # + def __update_callbacks(name, filters = [], block = nil) #:nodoc: + type = [:before, :after, :around].include?(filters.first) ? filters.shift : :before + options = filters.last.is_a?(Hash) ? filters.pop : {} + filters.unshift(block) if block + + chain = send("_#{name}_callbacks") + yield chain, type, filters, options if block_given? + + __define_runner(name) + end + + # Set callbacks for a previously defined callback. + # + # Syntax: + # set_callback :save, :before, :before_meth + # set_callback :save, :after, :after_meth, :if => :condition + # set_callback :save, :around, lambda { |r| stuff; yield; stuff } + # + # Use skip_callback to skip any defined one. + # + # When creating or skipping callbacks, you can specify conditions that + # are always the same for a given key. For instance, in ActionPack, + # we convert :only and :except conditions into per-key conditions. + # + # before_filter :authenticate, :except => "index" + # becomes + # dispatch_callback :before, :authenticate, :per_key => {:unless => proc {|c| c.action_name == "index"}} + # + # Per-Key conditions are evaluated only once per use of a given key. + # In the case of the above example, you would do: + # + # run_callbacks(:dispatch, action_name) { ... dispatch stuff ... } + # + # In that case, each action_name would get its own compiled callback + # method that took into consideration the per_key conditions. This + # is a speed improvement for ActionPack. + # + def set_callback(name, *filters, &block) + __update_callbacks(name, filters, block) do |chain, type, filters, options| + filters.map! do |filter| + chain.delete_if {|c| c.matches?(type, filter) } + Callback.new(chain, filter, type, options.dup, self) + end + + options[:prepend] ? chain.unshift(*filters) : chain.push(*filters) + end + end + + # Skip a previously defined callback for a given type. + # + def skip_callback(name, *filters, &block) + __update_callbacks(name, filters, block) do |chain, type, filters, options| + chain = send("_#{name}_callbacks=", chain.clone(self)) + + filters.each do |filter| + filter = chain.find {|c| c.matches?(type, filter) } + + if filter && options.any? + filter.recompile!(options, options[:per_key] || {}) + else + chain.delete(filter) + end + end + end + end + + # Reset callbacks for a given type. + # + def reset_callbacks(symbol) + send("_#{symbol}_callbacks").clear + __define_runner(symbol) + end + + # Define callbacks types. + # + # ==== Example + # + # define_callbacks :validate + # + # ==== Options + # + # * :terminator - Indicates when a before filter is considered + # to be halted. + # + # define_callbacks :validate, :terminator => "result == false" + # + # In the example above, if any before validate callbacks returns false, + # other callbacks are not executed. Defaults to "false". + # + # * :rescuable - By default, after filters are not executed if + # the given block or an before_filter raises an error. Supply :rescuable => true + # to change this behavior. + # + # * :scope - Show which methods should be executed when a class + # is giben as callback: + # + # define_callbacks :filters, :scope => [ :kind ] + # + # When a class is given: + # + # before_filter MyFilter + # + # It will call the type of the filter in the given class, which in this + # case, is "before". + # + # If, for instance, you supply the given scope: + # + # define_callbacks :validate, :scope => [ :kind, :name ] + # + # It will call "#{kind}_#{name}" in the given class. So "before_validate" + # will be called in the class below: + # + # before_validate MyValidation + # + # Defaults to :kind. + # + def define_callbacks(*symbols) + config = symbols.last.is_a?(Hash) ? symbols.pop : {} + symbols.each do |symbol| + extlib_inheritable_accessor("_#{symbol}_callbacks") do + CallbackChain.new(symbol, config) + end + + __define_runner(symbol) + end + end + end + end +end diff --git a/lib/aqua/support/active_support/class.rb b/lib/aqua/support/active_support/class.rb new file mode 100644 index 0000000..cbe43be --- /dev/null +++ b/lib/aqua/support/active_support/class.rb @@ -0,0 +1,228 @@ +require File.dirname(__FILE__) + '/array' +require File.dirname(__FILE__) + '/object' + +# Retain for backward compatibility. Methods are now included in Class. +module ClassInheritableAttributes # :nodoc: +end + +# Allows attributes to be shared within an inheritance hierarchy, but where each descendant gets a copy of +# their parents' attributes, instead of just a pointer to the same. This means that the child can add elements +# to, for example, an array without those additions being shared with either their parent, siblings, or +# children, which is unlike the regular class-level attributes that are shared across the entire hierarchy. +class Class # :nodoc: + def class_inheritable_reader(*syms) + options = syms.extract_options! + syms.each do |sym| + next if sym.is_a?(Hash) + class_eval(<<-EOS, __FILE__, __LINE__ + 1) + def self.#{sym} # def self.after_add + read_inheritable_attribute(:#{sym}) # read_inheritable_attribute(:after_add) + end # end + # + #{" # + def #{sym} # def after_add + self.class.#{sym} # self.class.after_add + end # end + " unless options[:instance_reader] == false } # # the reader above is generated unless options[:instance_reader] == false + EOS + end + end + + def class_inheritable_writer(*syms) + options = syms.extract_options! + syms.each do |sym| + class_eval(<<-EOS, __FILE__, __LINE__ + 1) + def self.#{sym}=(obj) # def self.color=(obj) + write_inheritable_attribute(:#{sym}, obj) # write_inheritable_attribute(:color, obj) + end # end + # + #{" # + def #{sym}=(obj) # def color=(obj) + self.class.#{sym} = obj # self.class.color = obj + end # end + " unless options[:instance_writer] == false } # # the writer above is generated unless options[:instance_writer] == false + EOS + end + end + + def class_inheritable_array_writer(*syms) + options = syms.extract_options! + syms.each do |sym| + class_eval(<<-EOS, __FILE__, __LINE__ + 1) + def self.#{sym}=(obj) # def self.levels=(obj) + write_inheritable_array(:#{sym}, obj) # write_inheritable_array(:levels, obj) + end # end + # + #{" # + def #{sym}=(obj) # def levels=(obj) + self.class.#{sym} = obj # self.class.levels = obj + end # end + " unless options[:instance_writer] == false } # # the writer above is generated unless options[:instance_writer] == false + EOS + end + end + + def class_inheritable_hash_writer(*syms) + options = syms.extract_options! + syms.each do |sym| + class_eval(<<-EOS, __FILE__, __LINE__ + 1) + def self.#{sym}=(obj) # def self.nicknames=(obj) + write_inheritable_hash(:#{sym}, obj) # write_inheritable_hash(:nicknames, obj) + end # end + # + #{" # + def #{sym}=(obj) # def nicknames=(obj) + self.class.#{sym} = obj # self.class.nicknames = obj + end # end + " unless options[:instance_writer] == false } # # the writer above is generated unless options[:instance_writer] == false + EOS + end + end + + def class_inheritable_accessor(*syms) + class_inheritable_reader(*syms) + class_inheritable_writer(*syms) + end + + def class_inheritable_array(*syms) + class_inheritable_reader(*syms) + class_inheritable_array_writer(*syms) + end + + def class_inheritable_hash(*syms) + class_inheritable_reader(*syms) + class_inheritable_hash_writer(*syms) + end + + def inheritable_attributes + @inheritable_attributes ||= EMPTY_INHERITABLE_ATTRIBUTES + end + + def write_inheritable_attribute(key, value) + if inheritable_attributes.equal?(EMPTY_INHERITABLE_ATTRIBUTES) + @inheritable_attributes = {} + end + inheritable_attributes[key] = value + end + + def write_inheritable_array(key, elements) + write_inheritable_attribute(key, []) if read_inheritable_attribute(key).nil? + write_inheritable_attribute(key, read_inheritable_attribute(key) + elements) + end + + def write_inheritable_hash(key, hash) + write_inheritable_attribute(key, {}) if read_inheritable_attribute(key).nil? + write_inheritable_attribute(key, read_inheritable_attribute(key).merge(hash)) + end + + def read_inheritable_attribute(key) + inheritable_attributes[key] + end + + def reset_inheritable_attributes + @inheritable_attributes = EMPTY_INHERITABLE_ATTRIBUTES + end + + private + # Prevent this constant from being created multiple times + EMPTY_INHERITABLE_ATTRIBUTES = {}.freeze unless const_defined?(:EMPTY_INHERITABLE_ATTRIBUTES) + + def inherited_with_inheritable_attributes(child) + inherited_without_inheritable_attributes(child) if respond_to?(:inherited_without_inheritable_attributes) + + if inheritable_attributes.equal?(EMPTY_INHERITABLE_ATTRIBUTES) + new_inheritable_attributes = EMPTY_INHERITABLE_ATTRIBUTES + else + new_inheritable_attributes = inheritable_attributes.inject({}) do |memo, (key, value)| + memo.update(key => value.duplicable? ? value.dup : value) + end + end + + child.instance_variable_set('@inheritable_attributes', new_inheritable_attributes) + end + + alias inherited_without_inheritable_attributes inherited + alias inherited inherited_with_inheritable_attributes +end + +class Class + # Defines class-level inheritable attribute reader. Attributes are available to subclasses, + # each subclass has a copy of parent's attribute. + # + # @param *syms Array of attributes to define inheritable reader for. + # @return Array of attributes converted into inheritable_readers. + # + # @api public + # + # @todo Do we want to block instance_reader via :instance_reader => false + # @todo It would be preferable that we do something with a Hash passed in + # (error out or do the same as other methods above) instead of silently + # moving on). In particular, this makes the return value of this function + # less useful. + def extlib_inheritable_reader(*ivars) + options = ivars.extract_options! + + ivars.each do |ivar| + self.class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def self.#{ivar} + return @#{ivar} if self.object_id == #{self.object_id} || defined?(@#{ivar}) + ivar = superclass.#{ivar} + return nil if ivar.nil? && !#{self}.instance_variable_defined?("@#{ivar}") + @#{ivar} = ivar.duplicable? ? ivar.dup : ivar + end + RUBY + unless options[:instance_reader] == false + self.class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{ivar} + self.class.#{ivar} + end + RUBY + end + end + end + + # Defines class-level inheritable attribute writer. Attributes are available to subclasses, + # each subclass has a copy of parent's attribute. + # + # @param *syms Boolean}]> Array of attributes to + # define inheritable writer for. + # @option syms :instance_writer if true, instance-level inheritable attribute writer is defined. + # @return An Array of the attributes that were made into inheritable writers. + # + # @api public + # + # @todo We need a style for class_eval <<-HEREDOC. I'd like to make it + # class_eval(<<-RUBY, __FILE__, __LINE__), but we should codify it somewhere. + def extlib_inheritable_writer(*ivars) + options = ivars.extract_options! + + ivars.each do |ivar| + self.class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def self.#{ivar}=(obj) + @#{ivar} = obj + end + RUBY + unless options[:instance_writer] == false + self.class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{ivar}=(obj) self.class.#{ivar} = obj end + RUBY + end + + self.send("#{ivar}=", yield) if block_given? + end + end + + # Defines class-level inheritable attribute accessor. Attributes are available to subclasses, + # each subclass has a copy of parent's attribute. + # + # @param *syms Boolean}]> Array of attributes to + # define inheritable accessor for. + # @option syms :instance_writer if true, instance-level inheritable attribute writer is defined. + # @return An Array of attributes turned into inheritable accessors. + # + # @api public + def extlib_inheritable_accessor(*syms, &block) + extlib_inheritable_reader(*syms) + extlib_inheritable_writer(*syms, &block) + end +end diff --git a/lib/aqua/support/active_support/concern.rb b/lib/aqua/support/active_support/concern.rb new file mode 100644 index 0000000..eb31f7c --- /dev/null +++ b/lib/aqua/support/active_support/concern.rb @@ -0,0 +1,29 @@ +module ActiveSupport + module Concern + def self.extended(base) + base.instance_variable_set("@_dependencies", []) + end + + def append_features(base) + if base.instance_variable_defined?("@_dependencies") + base.instance_variable_get("@_dependencies") << self + return false + else + return false if base < self + @_dependencies.each { |dep| base.send(:include, dep) } + super + base.extend const_get("ClassMethods") if const_defined?("ClassMethods") + base.send :include, const_get("InstanceMethods") if const_defined?("InstanceMethods") + base.class_eval(&@_included_block) if instance_variable_defined?("@_included_block") + end + end + + def included(base = nil, &block) + if base.nil? + @_included_block = block + else + super + end + end + end +end diff --git a/lib/aqua/support/active_support/kernel.rb b/lib/aqua/support/active_support/kernel.rb new file mode 100644 index 0000000..d9b84e6 --- /dev/null +++ b/lib/aqua/support/active_support/kernel.rb @@ -0,0 +1,61 @@ +module Kernel + # Sets $VERBOSE to nil for the duration of the block and back to its original value afterwards. + # + # silence_warnings do + # value = noisy_call # no warning voiced + # end + # + # noisy_call # warning voiced + def silence_warnings + with_warnings(nil) { yield } + end + + # Sets $VERBOSE to true for the duration of the block and back to its original value afterwards. + def enable_warnings + with_warnings(true) { yield } + end + + # Sets $VERBOSE for the duration of the block and back to its original value afterwards. + def with_warnings(flag) + old_verbose, $VERBOSE = $VERBOSE, flag + yield + ensure + $VERBOSE = old_verbose + end + + # For compatibility + def silence_stderr #:nodoc: + silence_stream(STDERR) { yield } + end + + # Silences any stream for the duration of the block. + # + # silence_stream(STDOUT) do + # puts 'This will never be seen' + # end + # + # puts 'But this will' + def silence_stream(stream) + old_stream = stream.dup + stream.reopen(RUBY_PLATFORM =~ /mswin/ ? 'NUL:' : '/dev/null') + stream.sync = true + yield + ensure + stream.reopen(old_stream) + end + + # Blocks and ignores any exception passed as argument if raised within the block. + # + # suppress(ZeroDivisionError) do + # 1/0 + # puts "This code is NOT reached" + # end + # + # puts "This code gets executed and nothing related to ZeroDivisionError was seen" + def suppress(*exception_classes) + begin yield + rescue Exception => e + raise unless exception_classes.any? { |cls| e.kind_of?(cls) } + end + end +end diff --git a/lib/aqua/support/active_support/object.rb b/lib/aqua/support/active_support/object.rb new file mode 100644 index 0000000..a2d4d50 --- /dev/null +++ b/lib/aqua/support/active_support/object.rb @@ -0,0 +1,49 @@ +class Object + # Can you safely .dup this object? + # False for nil, false, true, symbols, numbers, class and module objects; true otherwise. + def duplicable? + true + end +end + +class NilClass #:nodoc: + def duplicable? + false + end +end + +class FalseClass #:nodoc: + def duplicable? + false + end +end + +class TrueClass #:nodoc: + def duplicable? + false + end +end + +class Symbol #:nodoc: + def duplicable? + false + end +end + +class Numeric #:nodoc: + def duplicable? + false + end +end + +class Class #:nodoc: + def duplicable? + false + end +end + +class Module #:nodoc: + def duplicable? + false + end +end