« 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。ざんねん。
トラックバック
このエントリーのトラックバックURL:
http://www.4bit.net/x/mt/mt-tb.cgi/63