D
Daniel Berger
Hi everyone,
I need help!
I've got a library called "use" that let's you mixin methods from
modules in a fine-grained fashion. Unfortunately, something broke it
between Ruby 1.8.6-p111 and Ruby 1.8.6-p287.
It may have something to do with the way I autogenerate some methods in
the windows-api library, as simple cases still work fine, but I'm not
sure how or why.
Here's some sample code:
# test.rb
require 'use'
require 'windows/path'
require 'windows/msvcrt/io'
class Foo
use Windows:ath
use Windows::MSVCRT::IO, :tmpfile
def initialize
p PathIsRoot("C:\\")
p tmpfile()
end
end
Foo.new
Using Ruby 1.8.6-p111 I get:
true
2009464032
Using Ruby 1.8.6-p287 I get:
(eval):3:in `PathIsRoot': uninitialized constant Class:athIsRoot
(NameError)
from test.rb:10:in `initialize'
from test.rb:15:in `new'
from test.rb:15
What the heck happened between p 111 and p 287?
Below is the full source for use.rb.
Regards,
Dan
class Class
# The version of the 'use' library
USE_VERSION = '1.2.2'
# Allows you to include mixins in a fine grained manner. Instead of
# including all methods from a given module, you can instead mixin
# only those methods you want through a combination of the :include,
# :exclude, and :alias options.
#
# Examples:
#
# # Defines a 'bar' and 'baz' method
# module Foo
# def bar
# "hello"
# end
# def baz
# "world"
# end
# end
#
# # Defines a 'bar', 'blah', and 'zap' methods
# module Test
# def bar
# "goodbye"
# end
# def blah
# "new york"
# end
# def zap
# "zap"
# end
# end
#
# # From the Foo module, only mixin the 'bar' method. From the Test
# # module exclude the 'bar' and 'zap' methods.
# class Zap
# use Foo, :bar
# use Test, :exclude => [:bar, :zap]
# end
#
# z = Zap.new
#
# z.bar # => "hello"
# z.baz # => NoMethodError - wasn't mixed in
# z.zap # => NoMethodError - wasn't mixed in
# z.blah # =>"new york"
#
# # Alias a method on the fly
# class MyKlass
# use Foo :alias => {:bar, :test}
# end
#
# m = MyKlass.new
# m.test # => "hello"
# m.bar # => NoMethodError - was aliased to 'test'
#
# If no options follow the module name this method is identical
# to a standard include.
#
def use(*args)
valid_keys = %w/include exclude alias/
excluded = []
included = []
aliased = []
mod = args.shift.clone
# If no arguments follow the mod name, treat as a standard include
if args.empty?
included.concat(mod.instance_methods)
end
m = Module.new
args.each{ |arg|
if arg.kind_of?(Hash)
arg.each{ |key, val|
case key.to_s
when "include"
if val.respond_to?each)
val.each{ |arg| included.push(arg.to_s) }
else
included.push(val.to_s)
end
when "exclude"
if val.respond_to?each)
val.each{ |arg| excluded.push(arg.to_s) }
else
excluded.push(val.to_s)
end
when "alias"
aliased.push(val)
else
raise "invalid key '#{key}'"
end
}
else
included.push(arg.to_s)
end
}
unless included.empty? || excluded.empty?
err = "you cannot include and exclude in the same statement"
raise ArgumentError, err
end
imethods = mod.instance_methods
# Remove excluded methods
unless excluded.empty?
(imethods & excluded).each{ |meth|
mod.module_eval{ remove_method(meth) }
}
end
# Alias methods
aliased.each{ |pair|
pair.each{ |old, new|
included.push(old) # Aliased methods automatically included
mod.module_eval{
alias_method(new, old)
remove_method(old)
}
}
}
# Remove methods not specifically included. The rescue was needed
# for cases where a module included another module. Also, don't
# remove methods that already exist unless specifically included.
unless included.empty?
(imethods - included).each{ |meth|
if superclass.instance_methods.include?(meth)
if included.include?(meth)
mod.module_eval{ undef_method(meth) rescue nil }
else
mod.module_eval{ remove_method(meth) rescue nil }
end
else
mod.module_eval{ undef_method(meth) rescue nil }
end
}
end
m.module_eval{ include mod }
# Raise a warning if methods are shadowed (in $VERBOSE mode)
if $VERBOSE
imethods = instance_methods(true)
m.instance_methods.each{ |meth|
next unless imethods.include?(meth)
warn "method '#{meth}' aliased, shadows old '#{meth}'"
}
end
include m
end
end
I need help!
I've got a library called "use" that let's you mixin methods from
modules in a fine-grained fashion. Unfortunately, something broke it
between Ruby 1.8.6-p111 and Ruby 1.8.6-p287.
It may have something to do with the way I autogenerate some methods in
the windows-api library, as simple cases still work fine, but I'm not
sure how or why.
Here's some sample code:
# test.rb
require 'use'
require 'windows/path'
require 'windows/msvcrt/io'
class Foo
use Windows:ath
use Windows::MSVCRT::IO, :tmpfile
def initialize
p PathIsRoot("C:\\")
p tmpfile()
end
end
Foo.new
Using Ruby 1.8.6-p111 I get:
true
2009464032
Using Ruby 1.8.6-p287 I get:
(eval):3:in `PathIsRoot': uninitialized constant Class:athIsRoot
(NameError)
from test.rb:10:in `initialize'
from test.rb:15:in `new'
from test.rb:15
What the heck happened between p 111 and p 287?
Below is the full source for use.rb.
Regards,
Dan
class Class
# The version of the 'use' library
USE_VERSION = '1.2.2'
# Allows you to include mixins in a fine grained manner. Instead of
# including all methods from a given module, you can instead mixin
# only those methods you want through a combination of the :include,
# :exclude, and :alias options.
#
# Examples:
#
# # Defines a 'bar' and 'baz' method
# module Foo
# def bar
# "hello"
# end
# def baz
# "world"
# end
# end
#
# # Defines a 'bar', 'blah', and 'zap' methods
# module Test
# def bar
# "goodbye"
# end
# def blah
# "new york"
# end
# def zap
# "zap"
# end
# end
#
# # From the Foo module, only mixin the 'bar' method. From the Test
# # module exclude the 'bar' and 'zap' methods.
# class Zap
# use Foo, :bar
# use Test, :exclude => [:bar, :zap]
# end
#
# z = Zap.new
#
# z.bar # => "hello"
# z.baz # => NoMethodError - wasn't mixed in
# z.zap # => NoMethodError - wasn't mixed in
# z.blah # =>"new york"
#
# # Alias a method on the fly
# class MyKlass
# use Foo :alias => {:bar, :test}
# end
#
# m = MyKlass.new
# m.test # => "hello"
# m.bar # => NoMethodError - was aliased to 'test'
#
# If no options follow the module name this method is identical
# to a standard include.
#
def use(*args)
valid_keys = %w/include exclude alias/
excluded = []
included = []
aliased = []
mod = args.shift.clone
# If no arguments follow the mod name, treat as a standard include
if args.empty?
included.concat(mod.instance_methods)
end
m = Module.new
args.each{ |arg|
if arg.kind_of?(Hash)
arg.each{ |key, val|
case key.to_s
when "include"
if val.respond_to?each)
val.each{ |arg| included.push(arg.to_s) }
else
included.push(val.to_s)
end
when "exclude"
if val.respond_to?each)
val.each{ |arg| excluded.push(arg.to_s) }
else
excluded.push(val.to_s)
end
when "alias"
aliased.push(val)
else
raise "invalid key '#{key}'"
end
}
else
included.push(arg.to_s)
end
}
unless included.empty? || excluded.empty?
err = "you cannot include and exclude in the same statement"
raise ArgumentError, err
end
imethods = mod.instance_methods
# Remove excluded methods
unless excluded.empty?
(imethods & excluded).each{ |meth|
mod.module_eval{ remove_method(meth) }
}
end
# Alias methods
aliased.each{ |pair|
pair.each{ |old, new|
included.push(old) # Aliased methods automatically included
mod.module_eval{
alias_method(new, old)
remove_method(old)
}
}
}
# Remove methods not specifically included. The rescue was needed
# for cases where a module included another module. Also, don't
# remove methods that already exist unless specifically included.
unless included.empty?
(imethods - included).each{ |meth|
if superclass.instance_methods.include?(meth)
if included.include?(meth)
mod.module_eval{ undef_method(meth) rescue nil }
else
mod.module_eval{ remove_method(meth) rescue nil }
end
else
mod.module_eval{ undef_method(meth) rescue nil }
end
}
end
m.module_eval{ include mod }
# Raise a warning if methods are shadowed (in $VERBOSE mode)
if $VERBOSE
imethods = instance_methods(true)
m.instance_methods.each{ |meth|
next unless imethods.include?(meth)
warn "method '#{meth}' aliased, shadows old '#{meth}'"
}
end
include m
end
end