K.Sasada's Home Page

こめんとのついか

こめんとこめんと!

message

please add long comment :).

_6(Fri)

require の整理.

拡張子が与えられない時,$: を "2回調べる" という挙動を知らなかった.なんだこの仕様.

つまり,まず $: のパスに feature + '.rb' がないか調べて,なかったら $: のパスに feature + '.so' が無いか調べる.


というわけで,現状は多分こんな感じ.

# 現在
def require feature
  fullpath = nil

  # ロードするファイルを特定する
  if feature.absolute_path?
    fullpath = feature
  else
    feature_ext = File.extname(feature)
    if ext.empty?
      exts = %w(.rb .so)
    else
      exts = [feature_ext]
    end

    try(:found){
      exts.each{|ext| # .rb で $: を探し,無かったら改めて .so を $: から探す
        $:.each{|path|
          if File.exist?(path+feature+ext)
            fullpath = path+feature+ext
            throw :found
          end
        }
      }
    }
  end

  raise LoadError unless fullpath

  # 重複をチェックする
  if $LOADED_FEATURE.include? ...
    return false
  end

  # 実際にロードする
  once = ($LOADING_TABLE[full_path] ||= Once.new)
  once.once{
    load full_path
  }
  $LOADING_TABLE[full_path] = nil
  $LOADED_FEATURE << full_path
end

で,これを拡張可能にしたいわけです.例えば,zip ファイルの中の .rb ファイルをロードしたりとかしたいわけです.独自に暗号化した .rb.encrypted なファイルをロードしたいわけです.

処理をよく見ると,(1) fullpath を調べるところ,(2) 重複を調べるところ(すでに require しているかどうかをチェックするところ),そして (3) 実際にロードするところに分かれていることがわかります.

今回拡張したいのは (1) のフルパスを調べるところ(どこからロードするか,決めるところ),(3) どうやって実際にロードするか,になります.というわけで,2つの拡張が書きやすい仕組みを導入出来ないか検討して,Finder,Loader の2つの仕組みを導入出来ないか検討しました.

で,検討結果が下記です.

class Finder
  def self.responsible? path, ext
    false
  end

  attr_reader :path, :ext

  def initialize path, ext
    @path = path
    @ext = ext
  end

  def lookup feature, ext
    # return appropriate Loader object
    nil
  end
end

class Loader
  attr_reader :finder, :feature, :path
  def initialize finder, feature, path
    @finder = finder
    @feature = feature
    @path = path
  end

  def fullpath
    
  end

  def filename
    
  end

  def load
    # execute loading
    # for .rb script, then eval(script)
  end

  def load_script_string script_string, file
  end

  def laod_script_file filename, file = filename
  end

  module_function :load_script_string
  module_function :load_script_file

  ## __LOADER__.require_relative(feature)
  def require_relative feature
    loader = @finder.lookup(feature)
  end
end

$FINDER_CACHE = {}
$FINDER_CLASSES = [CompiledRubyScriptFileFinder, RubyScriptFileFinder, ExtFileFinder]

def require feature
  loader = require_find_loader(feature)
  require_latter loader
end

def find_finder path, ext, feature
  if $FINDER_CACHE.has_key? [path, ext]
    $FINDER_CACHE[[path, ext]]
  else
    finders = []
    $FINDER_CLASSES.each{|finder_class|
      if finder_class.responsible?(path, ext)
        finders << finder_class.new(path, ext)
      end
    }
    $FINDER_CACHE[[path, ext]] = finders
  end
end

def require_find_loader feature
  feature_ext = File.extname(feature)

  if feature_ext.empty?
    exts = ['.rb', '.so']
  else
    exts = [feature_ext]
  end

  # loader の探索
  loader = nil
  exts.each{|ext|
    $:.each{|path|
      finders = find_finders path, ext
      finders.each{|finder|
        break if loader = finder.lookup(feature)
      }
    }
  }
  raise LoadError unless loader
  loader
end

def require_latter loader
  # 重複のチェック
  return false if $LOADED_FEATURES.include?(loader.filename)
  return false if $LOADED_FEATURES.include?(loader.fullpath)

  # 実際のロード
  once = ($LOADING[loader.fullpath] ||= Once.new)
  once.once{
    loader.load
  }
  $LOADING[loader.fullpath] = nil
end

まず,Finder オブジェクトは $LOAD_PATH の各パスに対応します.対応するパスであれば,Finder オブジェクトを生成します.この処理は,require ごとに走るため,$FINDER_CACHE というものでキャッシュしています.

ここでは,拡張子もキャッシュのキーになっているのが大事です.

で,生成された Finder オブジェクトを利用して, Finder#lookup(feature) でロード出来るかチェックします.ロード出来るようだったら,Loader オブジェクトを生成し,返します.

Loader オブジェクトは,適当な fullpath を返すようにします.これで,重複チェックを行い,また最後に $LOADED_FEATURE への追加を行います.

Loader オブジェクトは,Ruby スクリプト(に相当する何か)を独自の方法で load します.

デフォルトで,RubyScriptFileFinder と ExtFileFinder,そしてそれにそれぞれ対応する RubyScriptFileLoader,ExtFileLoader を用意.従来と完全互換です.


というのを考えたんだけど,複雑過ぎるかなぁ.

重複のチェックは,もしかして Finder#lookup でやらせるべきなんだろうか.


好きなだけ長いコメントをどうぞ。

お名前


back

tton 記述が使えます。YukiWikiな記述してりゃ問題ありません。

「行頭に#code」 と、「行頭に#end」 で挟むと、その間の行は pre で囲まれます。プログラムのソースを書くときに使ってください。

例:

#code

(なんかプログラム書く)

#end

リンクは

[[なまえ|http://www.example.org]]

とか

[[http://www.example.org]]

で貼れます。

$Date: 2003/04/28 10:27:51 $