module Linecook

  # Attributes provides a context for specifying default attributes.  For
  # example:
  #
  #   attributes = Attributes.new
  #   attributes.instance_eval %{
  #     attrs['a'] = 'A'
  #     attrs['b']['c'] = 'C'
  #   }
  #
  #   attributes.to_hash
  #   # => {'a' => 'A', 'b' => {'c' => 'C'}}
  #
  # Note that attrs is an auto-filling nested hash, making it easy to set
  # nested attributes, but it is not indifferent, meaning you do need to
  # differentiate between symbols and strings.  Normally strings are
  # preferred.
  class Attributes
    # A proc used to create nest_hash hashes
    NEST_HASH_PROC = Proc.new do |hash, key|
      hash[key] = Hash.new(&NEST_HASH_PROC)
    end

    class << self
      # Returns an auto-filling nested hash.
      def nest_hash
        Hash.new(&NEST_HASH_PROC)
      end

      # Recursively disables automatic nesting of nest_hash hashes.
      def disable_nest_hash(hash)
        if hash.default_proc == NEST_HASH_PROC
          hash.default = nil
        end

        hash.each_pair do |key, value|
          if value.kind_of?(Hash)
            disable_nest_hash(value)
          end
        end

        hash
      end
    end

    # An auto-filling nested hash
    attr_reader :attrs

    def initialize
      @attrs = Attributes.nest_hash
    end

    # A list of file extnames that may be loaded by load_attrs
    def load_attrs_extnames
      %w{.rb .yml .yaml .json}
    end

    # Loads the attributes file into attrs. The loading mechanism depends on
    # the file extname:
    #
    #   .rb: evaluate in the context of attributes
    #   .yml,.yaml,.json: load as YAML and merge into attrs
    #
    # All other file types raise an error.
    def load_attrs(path)
      case File.extname(path)
      when '.rb'
        instance_eval(File.read(path), path)
      when '.yml', '.yaml', '.json'
        attrs.merge!(YAML.load_file(path))
      else
        raise "unsupported attributes format: #{path.inspect}"
      end

      self
    end

    # Disables automatic nesting and returns attrs.
    def to_hash
      Attributes.disable_nest_hash(attrs)
    end
  end
end