# = 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. # # 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 # # == Authors # # * Thomas Sawyer # * George Moschovitis # # == History # # * 2006-11-07 trans Created this ultra-concise version of annotations. # # == Todo # # * Might be nice to have a default object of annotation, eg. the next # method defined, like how +desc+ annotates a rake +task+. # * 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 OpenObject # rather than just a hash. # # == Copying # # Copyright (c) 2005 Thomas Sawyer, George Moschovitis # # 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. require 'facets/hash/to_h' require 'facets/hash/rekey' require 'facets/hash/op' # = 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.rekey) 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 return annotations[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.rekey) else key = keys.to_sym annotations[ref][key] = heritage(ref)[key].dup end end end # TITLE # # Annotated Attributes # # DESCRIPTION: # # This framework modifies the attr_* methods to allow easy # addition of annotations. # # COPYRIGHT: # # Copyright (c) 2005 Thomas Sawyer # # LICENSE: # # 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: # # - Thomas Sawyer # - George Moschovitis require 'facets/annotations.rb' require 'facets/inheritor.rb' # = Annotated Attributes # # This framework modifies the attr_* methods to allow easy addition of annotations. # It the built in attribute methods (attr, attr_reader, attr_writer and attr_accessor), # to allow annotations to added to them directly rather than requireing a separate # #ann statement. # # class X # attr :a, :valid => lambda{ |x| x.is_a?(Integer) } # end # # See annotation.rb for more information. # # NOTE This library was designed to be backward compatible with the # standard versions of the same methods. class ::Module inheritor :instance_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 alias :plain_reader :attr_reader alias :plain_writer :attr_writer alias :plain_accessor :attr_accessor code = '' [ :_reader, :_writer, :_accessor].each do |m| code << %{ def attr#{m}(*args) args.flatten! harg={}; while args.last.is_a?(Hash) harg.update(args.pop) end raise ArgumentError if args.empty? and harg.empty? if args.empty? # hash mode harg.each { |a,h| attr#{m}(a,h) } else klass = harg[:class] = args.pop if args.last.is_a?(Class) args.each { |a| plain#{m} a a = a.to_sym ann(a,harg) } instance_attributes!.concat( args ) #merge! # Use this callback to customize for your needs. if respond_to?(:attr_callback) attr_callback(self, args, harg) end # return the names of the attributes created return args end end } end class_eval( code ) # TODO Should attribute alias be kept? alias_method :attribute, :attr_accessor # Return list of attributes that have a :class annotation. # # class MyClass # attr_accessor :test # attr_accessor :name, String, :doc => 'Hello' # attr_accessor :age, Fixnum # end # # MyClass.instance_attributes # => [:test, :name, :age, :body] # MyClass.classified_attributes # => [:name, :age] def classified_attributes instance_attributes.find_all do |a| self.ann(a, :class) end end end