prepend.rb 2.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. # Fake implementation of prepend(), which does not support overriding
  2. # inherited methods nor methods that are formerly overridden by
  3. # another invocation of prepend().
  4. #
  5. # Here's what <Original>.prepend(<Wrapper>) does:
  6. #
  7. # - Create an anonymous stub module (hereinafter <Stub>) and define
  8. # <Stub>#<method> that calls #<method>_without_<Wrapper> for each
  9. # instance method of <Wrapper>.
  10. #
  11. # - Rename <Original>#<method> to #<method>_without_<Wrapper> for each
  12. # instance method of <Wrapper>.
  13. #
  14. # - Include <Stub> and <Wrapper> into <Original> in that order.
  15. #
  16. # This way, a call of <Original>#<method> is dispatched to
  17. # <Wrapper><method>, which may call super which is dispatched to
  18. # <Stub>#<method>, which finally calls
  19. # <Original>#<method>_without_<Wrapper> which is used to be called
  20. # <Original>#<method>.
  21. #
  22. # Usage:
  23. #
  24. # class Mechanize
  25. # # module with methods that overrides those of X
  26. # module Y
  27. # end
  28. #
  29. # unless X.respond_to?(:prepend, true)
  30. # require 'mechanize/prependable'
  31. # X.extend(Prependable)
  32. # end
  33. #
  34. # class X
  35. # prepend Y
  36. # end
  37. # end
  38. class Module
  39. def prepend(mod)
  40. stub = Module.new
  41. mod_id = (mod.name || 'Module__%d' % mod.object_id).gsub(/::/, '__')
  42. mod.instance_methods.each { |name|
  43. method_defined?(name) or next
  44. original = instance_method(name)
  45. next if original.owner != self
  46. name = name.to_s
  47. name_without = name.sub(/(?=[?!=]?\z)/) { '_without_%s' % mod_id }
  48. arity = original.arity
  49. arglist = (
  50. if arity >= 0
  51. (1..arity).map { |i| 'x%d' % i }
  52. else
  53. (1..(-arity - 1)).map { |i| 'x%d' % i } << '*a'
  54. end << '&b'
  55. ).join(', ')
  56. if name.end_with?('=')
  57. stub.module_eval %{
  58. def #{name}(#{arglist})
  59. __send__(:#{name_without}, #{arglist})
  60. end
  61. }
  62. else
  63. stub.module_eval %{
  64. def #{name}(#{arglist})
  65. #{name_without}(#{arglist})
  66. end
  67. }
  68. end
  69. module_eval {
  70. alias_method name_without, name
  71. remove_method name
  72. }
  73. }
  74. include stub
  75. include mod
  76. end
  77. private :prepend
  78. end unless Module.method_defined?(:prepend)