=begin
 
$Title: Accessors $
$Author: transami $
$Date: 2004-10-06 13:56:57 $

= Description

  The Bill wants more from attributes.

= Usage

== Using Cast Attribute Accessors

  # simple type casting
  attr_reader :a => :to_s

  # fancy type casting
  attr_reader :a => 'to_s.capitalize'

  # a powerful reader!
  attr_reader :a => fn{ |x| eval x }

  # a writer that type checks (use type.rb)
  attr_writer :a => fn{ |x| big4 x }

  # backwards compatible, cast attributes go at the end
  attr_accessor :x, :y, :a => :to_s

  # they can also be grouped
  attr_accessor [:x, :y] => :to_s
  
== Using Accessor Shorthand

  A nice shorthand is provided.

    attr :r, :a, :a=, :w=
  
  This is the same as doing
  
    attr_reader :r
    attr_accessor :a
    attr_writer :w
  
  Also

    attr :b?, :b!
  
  creating
    
    def b?
      @b ? true : @b
    end
  
    def b!(x)
      @b.replace(x)
    end
  
  The shorthand notation can be used with casting too.
  
    attr :a => :to_s

== Using Return Value

  Attribute methods now return a list of the methods created. 
  This can then be passed to other useful methods.
  
    private attr :a

= Authenticate
  
  Copyright (C) 2002 T. Sawyer
  Ruby Distribute License
  
  This program is free software.
  You can distribute/modify this program under
  the terms of the Ruby Distribute License.
    
=end

#require 'facet/hash/update_each'

# Modify module attribute methods.
# *add define_attribute
# *redefine old attr_* methods
# *add attr_setter

class Module
  
  # Create an attribute method for reading an
  # instance variable. This is the same as the built
  # in method, which it replaces, but adds casting.
  #
  # Casting allows the addition of a method invocation
  # on the instance variable. It is defined using a hash
  # parameter, so all castings must come at the end of a
  # call to attr_reader.
  #
  #   require 'carat/attr'
  #
  #   attr_reader :a => :to_s
  #
  # _is equivalent to_
  #
  #   def a
  #     @a.to_s
  #   end
  #
  def attr_reader(*args)
    made = []
    if Hash === args.last
      args.concat( args.pop.to_a )
    end
    #hargs = (Hash === args.last ? args.pop : {})
    args.each { |a,c|
      a = a.to_s.strip
      c = ".#{c.to_s.strip}".chomp('.')
      module_eval %Q{ def #{a}; @#{a}#{c} ; end }
      made << "#{a}".to_sym
    }
    (@__atributes__ ||= []).concat( made )
    return *made
  end

  # Create an attribute method for writing to an
  # instance variable. This is the same as the built
  # in method, which it replaces, but adds casting.
  #
  # Casting allows the addition of a method invocation
  # on the instance variable. It is defined using a hash
  # parameter, so all castings must come at the end of a
  # call to attr_writer.
  #
  #   require 'carat/attr'
  #
  #   attr_writer :a => :to_s
  #
  # _is equivalent to_
  #
  #   def a=(x)
  #     @a = x.to_s
  #   end
  #
  def attr_writer(*args)
    made = []
    if Hash === args.last
      args.concat( args.pop.to_a )
    end
    #hargs = (Hash === args.last ? args.pop : {})
    args.each { |a,c|
      a = a.to_s.strip.chomp('=').chomp('!')
      c = ".#{c.to_s.strip}".chomp('.')
      module_eval %Q{ def #{a}=(x); @#{a}=x#{c}; end }
      made << "#{a}=".to_sym
    }
    (@__atributes__ ||= []).concat( made )
    return *made
  end

  # Create an attribute method for writing to an
  # instance variable. This is the same as the built
  # in method, which it replaces, but adds casting.
  #
  # Casting allows the addition of a method invocation
  # on the instance variable. It is defined using a hash
  # parameter, so all castings must come at the end of a
  # call to attr_writer.
  #
  #   require 'carat/attr'
  #
  #   attr_accessor :a => :to_s
  #
  # _is equivalent to_
  #
  #   def a
  #     @a.to_s
  #   end
  #
  #   def a=(x)
  #     @a = x.to_s
  #   end
  #
  def attr_accessor(*args)
    made = []
    if Hash === args.last
      args.concat( args.pop.to_a )
    end
    #hargs = (Hash === args.last ? args.pop : {})
    args.each { |a,c|
      a = a.to_s.strip.chomp('=')
      c = ".#{c.to_s.strip}".chomp('.')
      module_eval %Q{ def #{a}; @#{a}#{c} ; end }
      module_eval %Q{ def #{a}=(x); @#{a}=x#{c}; end }
      made << "#{a}".to_sym
      made << "#{a}=".to_sym
    }
    (@__atributes__ ||= []).concat( made )
    return *made
  end

  # Create an attribute method for boolean testing
  # of an instance variable in the form of _var?_.
  #
  #   require 'facet/module/attr_tester'
  #
  #   attr_tester :a
  #
  # _is equivalent to_
  #
  #   def a?
  #     @a ? true : @a
  #   end
  #
  # Casting is also supported (see attr_reader).
  #
  #   attr_tester :a => :evaluate
  #
  # _is equivalent to_
  #
  #   def a?
  #     @a.evaluate ? true : @a
  #   end
  #
  def attr_tester(*args)
    made = []
    if Hash === args.last
      args.concat( args.pop.to_a )
    end
    #hargs = (Hash === args.last ? args.pop : {})
    args.each { |a,c|
      a = a.to_s.strip.chomp('?')
      c = ".#{c.to_s.strip}".chomp('.')
      module_eval %Q{ def #{a}?; @#{a}#{c} ? true : @#{a}#{c}; end }
      made << "#{a}?".to_sym
    }
    (@__atributes__ ||= []).concat made
    return *made
  end

  # Create an attribute method for getting and setting an
  # instance variable.
  #
  #   require 'carat/attr'
  #
  #   attr_setter :a
  #
  # _is equivalent to_
  #
  #   def a(*args)
  #     if args.size > 0
  #       @a = args[0]
  #       self
  #     else
  #       @a
  #     end
  #   end
  #
  # Casting is supported on both getting and setting.
  #
  #   attr_setter :a => :to_s
  #
  # _is equivalent to_
  #
  #   def a(*args)
  #     @a = args[0].to_s if args.size > 0
  #     @a.to_s
  #   end
  #
  def attr_setter(*args)
    made = []
    if Hash === args.last
      args.concat( args.pop.to_a )
    end
    #hargs = (Hash === args.last ? args.pop : {})
    args.each { |a,c|
      a = a.to_s.strip
      c = ".#{c.to_s.strip}".chomp('.')
      module_eval %Q{
        def #{a}(*args)
          args.size > 0 ? ( @#{a}=args[0]#{c} ; self ) : @#{a}#{c}
        end
      }
      made << "#{a}".to_sym
    }
    (@__atributes__ ||= []).concat( made )
    return *made
  end
  
  # DOES ANYONE USE THIS OLD THING?
  #alias attribute attr

  # The wondrful shortcut.
  def attr(*args)
    # Allows compatibility with old definition, while also
    # extending the capabilites to allow multiple parameters.
    # This form does not allow casting though.
    if TrueClass === args.last or FalseClass === args.last or NilClass === args.last
      if args.pop
        args.concat( args.collect{ |a|
          a = a.to_s.strip
          case a[-1,1]
          when '=' 
            a[0..-2]
          when '?' 
            "#{a[0..-2]}="
          else
            "#{a}="
          end
        } )
      end
    end
    #
    made = []
    readers = []
    writers = []
    testers = []
    if Hash === args.last
      args.concat( args.pop.to_a )
    end
    args.each do |a,c|
      a = a.to_s.strip
      t = a.slice(-1,1)
      m = a.chomp('!').chomp('=').chomp('?')
      case t
      when '='
        readers << [m,c]
        writers << [m,c]
      when '?'
        testers << [m,c]
      when '!'
        writers << [m,c]
      else
        readers << [m,c]
      end
    end
    made.concat( [ attr_reader( *readers ) ] )
    made.concat( [ attr_writer( *writers ) ] )
    made.concat( [ attr_tester( *testers ) ] )
    return *made
  end
  
end