# = Fileable
#
# Make File-esque classes. Fileable makes it easy to
# create classes that can load from files.
#
# Class level mixin for loading/opening file classes.
# You will generally want to use extend with this.
#
# NOTE: This is an expiremental library, and is still
#       undergoing revision.
#
# = Copying
#
# Copyright (c) 2007 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.
#
# == Authors
#
# * Thomas Sawyer
#
# == Todo
#
# * Consider cachable version of Fileable.


# = Fileable
#
# Make File-esque classes. Fileable makes it easy to
# create classes that can load from files.
#
# Class level mixin for loading/opening file classes.
# You will generally want to use extend with this.
#
# NOTE: This is an expiremental library, and is still
#       undergoing revision.
#
#
module Fileable

  # When included extend DSL too.

  def self.included(base)
    base.extend DSL
  end

  # Store raw content of file.

  #attr_reader :content

  # New fileable object. By default this is called
  # by #read passing the file contents. Override it
  # if need is differnt.

  def initialize(content)
    @content = content
  end

  # Override this if reading is differnt.

  def read(file)
    self.file = file if defined?(:file=)
    initialize(File.read(file))
  end

  #
  module DSL

    # While this doesn't allpy to classes, for modules
    # it is needed to keep the DSL inheritance going.

    def included(base)
      base.extend DSL
    end

    # Override this with the name or name-glob of
    # the default file. If no default, return nil.

    def filename; nil; end

    # Load from file(s).

    def open(path=nil)
      file = file(path)
      if file
        fobj = new
        fobj.send(:read, file)
        return fobj
      end
    end

    # An initializer that can take either a File, Pathname
    # or raw data. This works much like YAML::load does.
    # Unlike +open+, +load+ requires an exact path parameter.

    def load(path_or_data)
      case path_or_data
      when File
        open(path_or_data.path)
      when Pathname
        open(path_or_data.realpath)
      else
        new(path_or_data)
      end
    end

    # Lookup file.

    def lookup(name=nil)
      file = locate(name)
      file ? open(file) : nil #raise LoadError
    end

    # Locate file (case insensitive).

    def locate(name=nil)
      name ||= filename
      raise LoadError unless name
      Dir.ascend(Dir.pwd) do |dir|
        match = File.join(dir, name)
        files = Dir.glob(match, File::FNM_CASEFOLD)
        if file = files[0]
          return file
        end
      end
      return nil
    end

    # Find file. The +path+ has to be either the
    # exact path or the directory where a
    # standard-named file resides.

    def file(path=nil)
      if !path
        raise LoadError unless filename
        path = filename
      elsif File.directory?(path)
        raise LoadError unless filename
        path = File.join(path, filename)
      end
      if file = Dir.glob(path, File::FNM_CASEFOLD)[0]
        File.expand_path(file)
      else
        raise Errno::ENOENT
      end
    end

    # Load cache. PackageInfo is multiton when loaded by file.

    def load_cache
      @load_cache ||= {}
    end
  end
end

