require 'yaml' require 'facet/hash/traverse' require 'facet/string/tabto' require 'facet/dir/self/ascend' require 'facet/basicobject' #-- # NOTE At some point get past the use of the global variable perhaps? #++ class ProjectInfo INFO_FILES = [ 'ProjectInfo', 'ReapFile', 'projectinfo', 'reapfile' ] class << self # Load the project information from a file. Generally # no file needs to be specified; the file will be found # by ascending up the current path until a default # file name is found (eg. ProjectInfo or Reapfile). def load( fpath=nil, report=false ) if fpath new.read( fpath, report ) else new.read( find, report ) end end # Find project information file. def find info_dir, info_file = nil, nil Dir.ascend(Dir.pwd) do |info_dir| info_file = INFO_FILES.find{ |f| File.file?( File.join( info_dir, f ) ) } break if info_file end return File.join( info_dir, info_file ) end #def add_file( f ) # INFO_FILES.unshift( f ) #end end attr_reader :info, :info_stream, :info_dir, :info_file def initialize( &block ) @info = {} @info_stream = nil define( &block ) if block_given? $PROJECT_INFO = self end # Define project information programmatically. def define( &block ) return unless block @info = HashBuilder.new( &block ).to_h @info_stream = @info.to_yaml validate defaults self end # Load project information from YAML file. def read( fpath, report=true ) return unless fpath @info_dir = File.dirname( fpath ) @info_file = fpath #File.basename( fpath ) @info_stream = File.read( fpath ).strip @info = YAML::load( info_stream ).traverse{ |k,v| [k.to_s.downcase, v] } Dir.chdir(@info_dir) if report puts "(in #{Dir.pwd})" #unless dir == Dir.pwd end validate defaults self end # Update project information. def update( info=nil, &block ) if info @info.update( info.traverse{ |k,v| [k.to_s.downcase, v] } ) end if block_given? @info.update( HashBuilder.new( &block ).to_h ) end @info_stream = @info.to_yaml validate defaults self end # Project information file exists? (may need to change to info exists?) def exists? @info_file end # Validate project information. def validate raise "NAME is a required piece of information" unless info['name'] raise "VERSION is a required piece of informatiomn" unless info['version'] end # Project information defaults. def defaults self['title'] ||= self['name'].capitalize self['series'] ||= '1' self['date'] ||= Time.now.strftime("%Y-%m-%d") self['author'] ||= "Anonymous" self['maintainer'] ||= self['author'] self['arch'] ||= 'Any' self['license'] ||= 'Ruby/GPL' self['status'] ||= 'Beta' self['project'] ||= self['rubyforge'] ? self['rubyforge']['project'] : nil self['homepage'] ||= self['rubyforge'] ? self['rubyforge']['homepage'] : nil end # Information fetch. def [](name) info[name] end # Information sore. def []=(name, x) info[name] = x end # Information to hash. def to_h @info end end # Build a hash from missing method calls. class ProjectInfo::HashBuilder < BasicObject def initialize( blockstr=nil, &block ) @hash = {} @flag = {} raise "both string and block given" if blockstr and block_given? if blockstr instance_eval blockstr else instance_eval &block end end def to_h ; @hash ; end def method_missing( sym, *args, &block ) sym = sym.to_s.downcase if @hash.key?(sym) unless @flag[sym] @hash[sym] = [ @hash[sym] ] @flag[sym] = true end if block_given? @hash[sym] << self.__class__.new( &block ).to_h else @hash[sym] << args[0] end else if block_given? @hash[sym] = self.__class__.new( &block ).to_h else @hash[sym] = args[0] end end end end