# -*- ruby -*-

=begin
= Module aliasing
== Abstract
Module with methods aliased or undefined. For example;

  class Foo
    ModuleA.aliasing :new_method => :old_method,
                     :method_to_be_undefined => nil
  end

class (({Foo})) has method (({new_method})) equivalent to
(({ModuleA#old_method})), but doesn't have
(({method_to_be_undefined})).
=end

class Module
  # Copyright = %$Copyleft: (c) 2000 Nakada.Nobuyoshi <nobu.nokada@softhome.net> $.freeze
  # RCSID = %w$Id: aliasing.rb,v 0.3 2006-08-29 02:20:59 nobu Exp $[1..-1].each {|s| s.freeze}.freeze
  # Revision = RCSID[1].split('.').collect {|s| s.to_i}.freeze

=begin
== Instance methods
=end

=begin
--- Module#aliasing(aliases, [only = false])
    Makes new module including (({self})) module with aliasing
    methods, according to ((|aliases|)) hash mapping from new method
    ID to old method ID.  If any value is (({nil})) or (({false})),
    its corresponding method is undefined.
=end
  def aliasing(*aliases)
    myself = self
    only = false
    sym = newname = oldname = nil
    methods = {}
    consts = {}
    instance_methods(true).each {|sym| methods[sym.intern] = sym}
    constants.each {|sym| consts[sym.intern] = sym}

    case aliases.last
    when true, false
      only = aliases.pop
    end
    if Hash === aliases.last
      h = aliases.pop
      aliases.collect! {|sym| [sym, true]}.concat(h.to_a)
    end

    Module.new.module_eval do
      include(myself)
      aliases.each do |newname, oldname|
	# copyies ((|oldname|)) to ((|newname|)), if ((|oldname|)) is
	# (({nil})) or (({false})), it is assumed as undefined.
	newname = newname.to_s.intern if newname.respond_to?(:intern)
	if oldname == false
	  undef_method(newname) if methods.delete(newname)
	  remove_const(newname) if consts.delete(newname)
	  next
	end

	methods.delete(newname)
	consts.delete(newname)

	oldname = oldname.to_s.intern if oldname.respond_to?(:intern)
	next if oldname == nil or oldname == true or oldname == newname

	if methods.delete(oldname)
	  alias_method(newname, oldname)
	  undef_method(oldname)
	end
	if consts.delete(oldname)
	  const_set(newname, const_get(oldname))
	  remove_const(oldname)
	end
      end
      if only
	methods.each_key {|sym| undef_method(sym)}
	consts.each_key {|sym| remove_const(sym)}
      end
      self
    end
  end

=begin
--- Module#only_aliasing(aliases)
    Simillar to ((<Module#aliasing>)), but undef methods not included.
=end
  def only_aliasing(*aliases)
    aliasing(*aliases.push(true))
  end
end

if $0 == __FILE__
  s = DATA.read.gsub!(/^=begin\s+(\S+).*?\n=end.*?$/m) do
    $&.gsub(ARGV.empty? || ARGV.include?($1) ? /^=/ : /^/, '#\&')
  end
  eval s, nil, __FILE__, __LINE__+3
end
__END__

=begin example
== Example
module Foo
  def foo; 'foo' end
  def bar; 'bar in Foo' end
  def zot; 'zot' end
  def beep; 'beep' end
end

module Bar
  def bar; 'bar in Bar' end
end

class Hoge
  include Foo.aliasing(:hoge => :foo, :bar_in_foo => :bar, :beep => false)
  include Bar.aliasing(:bar_in_bar => :bar)
end

require 'ok'
x = Hoge.new
Check.new('aliasing module') do |q|
  q.ok?('simple alias') {x.hoge == 'foo'}
  q.ok?('pass through') {x.zot == 'zot'}
  q.ok?('old name undefed?') {begin x.foo; nil; rescue NameError; $! end}
  q.ok?('renamed method') {begin x.bar; nil; rescue NameError; $! end}
  q.ok?('undefed method') {begin x.beep; nil; rescue NameError; $! end}
  q.ok?('conflicting methods 1') {x.bar_in_foo == 'bar in Foo'}
  q.ok?('conflicting methods 2') {x.bar_in_bar == 'bar in Bar'}
end
=end #`#"#'

=begin test
== Test
require 'runit/testcase'
require 'runit/cui/testrunner'

class TestAliasing < RUNIT::TestCase
  module Foo
    def foo; 'foo' end
    def bar; 'bar in Foo' end
    def zot; 'zot' end
    def beep; 'beep' end
  end

  module Bar
    def bar; 'bar in Bar' end
  end

  class Hoge
    include Foo.aliasing(:hoge => :foo, :bar_in_foo => :bar, :beep => false)
    include Bar.aliasing(:bar_in_bar => :bar)
  end

  Kernel.const_defined?(:NoMethodError) or NoMethodError = NameError

  def setup
    @x = Hoge.new
  end

  def test_simple_alias
    assert_equal('foo', @x.hoge)
  end

  def test_pass_through
    assert_equal('zot', @x.zot)
  end

  def test_old_name_undefed_p
    assert_exception(NoMethodError) {@x.foo}
  end

  def test_renamed_method
    assert_exception(NoMethodError) {@x.bar}
  end

  def test_undefed_method
    assert_exception(NoMethodError) {@x.beep}
  end

  def test_conflicting_methods_1
    assert_equal('bar in Foo', @x.bar_in_foo)
  end

  def test_conflicting_methods_2
    assert_equal('bar in Bar', @x.bar_in_bar)
  end
end

RUNIT::CUI::TestRunner.run(TestAliasing.suite)

=end #`#"#'
