#-- # Annotation # # Copyright (c) 2005 Thmas Sawyer # # Ruby License # # This module is free software. You may use, modify, and/or redistribute this # software under the same terms as Ruby. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. # #================================================================================ # ChangeLog #================================================================================ # 05.10.25 trans Initial release. #================================================================================ #++ #:title: Annotations # # Annotations allows you to annontate objects, including methods with arbitrary # "metadata". These annotations don't do anything in themselves. They are # merely comments. But you can put them to use. For instance an attribute # validator might check for an annotation called :valid and test against it. # # class X # attr :a # ann :@a, :valid => lambda{ |x| x.is_a?(Integer) } # # def validate # instance_variables.each { |iv| # if validator = self.class.ann(iv)[:valid] # value = instance_variable_get(iv) # unless validator.call(vale) # raise "Invalid value #{value} for #{iv}" # end # end # } # end # # end # # == Author(s) # # * Thomas Sawyer # # facets require 'nano/array/merge' require 'mega/openobject' require 'mega/inheritor' require 'mega/null' # Annotations is an OpenObject, and is used across the # board for keeping annotations. # Annotation class serves for both simple and # and inherited cases depending on whether # a base class is given. class Annotation < OpenObject def initialize( set={} ) super( set ) end def inherits( base, &heritage ) @base = base @heritage = heritage self end def method_missing( sym, *args, &blk ) name = sym.to_s.gsub(/([=!?])$/, '').to_sym type = sym.to_s[-1,1] unless @base r = super return Kernel.null if r.nil? and type != '?' return r else if type == '!' unless __key__?( name ) parent = nil @base.ancestors.each do |anc| if ans = @heritage.call(anc) #anc.method_annotation[@key] if parent = ans[name] break end end end if parent return __table__[name] = parent.dup #vs. __class__.new? end end return super( name ) end iob = dup super return inheritance if iob != self return inheritance.__send__( sym, *args, &blk ) end end def [](x) @base ? inheritance[x] : super end def inheritance return self unless @base inh = @base.ancestors.collect{ |anc| @heritage.call(anc) }.compact if inh a = Annotation.new( inh.pop.to_h ) inh.push( a ) #.dup ) inh.reverse.inject{ |memo, i| memo.__merge__( i ) } end end end # This is a simple decorator for accessing a module's # method annotations (and it's own annotations). class Annotator < BasicObject def initialize( mod ) ; @mod = mod ; end def inspect ; "#" ; end def self ; @mod.annotation ; end def [](sym) sym == :self ? @mod.annotation : @mod.method_annotation( sym ) end def method_missing( sym, *args, &blk ) type = sym.to_s[-1,1] name = sym.to_s.gsub(/[=!?]$/, '').to_sym return @mod.annotation if name == :self r = @mod.method_annotation( name ) return nil if type == '?' and r == Kernel.null return r end end # Any object can be annotated. module Kernel # Store and retrieve annotations for an object. def annotation @_annotation ||= Annotation.new end def ann( harg=nil ) return annotation unless harg annotation.__update__( harg ) end end # Modules and Classes have more sifisticated annotation. # They differ from normal object annotation in that # the are inheritables. class Module def annotation @_annotation ||= Annotation.new.inherits( self ){ |anc| anc.annotation } end def method_annotation( key=nil ) @_method_annotation ||= {} return @_method_annotation unless key key = key.to_sym if ann = @_method_annotation[key] return ann elsif ancestors.any? { |anc| anc.method_annotation[key] } @_method_annotation[key] = Annotation.new.inherits( self ){ |anc| anc.method_annotation[key] } else null end end # Does this module/class or its ancestors have any annotation # for a given method? def annotated?( key ) return annotation if self == key r = method_annotation( key ) null == r ? nil : r end # Returns a list of all annotated methods. def annotated_methods(include_ancestors=true) annlist = [] if include_ancestors ancestors.inject([]){ |memo, anc| memo | anc.method_annotation.keys } else method_annotation.keys end end # Define annotation for a key. The note parameter must be # a Hash or Hash-like object. private def define_annotation( key, note ) key = key.to_sym method_annotation[key] = Annotation.new(note).inherits(self){ |anc| anc.method_annotation[key] } method_annotation[key] end public def ann(*args) harg = args.last.is_a?(Hash) ? args.pop : {} #return annotator if args.empty? and harg.empty? return @_ann ||= Annotator.new( self ) if args.empty? and harg.empty? # for array of "mini-hashes" format if harg.empty? and Hash === args[0] args.each{|e| e.each{|k,v| harg[k]=v}} args = [] end # querying a single annotation if harg.empty? and args.size == 1 return annotation if self == args[0] or :self == args[0] #return args[0].annotation unless args[0].is_a?(Symbol) or args[0].is_a?(String) return method_annotation( args[0] ) #.inherit end # if last element is a class (and there are more then just the one element) harg[:class] = args.pop if args.last.is_a?(Class) and args.size > 1 if args.empty? # hash format harg.each do |key,hsh| if key == :self annotation.__update__( hsh ) next end note = annotated?( key ) if note and null != note note.__update__( hsh ) else define_annotation( key, hsh ) end end return harg.keys else # name, hash format args.each do |key| if key == :self annotation.__update__( harg ) next end note = annotated?( key ) if note and null != note note.__update__( harg ) else define_annotation( key, harg ) end end return args end end #def ann end # attr on steroids! Override the default implementations # to support annotations. class Module inheritor :attributes, [], :| def attr( *args ) args.flatten! case args.last when TrueClass args.pop attr_accessor( *args ) when FalseClass args.pop attr_reader( *args ) else attr_reader( *args ) end end code = '' [ :_reader, :_writer, :_accessor].each do |m| d = case m when :_reader %{def %1$s; @%1$s; end} when :_writer %{def %1$s=(x); @%1$s = x; end} when :_accessor %{def %1$s; @%1$s; end ; def %1$s=(x); @%1$s = x; end} else %{def %1$s; @%1$s; end} end code << %{ def attr#{m}(*args) args.flatten! harg = {} while args.last.is_a?(Hash) ; harg.update(args.pop) ; end harg[:class] = args.pop if args.last.is_a?(Class) raise if args.empty? and harg.empty? if args.empty? harg.each { |a,h| module_eval( "#{d}" % a) a = a.to_sym define_annotation( a, h ) } attributes!.merge!( harg.keys ) # return the names of the attributes created return harg.keys else args.each { |a| module_eval( "#{d}" % a) a = a.to_sym define_annotation( a, harg ) } attributes!.merge!( args ) # return the names of the attributes created return args end end } end class_eval( code ) alias_method :attribute, :attr_accessor end # _____ _ # |_ _|__ ___| |_ # | |/ _ \/ __| __| # | | __/\__ \ |_ # |_|\___||___/\__| # =begin testing require 'test/unit' class TC01 < Test::Unit::TestCase class X def x1 ; end ann :x1, :q=>3 ann :x1, :a=>1 end def test_01_001 assert_equal( 1, X.ann(:x1)[:a] ) assert_equal( 3, X.ann(:x1)[:q] ) assert_equal( null, X.ann(:x2) ) end def test_01_002 assert_equal( 1, X.ann(:x1)[:a] ) assert_equal( 3, X.ann(:x1)[:q] ) assert_equal( nil, X.ann(:x1)[:c] ) end def test_01_003 assert_equal( 1, X.ann.x1[:a] ) assert_equal( 3, X.ann.x1[:q] ) end def test_01_004 assert_equal( 1, X.ann.x1.a ) assert_equal( 3, X.ann.x1.q ) end def test_01_005 assert_equal( null, X.ann.x2 ) assert_equal( null, X.ann.x1.r ) end def test_02_007 X.ann.x1.a = 2 assert_equal(2, X.ann.x1.a) end end class TC02 < Test::Unit::TestCase class X def x ; end ann :x, :z=>1 end class Y < X #ann :x, :y=>2 end def test_02_001 assert( Y.annotated?( :x ) ) end def test_02_002 #assert_equal( [:x1], Y.annotated_methods ) end def test_02_003 #assert_equal( 1, Y.ann(:x).inheritance ) #assert_equal( 1, Y.ann(:x) ) end def test_02_004 assert_equal(1, X.ann(:x)[:z]) assert_equal(1, Y.ann(:x)[:z]) end def test_02_005 assert_equal(1, Y.ann(:x).z) end def test_02_006 assert_equal(1, Y.ann.x.z) end def test_02_007 Y.ann.x.z = 2 assert_equal(2, Y.ann.x.z) end end class TC03 < Test::Unit::TestCase class X def n ; end ann :n, :v=>1 ann :n, :p=>3 end class Y < X ann :n, :b=>2 end def test_03_001 assert_equal(2, Y.ann(:n)[:b]) assert_equal(1, Y.ann(:n)[:v]) end def test_03_002 assert_equal(2, Y.ann(:n).b) end def test_03_003 assert_equal(2, Y.ann.n.b) end def test_03_004 #assert_equal( [:n], Y.annotated_methods ) end end class TC04 < Test::Unit::TestCase module M ann :m, :c=>3 end def test_04_001 assert_equal(3, M.ann.m.c) end end class TC05 < Test::Unit::TestCase module M ann :m, :c=>3 end class Y ; end class Z < Y include M end def test_05_001 assert_equal( 3, Z.ann.m.c ) end end class TC06 < Test::Unit::TestCase module M ann :m, String end def test_06_001 assert_equal( String, M.ann.m.class ) end end class TC07 < Test::Unit::TestCase class K ann self, :oid => 'key' ann :w, String end def test_07_001 assert_equal( String, K.ann.w.class ) end def test_07_002 assert_equal( 'key', K.ann.self.oid ) end def test_07_003 #assert_equal( K.ann.self, K.ann(:self) ) #assert_equal( K.ann(K), K.annotation ) #assert_equal( K.ann.self.to_h, K.ann(K).to_h ) #assert_equal( K.ann.self, K.ann(K) ) end end class TC08 < Test::Unit::TestCase module M ann :this, :koko => [] ann.this.koko! << 1 end class C1 #ann :this, :koko => [] include M ann.this.koko! << 2 ann.this.koko! << 3 end class C2 include M ann.this.koko! << 4 end def test_08_001 assert_equal( [1], M.ann.this.koko ) end def test_08_002 assert_equal( [1,2,3], C1.ann.this.koko ) end def test_08_003 assert_equal( [1,4], C2.ann.this.koko ) end end class TC09 < Test::Unit::TestCase class A attr_accessor :x, :cast=>"to_s" end def test_09_001 a = A.new assert_equal( [:x], A.attributes ) end end class TC10 < Test::Unit::TestCase class A attr :x, :cast=>"to_s" end def test_10_001 assert_equal( "to_s", A.ann.x.cast ) end end =end