« Rubyにannotationを定義するとしたら? | メイン | LAMP »

2005年06月10日 (金)

metadataライブラリ [テクニカル]

[ruby-talk:93813] Re: Extensible meta-data ? に倣って、メタデータ処理ライブラリを書いてみた。ただし、クラスへのメタデータ付加は必ずselfを指定するようにする。やっぱりちょっとダサいけどしょうがないか。。。

使い方の適当な例は以下。ハンドラの設定部がポイント。

require 'annotation'
MetadataInterfaceName.def('META')
 
# ハンドラの例…属性を定義する
# ハンドラ設定は、メタデータ記述の前に行わなくてはいけない点に注意
# (ただし、定数アノテーション対応のため、記述後からのハンドラ起動機構も設ける予定)
MetadataHash.set_handler(:Attribute) do |owner, elem, data|
  case data
  when :reader, :writer, :accessor
    owner.__send__(:public)
    owner.__send__('attr_' + data.to_s, elem)
  end
end
 
class Foo
  META self, :Entity, :Table => 'foo'
  def foo
    puts "bar"
  end
  META :foo, :Method => "metadata", :Role => "bar"
  META :hoge, :Attribute => :accessor, :Role => "bar2"
end
 
p Foo.metadata
# => {[:hoge, :Role]=>"bar2", [Foo, :Table]=>"foo", [:foo, :Method]=>"metadata", 
#     [:hoge, :Attribute]=>:accessor, [:foo, :Role]=>"bar", [Foo, :Entity]=>true}
p Foo.metadata.fetch_element(:foo)
# => {:Method=>"metadata", :Role=>"bar"}
p Foo.metadata.fetch_name(:Role)
# => {:hoge=>"bar2", :foo=>"bar"}
p Foo.instance_methods - Object.instance_methods
# => ["hoge", "foo", "hoge="]

ちょっと長いけどソースはこちら。

class DoubleKeyHash < Hash
  def initialize(ifnone = nil, &block)
    super
    @key1key2 = {}
    @key2key1 = {}
  end
  def [](*keys)
    if keys.size == 1 and keys[0].kind_of?(Array)
      super(keys[0])
    elsif keys.size == 2
      super(keys)
    else
      raise ArgumentError
    end
  end
  def []=(*args)
    if args.size == 2
      raise ArgumentError unless args[0].kind_of?(Array) and args[0].size == 2
      add_keys(args[0][0], args[0][1])
      double_key_store(args[0][0], args[0][1], args[1])
    elsif args.size == 3
      add_keys(args[0], args[1])
      double_key_store(args[0], args[1], args[2])
    else
      raise ArgumentError
    end
  end
  def delete(key, &block)
    super
    remove_keys(key[0], key[1])
  end
  def update(other, &block)
    raise TypeError unless other.kind_of?(DoubleKeyHash)
    @key1key2.update(other.instance_eval('@key1key2'))
    @key2key1.update(other.instance_eval('@key2key1'))
    super
  end
  def fetch1(key1)
    ret = {}
    @key1key2.fetch(key1, []).each do |key2|
      ret[key2] = fetch([key1, key2])
    end
    ret
  end
  def fetch2(key2)
    ret = {}
    @key2key1.fetch(key2, []).each do |key1|
      ret[key1] = fetch([key1, key2])
    end
    ret
  end
  def double_key_store(key1, key2, value)
    store([key1, key2], value)
  end
  
  private
  def add_keys(key1, key2)
    @key1key2[key1] ||= []
    @key1key2[key1] << key2
    @key2key1[key2] ||= []
    @key2key1[key2] << key1
  end
  def remove_keys(key1, key2)
    if @key1key2.has_key?(key1)
      @key1key2[key1].delete(key2)
      @key1key2.delete(key1) if @key1key2[key1].empty?
    end
    if @key2key1.has_key?(key2)
      @key2key1[key2].delete(key1)
      @key2key1.delete(key2) if @key2key1[key2].empty?
    end
  end
end
 
class MetadataHash < DoubleKeyHash
  @@handler = {}
  @@default_handler = nil
  attr_reader :owner
  def initialize(owner_class)
    super()
    @owner = owner_class
  end
  def double_key_store(elem, name, data)
    super
    if @@handler.has_key?(name)
      @@handler[name].call(owner, elem, data)
    elsif @@default_handler
      @@default_handler.call(owner, elem, name, data)
    end
  end
  def update(other, &block)
    raise TypeError unless other.kind_of?(MetadataHash)
    super
    # handler calling is still not supported
  end
  def fetch_element(elem)
    fetch1(elem)
  end
  def fetch_name(name)
    fetch2(name)
  end
  def self.handler
    @@handler
  end
  def self.set_handler(name, &block)
    @@handler[name] = block
  end
  def self.default_handler
    @@default_handler
  end
  def self.set_default_handler(&block)
    @@default_handler = block
  end
end
 
module MetadataInterface
  def meta(elem, *values)
    md = __send__(MetadataInterfaceName.metadata)
    values.each do |value|
      if value.kind_of?(Hash)
        value.each do |name, data|
          md[elem, name] = data
        end
      else
        md[elem, value] = true
      end
    end
  end
  
  def metadata
    @metadata ||= MetadataHash.new(self)
  end
end
 
class MetadataInterfaceName
  # 名前の再定義
  # MetadataInterfaceName.def('META') のように用いる
  @@meta = :meta
  @@metadata = :metadata
  class << self
    def meta
      @@meta
    end
    def metadata
      @@metadata
    end
    def def(name)
      MetadataInterface.__send__(:alias_method, name, @@meta)
      MetadataInterface.__send__(:remove_method, @@meta)
      @@meta = name
    end
    def def_data(name)
      MetadataInterface.__send__(:alias_method, name, @@metadata)
      MetadataInterface.__send__(:remove_method, @@metadata)
      @@metadata = name
    end
  end
end
 
class Module
  include MetadataInterface
end

ちなみに、metadata定義をC#のattribute流儀の [...] にできないかと思って

MetadataInterfaceName.def('[]')

とやってみたが、[]はselfをつけないと配列リテラルとみなされるためNG。ざんねん。

投稿者 4bit : 2005年06月10日 19:30 このエントリーを含むはてなブックマーク

トラックバック

このエントリーのトラックバックURL:
http://www.4bit.net/x/mt/mt-tb.cgi/63

コメント

コメントしてください




保存しますか?