# = 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. # # == History # # LOG 2006-11-07 trans Created this ultra-concise version of annotations. # # == Notes # # TODO Might be nice to have a default object of annotation, eg. the next # method defined, like how +desc+ annotates a rake +task+. # # TODO The ann(x).name notation is kind of nice. Would like to add that # back-in if reasonable. Basically this require heritage to be an OpenHash # rather than just a hash. # # == Authors & Contributors # # * Thomas Sawyer # Author:: Thomas Sawyer # Copyright:: Copyright (c) 2005 Thomas Sawyer # License:: Ruby License require 'facets/core/hash/to_h' require 'facets/core/hash/symbolize_keys' require 'facets/core/hash/op_add' # = 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 #-- # By using a global veriable rather the definining a class instance variable # for each class/module, it is possible to quicky scan all annotations for the # entire system. To do the same without this would require scanning through # the ObjectSpace. (Still which is better?) # #$annotations = Hash.new { |h,k| h[k] = {} } #++ class Module def annotations #$annotations[self] @annotations ||= {} end def heritage(ref) ref = ref.to_sym ancestors.inject({}) { |memo, ancestor| ancestor.annotations[ref] ||= {} ancestor.annotations[ref] + memo } end # Set or read annotations. def ann( ref, keys_or_class=nil, keys=nil ) return heritage(ref) unless keys_or_class or keys if Class === keys_or_class keys ||= {} keys[:class] = keys_or_class else keys = keys_or_class end if Hash === keys ref = ref.to_sym annotations[ref] ||= {} annotations[ref].update(keys.symbolize_keys) else key = keys.to_sym heritage(ref)[key] end end # To change an annotation's value in place for a given class or module # it first must be duplicated, otherwise the change may effect annotations # in the class or module's ancestors. def ann!( ref, keys_or_class=nil, keys=nil ) return heritage(ref) unless keys_or_class or keys if Class === keys_or_class keys ||= {} keys[:class] = keys_or_class else keys = keys_or_class end if Hash === keys ref = ref.to_sym annotations[ref] ||= {} annotations[ref].update(keys.symbolize_keys) else key = keys.to_sym annotations[ref][key] = heritage(ref)[key].dup end end end # _____ _ # |_ _|__ ___| |_ # | |/ _ \/ __| __| # | | __/\__ \ |_ # |_|\___||___/\__| # =begin test require 'test/unit' class TestAnnotation1 < Test::Unit::TestCase class X def x1 ; end ann :x1, :a=>1 ann :x1, :b=>2 end def test_1_01 assert_equal( X.ann(:x1,:a), X.ann(:x1,:a) ) assert_equal( X.ann(:x1,:a).object_id, X.ann(:x1,:a).object_id ) end def test_1_02 X.ann :x1, :a => 2 assert_equal( 2, X.ann(:x1,:a) ) end end class TestAnnotation2 < Test::Unit::TestCase class X def x1 ; end ann :x1, :a=>1 ann :x1, :b=>2 end class Y < X ; end def test_2_01 assert_equal( Y.ann(:x1,:a), Y.ann(:x1,:a) ) assert_equal( Y.ann(:x1,:a).object_id, Y.ann(:x1,:a).object_id ) end def test_2_02 assert_equal( 1, Y.ann(:x1,:a) ) assert_equal( 2, Y.ann(:x1,:b) ) end def test_2_03 Y.ann :x1,:a => 2 assert_equal( 2, Y.ann(:x1,:a) ) assert_equal( 2, Y.ann(:x1,:b) ) end end class TestAnnotation3 < Test::Unit::TestCase class X ann :foo, Integer end class Y < X ann :foo, String end def test_3_01 assert_equal( Integer, X.ann(:foo, :class) ) end def test_3_02 assert_equal( String, Y.ann(:foo, :class) ) end end class TestAnnotation4 < Test::Unit::TestCase class X ann :foo, :doc => "hello" ann :foo, :bar => [] end class Y < X ann :foo, :class => String, :doc => "bye" end def test_4_01 assert_equal( "hello", X.ann(:foo,:doc) ) end def test_4_02 assert_equal( X.ann(:foo), { :doc => "hello", :bar => [] } ) end def test_4_03 X.ann(:foo,:bar) << "1" assert_equal( ["1"], X.ann(:foo,:bar) ) end def test_4_04 assert_equal( "bye", Y.ann(:foo,:doc) ) end def test_4_05 #assert_equal( nil, Y.ann(:foo,:bar) ) assert_equal( ["1"], Y.ann(:foo,:bar) ) end def test_4_06 Y.ann(:foo, :doc => "cap") assert_equal( "cap", Y.ann(:foo, :doc) ) end def test_4_07 Y.ann!(:foo,:bar) << "2" assert_equal( ["1", "2"], Y.ann(:foo,:bar) ) assert_equal( ["1", "2"], Y.ann(:foo,:bar) ) assert_equal( ["1"], X.ann(:foo,:bar) ) end end =end