Report abuse

#
# Stupid bit of ruby muckin
#
# Was trying to do something like this:
#
#   "svn co #{src} #{dest}".to(:puts).to(:system)
#
# It's pretty hard if you want the method calls in the #to to be passed to the
# receiver where the line resides.  It would be easy if we had a proper #caller
# or Binding.of_caller
#
# But we don't (yet), so I thought I could achieve the same effect by keeping
# a hold of the receiver in a proxy object.  So, it goes like this:
#
#   pass("svn co #{src} #{dest}").to(:puts).to(:system)
#
# I added a few niceties to make the return value sensible, and also added #into
# which modifies the passed object in place
#
#   pass("Foo").into("bar.+").to(:puts)
# 
# I have no idea if this is useful, if you find yourself passing the same argument into
# some methods over and over, then you could use this.
#
# Feb 2008 - Ian White http://blog.ardes.com/ian
#
# p.s. I was trying to get a nice sugary syntax rush,
#      there's already a bunch of constructs for doing this.
#
# Compare:
#
#   # plain ruby
#   msg = "oooh!"
#   MyObj.foo msg
#   MyObj.far msg
#   MyObj.faz msg
#
#   # #returning from ActiveSupport
#   returning "oooh!" do |s|
#     MyObj.foo s
#     MyObj.far s
#     MyObj.faz s
#   end
#
#   # #pass (this code)
#   MyObj.pass("oooh!").to :foo, :far, :faz
#

require 'rubygems'
require 'active_support'

#
# Usage:
#
# Object#pass(obj) creates a PassProxy which delegates methods to obj
#
# except
#   #to:    which sends obj to the specified methods, returning the PassProxy
#   #into:  which sends obj to the specified methods, updating the PassProxy with the results
#
# The receiver of #pass is used to determine where the methods are sent in #to and #into
# 
# pass("str").to :puts, :system
#   # calls main.puts("str")
#   # calls main.system("str")
#   # => "str"
#
# my_obj.pass(1).into(:times_2)
#   # calls my_obj.times_2(1)
#   # => 2
#
# Can be chained
#
# pass(1).into("2.+").to(:puts)
#  # 2.+(1)
#  # => 3
#  # main.puts(3)
#  # => 3

class Object
  def pass(*object, &block)
    PassProxy.new(self, *object, &block)
  end
end

class PassProxy < BasicObject
  def initialize(context, *object, &block)
    object = object.first if object.size == 1
    @__context__, @__object__, @__block__ = context, object, block
  end

  def method_missing(method, *args, &block)
    @__object__.send(method, *args, &block)
  end

  def respond_to?(method)
    @__object__.respond_to?(method)
  end

  def to(*methods, &block)
    methods << block if block_given?
    methods.each {|method| __call_method__(method)}
    self
  end

  def into(*methods, &block)
    methods << block if block_given?
    methods.each {|method| @__object__ = __call_method__(method)}
    self
  end

private 
  def __call_method__(method)
    if method.is_a?(Symbol)
      @__context__.send method, *@__object__, &@__block__
    elsif method.respond_to?(:call)
      @__context__.instance_exec *@__object__, &method
    elsif method.is_a?(String)
      @__context__.instance_exec *@__object__, &eval("proc{|*a| #{method}(*a)}")
    else
      raise "I can't figure out how to call #{method.inspect}"
    end
  end
end