Ruby に use を実装する。
RubyGems みたいに、あらかじめ spec をキャッシュしておく、みたいなのが許されるなら、こういうのもありなんじゃないかなぁと思った。以下の実装はいろいろだめなところがあるだろうけど…… (プラットホーム依存のところがほげほげとか)
# module 名 -> ファイル名へのハッシュテーブルを作る
# ruby 起動しまくって時間がかかるので前もってキャッシュする
require "pathname"
module_files = {}
$LOAD_PATH.each do |path|
path = Pathname.new(path)
next if path.relative?
Pathname.glob(path + "**/*").each do |t|
next unless t.file?
next if t.to_s =~ /tk/
puts t
ENV.delete("RUBYOPT")
ret = nil
IO.popen("ruby", "r+") do |io|
io.puts <<-CODE
# 自分自身はロード済みにしないと、ループして require している場合にまずい
$LOADED_FEATURES << '#{t.relative_path_from(path)}'
$depend_lib = []
def __modules
ret = []
ObjectSpace.each_object(Module) do |o|
ret << o
end
ret
end
alias _require_orig require
def require(lib)
prev = __modules
ret = _require_orig(lib)
if ret
$depend_lib.concat(__modules - prev)
end
ret
end
def set_trace_func(*)
end
prev = __modules
stdout = STDOUT.dup
STDOUT.reopen($stderr)
begin
_require_orig '#{t}'
ensure
STDOUT.reopen(stdout)
print Marshal.dump((__modules - prev - $depend_lib).map {|i| i.to_s })
STDOUT.flush
exit!
end
CODE
io.close_write
ret = io.read
ret = Marshal.load(ret)
end
ret.each do |m|
(module_files[m] ||= []) << t.to_s
end
end
end
require "pp"
File.open("module.cache", "wb") do |f|
Marshal.dump(module_files, f)
end本体
def use(mod)
mod = mod.to_s
mods = Marshal.load(File.read("module.cache"))
raise LoadError, "#{mod} is not found." unless mods[mod]
mods = mods[mod].map {|i|
[i, i.split(File::ALT_SEPARATOR || File::SEPARATOR).size]
}.sort_by {|i,ii|
ii
}
mods.select {|i,ii| ii == mods.first[1] }.each do |i,ii|
require i
end
end
use %(Test::Unit::AutoRunner)
use %(Test::Unit::TestCase)
class UseTEST < Test::Unit::TestCase
def test_success
use %(Pathname)
assert_kind_of Class, Pathname
use %(Test::Unit::AutoRunner)
assert_kind_of Class, Test::Unit::AutoRunner
use %(ERB)
assert_kind_of Class, ERB
use %(ERB::Util)
assert_kind_of Module, ERB::Util
use %(WEBrick)
assert_kind_of Module, WEBrick
use %(Net::HTTP)
assert_kind_of Module, Net
assert_kind_of Class, Net::HTTP
assert_raise(LoadError) do
use %(NotInTableModule)
end
end
end
Test::Unit::AutoRunner.run($0 != "-e" && $0)テーブルは require しているファイルに定義されているクラスを除いているので、test/unit.rb みたいな require をしている場合、そのファイルにいくら定義があっても「クラスの再定義/上書き」という判断しかできないので、use "Test::Unit" とかしても test/unit.rb は読みこまれない。
あとは webrick なんかを例にとると webrick.rb は他のファイルを require するだけなので、直接はこのファイルはモジュールハッシュ上にあらわれない。use では同じ深さを持っていて、WEBrick を定義している全てのファイルを require してる。
もっとたくさんテスト書いて、実装精密にすればつかえるかなぁ……
どっかのサーバで rubyforge の gems をミラーして解析して、このテーブルをつくっておいて、パッケージ検索できるようにするとか、あるいはそのままパッケージシステムにしてしまうとかがおもしろいかなぁ。既存の gems をそのままもってこれてたのしそう。