require 'reap/taskutils' # Tasks is the namespace in which to define task generators. module Tasks # Even task generators might use the task tools. include Reap::TaskUtils #-- # Help system has been deprecated for the time being. #def self.help( name, text ) # @help ||= {} # @help[name] = text #end #++ # Used to determine task generator availablity. #-- # TODO This is kind of a weak point in the design, I think. # And tasks themselves also need to have Rake's #needed? feature. #++ def self.available @available ||= {} end # Used to set task generator availablity condition. def self.avail( name, &cond ) available[name] = cond end # == Announce # # The announce task is intended for sending out a nice # formated email message to a mailing address, especially # mailing-lists. # # Announcement specific settings: # # server Email server to route message. # port Email server's port. # domain Email server's domain name. # account Email account name. # type Login type, either plain, cram_md5 or login. # secure Uses TLS security, true or false? # to Email address to send announcemnt. # from Email address sent from. # subject Subject of email message. # links Array of http links to related sites. # file File that contains announcement message. # memo Embedded announcement message. # slogan Motto for you project. # # Inherited settings: # # title Project title. # summary Brief one-line description. # description Long description of project. # homepage Project homepage web address. # def announce( name, &data ) require 'reap/class/announce.rb' desc "Email project announcement" unless desc task name do data = data.to_openobject data.title ||= master.title data.description ||= master.description data.version ||= master.version data.from ||= master.email Reap::Announce.new( data ).write_and_email end end # == Backup # # Backup your project to a backup directory. # The location of the backup will be under the # backup +dir+ given in the backup section of the # ProjectInfo file, then under the name of the # project's dir and a subdirectory of today's date. # For example, today reap itself was backed up to: # # ../BACKUPS/reap/2006-06-21/reap # # Backup specific settings: # # dir The backup directory (eg. '../BACKUPS'). def backup( name, &data ) desc "Backup project folder" unless desc task name do data = data.to_openobject if data.dir bdir = ::File.join( ::File.basename(Dir.pwd), Time.now.strftime("%Y_%m_%d") ) bdir = ::File.join( data.dir, bdir ) if File.exist?( bdir ) and not $FORCE tell bdir tell "Backup folder already exists. Use -f to overwrite." else if $PRETEND puts "rm -r #{bdir}" puts "mkdir -p #{bdir}" puts "cp -r #{Dir.pwd} #{bdir}" else FileUtils.rm_r( bdir ) if File.exist?( bdir ) FileUtils.mkdir_p( bdir ) FileUtils.cp_r( Dir.pwd, bdir) end tell "Completed backup to '#{bdir}'." end else tell "No backup 'dir' setting given for backup task." end end end # == Doap # # This task generates an XML DOAP project file. # # DOAP is an XML/RTF format for describing a project. It contains # much of the same information as Reap's ProjectInfo file, but is # more suitable to RESTful interapplication communications, like # RSS/Atom feeds --useful to project tracking sites. # # The task gets almost all of the required parametes from the root # node (i.e. master) of ProjectInfo file. def doap( name, &data ) require 'reap/class/doap.rb' desc "Generate DOAP project file" unless desc task name do data = data.to_openobject Reap::Doap.new( data.__merge__( master ) ).call end end avail :doap do ProjectInfo.exists? end # == ExTest (Extract Tests) # # This task scans every package script looking for sections of the form: # # =begin test # ... # =end # # With appropriate headers, it copies these sections to files in your # project's test/ dir, which then can be run using the Reap test task. # The exact directory layout of the files to be tested is reflected in # the test directory. You can then use Reap's test task to run the tests. # # Specific Settings: # # dir Specify the test directory. Default is 'test'. # files Specify files to extract. Default is 'lib/**/*.rb'. # def extest( name, &data ) require 'reap/class/extest.rb' desc "Extract embedded unit tests from scripts" unless desc task name do data = data.to_openobject Reap::ExTest.new( data ).extract end end # == File def file( name, &exe ) require 'reap/class/filer' name, source = preq_from_name( name ) task name do Reap::Filer.new( name, source, &exe ).build end end # == Info # # Displays the ProjectInfo file. This task is of little use # --it's easy enough to look at a ProjectInfo file with # less or your favorite editor. Mainly this task serves as # a development example. def info( name ) desc "Display ProjectInfo file" unless desc task name do puts ProjectInfo.instance.info_stream end end avail :info do ProjectInfo.exists? end #-- # # == Installer # # # # The installer task generates a specialized # # install.rb script specifically for your # # project. It currently does not support c/c++ # # ext/ compilation. # # # # template Template for installation. (See below) # # options Hash of variable options. Project name # # and version are automatically added this. # # # # The template is a special configuration for installation. # # It should be a literal string with each line containing # # four parts seprated by spaces. # # # # [type] [from] [glob] [to] # # # # There are five types: bin, lib, ext, etc (or conf) and data. # # # # The template use variables in the from of $name. These are filled # # in using the options hash. # # # # Here's a basic example of an entry: # # # # ins: !!installer # # template: | # # bin bin * . # # lib lib **/* $name/$version # # # # def installer( name, &data ) # # require 'reap/class/installer' # # desc "Generate install.rb script" unless desc # # task name do # data = data.to_openobject # # data.name ||= master.name # data.version ||= master.version # # i = Reap::Installer.new( data ) # puts i.compile #_and_write # end # # end #++ # == Count # # Count the file and lines of code in your project. # # dir The directory of scripts to count. # def count( name, &data ) desc "Line count project scripts" unless desc task name do data = data.to_openobject incl = data.include || 'lib/**/*' fc, l, c, t, bt, r, rb = 0, 0, 0, 0, false, 0, false Dir.glob( incl ).each do |fname| next unless fname =~ /.*rb/ # TODO should this be done? fc += 1 File.open( fname ) do |f| while line = f.gets l += 1 next if line =~ /^\s*$/ case line when /^=begin\s+test/ tb = true; t+=1 when /^=begin/ rb = true; r+=1 when /^=end/ r+=1 if !(rb or tb) (rb = false; r+=1) if rb (tb = false; t+=1) if tb when /^\s*#/ r += 1 else c+=1 if !(rb or tb) r+=1 if rb t+=1 if tb end end end end s = l - c - r puts "FILES: #{fc}, LINES: #{l}, CODE: #{c}, DOC: #{r}, TEST: #{t}, SPACE: #{s}" end end # == Manifest # # Create a manifest file for the package. Presently it is a very simple # md5 + filename manifest. In the future this will be exanded to build # a varity of manifest formats. # # Task specific settings: # # include Files to include # exclude Files to exclude from the included. # def manifest( name, &data ) require 'reap/class/manifest' desc "Create a MANIFEST file for this package" unless desc task name do data = data.to_openobject Reap::Manifest.new( data ).generate end end # == Package # # This task creates standard .zip, .tgz, or .tbz # packages, plus .gem or .deb distributions. # # Builds distribution packages. The package task supports # tar.gz, tar.bz2, zip source packages and gem, pacman and # debian ditribution packages. # # Task specific settings: # # dir Directory in which to store distributions. # include Files to include in distribution. # exclude Files to exclude from those. # distribute List of distribution types desired. Eg. # tgz, tar.gz, tbz, tar.bz2, zip # gem, deb, pac # project Project name. # category Software category. # architecture Can be any, i368, i686, ppc, etc. # dependencies List of packages this program depends. # recommends List of packages that can be used with this package. # replaces List of packages this one replaces. # executables Executable files in this distribution. # rules (see below) # # RubyGems specific settings: # # autorequire # platform # require_paths # # The package task also has subsection for each type of distribution. # These can be used to override settings in the package information # if they in some way differ. Possible subsections are: # # gems # pacman # debian # # Finally there is one last parameter that you can use for creating packages # called 'rules'. The rules setting allows you to define how files are # copied into the distribution package, so instead of a one to one copy # of the included files, you can actually have a file placed in a different # location within the distribution. This can be very handy if you wish to # develop you project with one layout, but need to distribute it with another. # # The rules parameter is a literal string that consists of one rule per line. # A line consists three space separated entries. # # from_path, file_glob, to_path # # If no 'to_path' is given then it is considered the same as the 'from_path'. # It also supports two variables $name and $version which will be substitued # any of these entries. Here is a possible example: # # rules: | # lib **/* lib/$name/$version # # This will move any file under lib/ to the equivalent location under lib/$name/$version/. # The default set of rules is a mirror image transfer, spelled out it would be: # # rules: | # bin * # ext **/* # lib **/* # data **/* # conf **/* # # If your using Rolls against a normal project folder the alterntive is to create # versioned paths, probably as follows: # # rules: | # ext/$name **/* ext/$name/$version # lib/$name **/* lib/$name/$version # data/$name **/* ext/$name/$version # conf/$name **/* ext/$name/$version ? # # Please note that the rules parameter is a new feature and is still considered beta. def package( name, &data ) require 'reap/class/package' desc "Create distribution packages" unless desc task name do data = data.to_openobject data.name ||= master.name data.title ||= master.title data.version ||= master.version data.status ||= master.status data.series ||= master.series data.author ||= master.author data.maintainer ||= master.maintainer data.email ||= master.email data.summary ||= master.summary data.architecture ||= master.architecture data.license ||= master.license data.project ||= master.project data.homepage ||= master.homepage Reap::Package.new( data ).generate_packages end end # == Publish # # Publish documents to hosting service. Three means of # publishing are current supported, ftp uploading, scp, # and web convenience option that recognizes particular # hosts (only RubyForge is currently supported). # # type Type of publishing, 'web', 'scp' or 'ftp'. # host Host server, default is 'rubyforge.org' # username Username for host. # dir Directory of files to publish. Or, # copy Files to publish using from and to. # project Project name (used for Rubyforge) # # If the type is 'scp' or 'ftp' you will also need to provide # the root directory parameter. # # root Document root directory at host. # # The dir parameter allows you to simply specify a local # directory, the contents of which will be published to # host's document root location (directory tree intact). # # If you need more control over which files to publish # where, you can use the copy parameter instead. Provide # an array of pattern strings in the form of "{from} {to}". # If the desitination is the host's document root you do # not need to specify the {to} part. For example: # # copy: [ 'web/*', 'doc/api/* doc/api' ] # # The first copies the files under your project's web directory # to the host's document root. The second copies your projects # doc/api files to the doc/api location on the host. # # When using the 'scp' type the internal template used for # the outbound destination is 'username@host:root/'. def publish( name, &data ) require 'reap/class/publish.rb' desc "Publish documents to the web or host" unless desc task name do data = data.to_openobject data.project ||= master.rubyforge.project || master.name data.username ||= master.rubyforge.username Reap::Publish.new( data ).publish end end # == RDoc # # This task generates RDoc API documentation from source # comments. # # Task specific settings: # # dir Directory to store documentation [doc]. # main File to use as main page of RDocs. # template Which RDoc template to use. # include Files to include in RDocs. # exclude Files to exclude from those. # options Pass-thru extra options to RDoc command. # # Inherited settings: # # title Project title to use in RDocs. # def rdoc( name, &data ) require 'reap/class/rdoc' desc "Generate RDoc API documentation" unless desc task name do data = data.to_openobject data.title ||= master.title Reap::RDoc.new( data ).call end end # == Release # # This task releases files to RubyForge --it should work with other # GForge instaces or SourceForge clones too. # # While defaults are nice, you may want a little more control. You can # specify additional attributes: # # dir Distribution directory # exclude Distribution types to exclude # host URL of host service # username Username of host service # project Project name at host # package Package name # date Date of release (defaults to Time.now) # groupid Group id number # processor Processor/Architecture (Any, i386, PPC, etc.) # release Release name (default is "%s") # is_public Public release? # changelog Change log file # notelog Release notes file # # The release option can be a template by using %s in the # string. The version number of your project will be sub'd # in for the %s. This saves you from having to update # the release name before every release. def release( name, &data ) require 'reap/class/release' desc "Release distribution files" unless desc task name do data = data.to_openobject data.version = master.version data.project ||= master.rubyforge.project || master.name data.username ||= master.rubyforge.username data.package ||= master.rubyforge.package data.groupid ||= master.rubyforge.groupid Reap::Release.new( data ).release end end # == Scaffold # # Generates a project directory layout within the current # directory. The scaffolding includes all the standard # features such as directories lib/ bin/ doc/ # and files such as README. # # To use this task you should first create an empty # directory and than change into it. This command will # not create a new project directory. As a safegaurd, this # command will not generate scaffolding in a directory with # files already present, unless you use the -f (force) option. # # There are currently two types of supported scaffoldings: # 'standard', which is the default if no type is given, # and 'subversion' which creates a "trunk" hierarchy. def scaffold( name ) desc "Generate a project directory layout" unless desc task name do |type| content = Dir.entries('.') - [ '.', '..' ] if not content.empty? and not $FORCE tell "Directory already has content. Use -f option to force scaffolding." return nil end type ||= 'standard' type = 'standard' if type == 'std' type = 'subversion' if type == 'svn' # gems workaround if dir = Gem.gempath('reap') dir = File.join( dir, 'data', 'reap', 'scaffold', type ) else dir = File.join( ::Config::CONFIG['datadir'], 'reap', 'scaffold', type ) end # unless File.directory?( dir ) tell "Unrecognized project type." else #FileUtils.mkdir_p( name ) FileUtils.cp_r( File.join( dir, '.'), '.' ) tell "Project ready." end end end avail :scaffold do ! ProjectInfo.exists? end # == Setup # # This task manually installs a project using setup.rb. # If setup.rb doesn't exist it will be created. def setup( name, &data ) desc "Locally install package using setup.rb" unless desc task name do data = data.to_openobject data.options ||= [] unless provide_setup_rb puts "Setup.rb is missing. Forced to cancel task." return nil end puts "Reap is shelling out work to setup.rb..." #-- # SHELL OUT! This will be fixed with swtich to Reap's installer instead of setup.rb. #++ exe = %{ruby setup.rb} exe << ' -q ' unless $VERBOSE exe << data.options.join(' ') exe << ' all' #success = false success = sh( exe ) puts "Setup complete!" if success end end # == Template # # Creates an empty ProjectInfo file in the current directory. # It will not overwrite a ProjectInfo file if one is already # present. Unlike 'scaffold', this is especially useful for # pre-existing projects. def template( name ) require 'facet/gem/self/gempath' desc "Create a ProjectInfo template" unless desc task name do f = nil if ::ProjectInfo::INFO_FILES.any?{ |f| File.exists?(f) } puts "Project file '#{f}' already exists." return end filename = 'ProjectInfo' # if using gems if dir = Gem.gempath('reap') dir = File.join( dir, 'data', 'reap', 'scaffold', 'standard' ) else dir = File.join( ::Config::CONFIG['datadir'], 'reap', 'scaffold', 'standard' ) end f = File.join( dir, filename ) raise "ProjectInfo template file #{f} is missing." unless File.file?( f ) # copy FileUtils.install( f, '.' ) tell "#{filename} created. You'll need to fill it out." end end avail :template do ! ProjectInfo.exists? end # == Test # # The Reap test class runs each test in it's own # proccess, making for a more pure test facility. # Reap runs each test in a separate proccess to aviod # potential conflicts between scripts. # # files Test files (eg. test/tc_**/*.rb) # Defaults to typcial selection. # # libs List of lookup directories to include in # load path. './lib' is always included. # # live Flag to quickly deactive use of local libs. # Test against installed files instead. # # requires List of any files to pre-require. # # NOTE This works well enough but it is a tad delicate. # It actually marshals test results across stdout->stdin # shell pipe. One consequence of this is that you can't # send debug info to stdout in your tests (including #p and #puts). def test( name, &data ) require 'reap/class/test' desc "Run unit-tests (each in a separate process)" unless desc task name do data = data.to_openobject Reap::Test.new( data ).call end end end #module Tasks