require 'yaml' require 'facet/hash/traverse' require 'facet/string/tabto' require 'facet/dir/self/ascend' require 'facet/kernel/require_all' require 'facet/basicobject' module Reap def self.register #( alternative_project_file=nil ) pi = ProjectInfo.load( nil, true ) pi.require_custom_tasks pi end def self.projectfile? ProjectInfo.instance.info_file end end # 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 # 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 fpath = find instance.read( fpath, 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 nil unless info_file 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 @info_dir = Dir.pwd #? @info_file = nil #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 #( *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 # Load custom tasks. def require_custom_tasks # Universal custom tasks for all projects. dir = File.join( Config::CONFIG['datadir'], 'reap/task' ) require_all( File.join(dir, '*') ) if File.directory?(dir) # Personal tasks for all projects. dir = File.expand_path( '~/.share/reap/task' ) require_all( File.join(dir, '*') ) if File.directory?(dir) # Project specific tasks. require_all('task/*') if File.directory?('task') 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 # 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