# = annotation.rb # # == Copyright (c) 2005 Thomas 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. # # == Authors & Contributors # # * Thomas Sawyer # Author:: Thomas Sawyer # Copyright:: Copyright (c) 2005 Thomas Sawyer # License:: Ruby License require 'facets/core/array/merge' require 'facets/more/openobject' #require 'facets/more/inheritor' require 'facets/more/nullclass' # = Annotation # # 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. # # Annotation is an OpenObject, and is used across the board for keeping annotations. # # Annotation class serves for both simple and inherited cases depending on whether # a base class is given. # # == Synopsis # # 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 # 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 ) p sym 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] parent = ans[name] break if parent end end if parent return table[name] = parent.dup #vs. object_class.new? end end return super( name ) end iob = dup super return inheritance if iob != self #return inheritance.__send__( sym, *args, &blk ) return inheritance.method_missing( 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 unless inh.empty? a = Annotation.new #( inh.pop.to_h ) #inh.push( a ) #.dup ) inh.reverse.inject(a){ |memo, i| memo.send(:merge, i) } return a end nil end end # This is a simple decorator for accessing a module's # method annotations (and it's own annotations). class Annotator #:nodoc: # Privatize a few Kernel methods that are most likely to clash, # but arn't essential here. TODO Maybe more in this context? private :class, :clone, :display, :type, :method, :to_a, :to_s # 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( harg=nil ) @_annotation ||= Annotation.new return @_annotation unless harg @_annotation.send(:update, harg) end #-- # Not sure about this. #++ def ann( *args ) #return annotation unless harg #annotation.send(:update, harg) #if singleton_class? (class << self; self; end).ann( *args ) #else # self.class.ann( *args ) #end 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 annotation( harg=nil ) @_annotation ||= Annotation.new.inherits( self ){ |anc| anc.annotation } return @_annotation unless harg @_annotation.send(:update,harg) if harg end def method_annotation( key=nil ) @_method_annotation ||= {} return @_method_annotation unless key key = key.to_sym if a = @_method_annotation[key] return a 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.send(:update,hsh) next end note = annotated?( key ) if note and null != note note.send(:update,hsh) else define_annotation( key, hsh ) end end return harg.keys else # name, hash format args.each do |key| if key == :self annotation.send(:update,harg) next end note = annotated?( key ) if note and null != note note.send(:update,harg) else define_annotation( key, harg ) end end return args end end #def ann end #class ::Module # _____ _ # |_ _|__ ___| |_ # | |/ _ \/ __| __| # | | __/\__ \ |_ # |_|\___||___/\__| # =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 =end