require "ripar/version" require 'ripar/combinder.rb' # Pass in the original chainable object # method calls in block will be applied # resulting value will be in riven class Ripar::Roller < BasicObject def initialize( original, &block ) @original = original # clone is for protection or original - not strictly necessary? @riven = original.clone roll_block( &block ) if block end # Callback from Combinder. # # This happens when the outside self is_a?(original.class) already, # and inside is the roller, which will both respond to the same set # of methods. So we want the method calls to go to the roller inside the # roller block, otherwise the current value of riven in the roller # will end up being constantly reset to original. Which # is exactly not the point. def __ambiguous_method__( binding_self, meth, *args, &blk ) # TODO maybe this should just always default to the roller? I don't see much point # in being fancier than that. # ::Kernel.puts "__ambiguous_method__ #{meth} for #{binding_self.inspect} and #{@obj.inspect}" if binding_self == @original # send it to the roller send meth, *args, &blk else # don't know what to do with it, so let Combinder raise an AmbiguousMethod # TODO too much coupling because this class knows about Combinder internals. raise ::NoMethodError, meth end end attr_accessor :riven, :original # this is so that we a roller is returned from a # call, you can call roller on it again, and keep # things rolling along. def roller( &block ) if block roll_block &block else self end end # instantiate the roller, pass it the block and # immediately return the modified copy def self.rive( original, &block ) new( original, &block ).riven end # Forward to riven, if the method exists. def method_missing(meth, *args, &block) if @riven.respond_to? meth interim = @riven.send( meth, *args, &block ) # ::Kernel.puts "sending #{meth}(#{args.inspect}) to #{@riven.inspect}: #{interim}" @riven = interim else super end end # used by combinder, so must be defined, otherwise it perturbs method_missing def send( meth, *args, &block ) method_missing( meth, *args, &block ) end # used by combinder, so must be defined, otherwise it perturbs method_missing # no point in using respond_to_missing?, because that's part of Object#respond_to, # not BasicObject def respond_to?( meth, include_all = false ) @riven.respond_to?(meth, include_all) || __methods__.include?(meth) end # make sure this BasicObject plays nicely in pry def inspect %Q{#<#{self.__class__} original: #{@original.inspect}, riven: #{@riven.inspect}>} end def pretty_inspect inspect end def to_s; inspect; end # include useful methods from Kernel, but rename define_method :__class__, ::Kernel.instance_method(:class) define_method :__object_id__, ::Kernel.instance_method(:object_id) protected def roll_block( &block ) case block.arity when 0 ::Ripar::Combinder.new( self, block.binding ).instance_eval &block when 1 yield self else ::Kernel.raise "Don't know how to handle arity #{block.arity}" end end private def __methods__ self.__class__.instance_methods end end