require 'yaml' require 'facet/hash/traverse' require 'facet/string/tabto' require 'facet/dir/self/ascend' require 'facet/kernel/require_all' require 'facet/basicobject' # Project information, generally read from a file. # Simply by calling 'ProjectInfo.load'. # # ProjectInfo is a Singleton. Access it via the # ProjectInfo.instance method. class ProjectInfo INFO_FILES = [ 'ProjectInfo', 'ReapFile', 'projectinfo', 'reapfile' ] class << self # Singelton Pattern. private :new def instance( *args, &block ) @instance ||= new( *args, &block ) 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 nil unless info_file return File.join( info_dir, info_file ) end # 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 ) instance.read( fpath ) 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 @info_dir = Dir.pwd #? @info_file = nil #validate defaults self end # Load project information from YAML file. def read( fpath ) 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] } #validate defaults @info.each do |key, value| case value when Reap::Task value.task_name = key end end 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 #( *fields ) val = true (puts "NAME is required in project information file."; val=false) unless info['name'] (puts "VERSION is required in project information file."; val=false) unless info['version'] exit -1 unless val 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 # Convert to a CascadinOpenObject. def to_cascading_open_object CascadingOpenObject.new( @info ) end # Information fetch. def [](name) info[name] end # Information store. def []=(name, x) info[name] = x end def each( &block ) @info.each( &block ) end # Information to hash. def to_h @info.dup 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