module Anise

  # TODO: The ann(x).name notation is kind of nice. Would like to add that
  #       back-in if reasonable.

  # The Annotation provides a framework for annotating class and module related
  # objects, typically symbols representing methods, with arbitrary metadata.
  # These annotations do not do anything in themselves. They are simply data.
  # But they can be put to good use. For instance an attribute validator might
  # check for an annotation called :valid and test against it.
  #
  # The standard annotator is `:ann` and is the defualt value of  annotating
  # methods.
  #
  #   class X
  #     extend Anise::Annotations
  #
  #     ann :a, :desc => "A Number"
  #
  #     attr :a
  #   end
  #
  #   X.ann(:a, :desc)  #=> "A Number"
  #
  # As stated, annotations need not only annotate methods, they are
  # arbitrary, so they can be used for any purpose. For example, we
  # may want to annotate instance variables.
  #
  #   class X
  #     ann :@a, :valid => lambda{ |x| x.is_a?(Integer) }
  #
  #     def validate
  #       instance_variables.each do |iv|
  #         if validator = self.class.ann(iv)[:valid]
  #           value = instance_variable_get(iv)
  #           unless validator.call(value)
  #             raise "Invalid value #{value} for #{iv}"
  #           end
  #         end
  #       end
  #     end
  #   end
  #
  # Or, we could even annotate the class itself.
  #
  #   class X
  #     ann self, :valid => lambda{ |x| x.is_a?(Enumerable) }
  #   end
  #
  # Although annotations are arbitrary they are tied to the class or
  # module they are defined against.
  #
  # Creating custom annotators used to entail using a special `#annotator` method,
  # but this limited the way custom annotators could operate. The new way
  # is to define a custom class method that calls the usual `#ann` method,
  # but add in a namespace.
  #
  #   class X
  #     def self.cool(ref, *keys)
  #       ann "#{ref}/cool", *keys
  #     end
  #   end
  #
  #   X.cool(:a, :desc) #=> "Awesome!"
  #
  # The result is exactly the same as before, but now the custom annotator
  # has complete control over the process.
  #
  module Annotations

    #
    # Access to a class or module's annotations.
    #
    def annotations
      @annotations ||= Store.new(self)
    end

    #
    # Callback method. This method is called for each new annotation.
    #
    def annotation_added(ref, ns=:ann)
      super(ref, ns) if defined?(super)
    end

    # Get/set annotations.
    #
    # @examples
    #   ann :ref, :key=>value
    #   ann :ref/:ns, :key=>value
    #
    #   ann :ref, :key
    #   ann :ref/:ns, :key
    #
    def ann(ref, *keys)
      if ref.to_s.index('/')
        ref, ns = ref.to_s.split('/')
      else
        ns = :ann
      end
      ref, ns = ref.to_sym, ns.to_sym

      if keys.empty?
        annotations.lookup(ref, ns)
      else
        annotations.annotate(ns, ref, *keys)
      end
    end

    #
    # Get/set annotations in-place. Use this method instead of `#ann`
    # when performing mass updates.
    #
    def ann!(ref, *keys)
      if ref.to_s.index('/')
        ref, ns = ref.to_s.split('/')
      else
        ns = :ann
      end
      ref, ns = ref.to_sym, ns.to_sym

      if keys.empty?
        annotations[ref, ns]
      else
        annotations.annotate!(ns, ref, *keys)
      end
    end

  end

end

# Copyright (c) 2006 Rubyworks. All rights reserved. (BSD-2-Clause License)