lib/reap/tasks.rb in reap-5.10.10 vs lib/reap/tasks.rb in reap-6.0.0

- old
+ new

@@ -1,13 +1,674 @@ +require 'reap/taskutils' -require 'reap/task/fileperm' -require 'reap/task/test' -require 'reap/task/testext' -require 'reap/task/rdoc' -require 'reap/task/announce' -require 'reap/task/package' -require 'reap/task/publish' -require 'reap/task/info' -require 'reap/task/install' -#require 'reap/task/noop' -#require 'reap/webpublish' +# 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.version ||= master.version + data.from ||= master.email + Reap::Announce.new( data ).write_and_email + 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 + + # == 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 + data.trunk ||= master.trunk + 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.options[:name] ||= master.name + data.options[:version] ||= master.version + Reap::Installer.new( data ).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/**/*' # TODO maybe check trunk too + + 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 + data.trunk ||= master.trunk + 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. + # + # 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 + # + + 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 + data.trunk ||= master.trunk + + 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 + data.trunk ||= master.trunk + + 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.trunk ||= master.trunk || '.' + data.options ||= [] + + unless provide_setup_rb( data.trunk ) + 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 + Dir.chdir( data.trunk ) do + success = sh( exe ) + end + + 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 + + data.trunk ||= master.trunk + + Reap::Test.new( data ).call + end + + end + +end #module Tasks