jandot-biorake 1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.textile +122 -0
- data/Rakefile +10 -0
- data/biorake.gemspec +42 -0
- data/lib/biorake.rb +77 -0
- data/sample/Rakefile +108 -0
- data/sample/individuals.txt +5 -0
- data/sample/intensities.txt +25 -0
- data/sample/probes.txt +5 -0
- data/test/capture_stdout.rb +26 -0
- data/test/event_task_creation.rb +23 -0
- data/test/file_creation.rb +32 -0
- data/test/test_event_task.rb +384 -0
- data/test/test_event_task_timestamps.rb +86 -0
- data/test/test_event_task_with_file.rb +72 -0
- metadata +77 -0
data/README.textile
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
h1. Why?
|
2
|
+
|
3
|
+
There is some interest in the bioinformatics community for using rake
|
4
|
+
as a workflow tool (see e.g. this "blog post from BioinformaticsZen":http://www.bioinformaticszen.com/2008/05/organised-bioinformatics-exp
|
5
|
+
).
|
6
|
+
|
7
|
+
Rake could be ideal for this type of work: a typical workflow will
|
8
|
+
take data and perform a first set of conversions on it (i.e. a task),
|
9
|
+
followed by a second set of conversions (that is dependent on the
|
10
|
+
first task), and so on. And obviously, bioinformaticians want to keep their data
|
11
|
+
in databases rather than files...
|
12
|
+
|
13
|
+
A typical Rakefile could look like this:
|
14
|
+
<pre>
|
15
|
+
task :001_load_data do
|
16
|
+
<load data in database>
|
17
|
+
end
|
18
|
+
|
19
|
+
task :002_calculate_averages => [:001_load_data] do
|
20
|
+
<update data in database>
|
21
|
+
end
|
22
|
+
|
23
|
+
task :003_make_histogram_of_averages => [:002_calculate_averages] do
|
24
|
+
<do stuff>
|
25
|
+
end
|
26
|
+
</pre>
|
27
|
+
|
28
|
+
The trouble is that there is no way yet to check whether a task has to
|
29
|
+
be rerun or not, because there are no timestamps. Regular rake will
|
30
|
+
rerun all three tasks from the example above, regardless if some of
|
31
|
+
them have already been completed.
|
32
|
+
|
33
|
+
BioRake adds this task timestamp functionality to rake for working with
|
34
|
+
databases. The functionality needed is very similar to the one available for
|
35
|
+
FileTasks.
|
36
|
+
|
37
|
+
So if we had reloaded the data (001), the timestamp for that task in a
|
38
|
+
metadata would be later than the one for task 002. As a result, task
|
39
|
+
002 would automatically have to be rerun if we were to run task 003.
|
40
|
+
|
41
|
+
h1. Implementation
|
42
|
+
|
43
|
+
I've started to implement an additional type of task, called
|
44
|
+
*event*. The above snippet from a Rakefile would actually contain
|
45
|
+
|
46
|
+
<pre>
|
47
|
+
event :001_load_data do
|
48
|
+
...
|
49
|
+
end
|
50
|
+
|
51
|
+
event :002_calculated_averages => [:001_load_data] do
|
52
|
+
...
|
53
|
+
end
|
54
|
+
</pre>
|
55
|
+
instead of using the *task* tag.
|
56
|
+
|
57
|
+
Similar to a FileTask, *timestamps* are used to check if certain tasks
|
58
|
+
have to be re-run or not. FileTasks have the advantage that every file
|
59
|
+
has a timestamp. To implement this the metadata of event completion
|
60
|
+
times is stored in the .rake directory inside the current directory.
|
61
|
+
|
62
|
+
A *event task* automatically:
|
63
|
+
|
64
|
+
# checks the metadata to see if the task has already been run
|
65
|
+
# if so: are there any prerequisites with timestamps that are newer than the task
|
66
|
+
itself?
|
67
|
+
# (re)run the task if necessary
|
68
|
+
# update the metadata
|
69
|
+
|
70
|
+
To re-run all tasks from scratch issue a Rake::EventTask.clean or simply
|
71
|
+
<pre>
|
72
|
+
rm -rf .rake
|
73
|
+
</pre>
|
74
|
+
to reset the metadata to before any events have occured.
|
75
|
+
|
76
|
+
h1. Status
|
77
|
+
|
78
|
+
Even though the tests seem to run and I've tried some things out, I
|
79
|
+
can't guarantee production-level stability (well: call it beta). Use
|
80
|
+
at your own risk.
|
81
|
+
|
82
|
+
h1. Sample
|
83
|
+
|
84
|
+
The sample/ directory contains an example Rakefile. Suppose a
|
85
|
+
researcher has intensities for a group of individuals on a number of
|
86
|
+
probes. This information should be loaded into a database with the
|
87
|
+
tables _individuals_, _probes_ and _intensities_.
|
88
|
+
|
89
|
+
As the _intensities_ table contains foreign keys for individual and
|
90
|
+
probe, the _individuals_ and _probes_ tables have to be loaded
|
91
|
+
*before* the intensities can be loaded.
|
92
|
+
|
93
|
+
In rake-speak, this would look like:
|
94
|
+
<pre>
|
95
|
+
event :load_probes do
|
96
|
+
_load the actual data_
|
97
|
+
end
|
98
|
+
|
99
|
+
event :load_individuals do
|
100
|
+
_load the actual data_
|
101
|
+
end
|
102
|
+
|
103
|
+
event :load_intensities => [:load_probes, :load_individuals] do
|
104
|
+
_load the actual data_
|
105
|
+
end
|
106
|
+
</pre>
|
107
|
+
|
108
|
+
In a later step, the researcher might want to calculate the average
|
109
|
+
intensity per probe. This would be a new task that depends on the
|
110
|
+
intensities being loaded:
|
111
|
+
|
112
|
+
<pre>
|
113
|
+
event :calculate_averages => [:load_intensities] do
|
114
|
+
_calculate averages and store in probes table_
|
115
|
+
end
|
116
|
+
</pre>
|
117
|
+
|
118
|
+
Here, we call the database that will contain the data _sample.sqlite3_. The
|
119
|
+
_metadata_ about completed events is stored in the .rake directory.
|
120
|
+
|
121
|
+
Try a _rake -T_...
|
122
|
+
|
data/Rakefile
ADDED
data/biorake.gemspec
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'biorake'
|
3
|
+
s.version = "1.0"
|
4
|
+
|
5
|
+
s.authors = ["Jan Aerts","Charles Comstock"]
|
6
|
+
s.email = "jan.aerts at gmail.com"
|
7
|
+
s.homepage = "http://github.com/jandot/biorake"
|
8
|
+
s.summary = "Extension to rake to allow for timestamp usage on tasks that don't alter files"
|
9
|
+
s.description = "Extension to rake to allow for timestamp usage on tasks that don't alter files (e.g. loading data in a database)"
|
10
|
+
|
11
|
+
s.platform = Gem::Platform::RUBY
|
12
|
+
s.files = [
|
13
|
+
"biorake.gemspec",
|
14
|
+
"lib/biorake.rb",
|
15
|
+
"Rakefile",
|
16
|
+
"README.textile",
|
17
|
+
"sample/individuals.txt",
|
18
|
+
"sample/intensities.txt",
|
19
|
+
"sample/probes.txt",
|
20
|
+
"sample/Rakefile",
|
21
|
+
"test/capture_stdout.rb",
|
22
|
+
"test/event_task_creation.rb",
|
23
|
+
"test/file_creation.rb",
|
24
|
+
"test/test_event_task.rb",
|
25
|
+
"test/test_event_task_timestamps.rb",
|
26
|
+
"test/test_event_task_with_file.rb"
|
27
|
+
]
|
28
|
+
|
29
|
+
s.add_dependency('rake')
|
30
|
+
|
31
|
+
s.has_rdoc = false
|
32
|
+
|
33
|
+
s.test_files = [
|
34
|
+
"test/test_event_task.rb",
|
35
|
+
"test/test_event_task_timestamps.rb",
|
36
|
+
"test/test_event_task_with_file.rb"
|
37
|
+
]
|
38
|
+
|
39
|
+
s.require_path = 'lib'
|
40
|
+
s.autorequire = 'rake'
|
41
|
+
|
42
|
+
end
|
data/lib/biorake.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
module Rake
|
5
|
+
|
6
|
+
# #########################################################################
|
7
|
+
# An EventTask is a task that includes time based dependencies with no
|
8
|
+
# associated file. Timestamps are contained in the .rake
|
9
|
+
# directory. If any of a DataTask's prerequisites have a timestamp
|
10
|
+
# that is later than the represented by this task, then the file
|
11
|
+
# must be rebuilt (using the supplied actions).
|
12
|
+
#
|
13
|
+
class EventTask < FileTask
|
14
|
+
|
15
|
+
# Is this data task needed? Yes if it doesn't exist, or if its time stamp
|
16
|
+
# is out of date.
|
17
|
+
def needed?
|
18
|
+
return true if !File.exist?(taskfn)
|
19
|
+
return true if out_of_date?(timestamp)
|
20
|
+
false
|
21
|
+
end
|
22
|
+
|
23
|
+
# Time stamp for data task.
|
24
|
+
def timestamp
|
25
|
+
if(File.exist?(taskfn))
|
26
|
+
File.mtime(taskfn)
|
27
|
+
else
|
28
|
+
Rake::EARLY
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def execute(args=nil)
|
33
|
+
super(args)
|
34
|
+
# And save the timestamp in storage
|
35
|
+
EventTask.touch(name.to_s)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def taskfn
|
41
|
+
".rake/"+name.to_s
|
42
|
+
end
|
43
|
+
|
44
|
+
# ----------------------------------------------------------------
|
45
|
+
# Task class methods.
|
46
|
+
#
|
47
|
+
class << self
|
48
|
+
# Apply the scope to the task name according to the rules for this kind
|
49
|
+
# of task. File based tasks ignore the scope when creating the name.
|
50
|
+
def scope_name(scope, task_name)
|
51
|
+
task_name
|
52
|
+
end
|
53
|
+
|
54
|
+
def touch(fn)
|
55
|
+
FileUtils.mkdir_p ".rake"
|
56
|
+
FileUtils.touch ".rake/#{fn}"
|
57
|
+
end
|
58
|
+
|
59
|
+
def clean
|
60
|
+
FileUtils.rm_rf ".rake"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end # class Rake::EventTask
|
64
|
+
end
|
65
|
+
|
66
|
+
# Declare an event task.
|
67
|
+
#
|
68
|
+
# Example:
|
69
|
+
# event :load_probes => [:load_individuals] do
|
70
|
+
# File.open("my_file.txt").each do |line|
|
71
|
+
# probe_name, individual_id = line.chomp.split(/\t/)
|
72
|
+
# Probe.new(:name => probe_name, :individual_id => individual_id).new.save
|
73
|
+
# end
|
74
|
+
# end
|
75
|
+
def event(*args, &block)
|
76
|
+
Rake::EventTask.define_task(*args, &block)
|
77
|
+
end
|
data/sample/Rakefile
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
require '../lib/biorake.rb'
|
2
|
+
begin
|
3
|
+
require 'rubygems'
|
4
|
+
require 'dm-core'
|
5
|
+
rescue LoadError
|
6
|
+
raise LoadError, "You must have dm-core installed to run the sample (www.datamapper.org)"
|
7
|
+
end
|
8
|
+
|
9
|
+
DataMapper.setup(:default, 'sqlite3:sample.sqlite3')
|
10
|
+
|
11
|
+
class Probe
|
12
|
+
include DataMapper::Resource
|
13
|
+
|
14
|
+
property :id, Integer, :serial => true
|
15
|
+
property :name, String
|
16
|
+
property :avg, Float
|
17
|
+
|
18
|
+
has n, :intensities
|
19
|
+
has n, :individuals, :through => :intensities
|
20
|
+
end
|
21
|
+
|
22
|
+
class Individual
|
23
|
+
include DataMapper::Resource
|
24
|
+
|
25
|
+
property :id, Integer, :serial => true
|
26
|
+
property :name, String
|
27
|
+
|
28
|
+
has n, :intensities
|
29
|
+
has n, :probes, :through => :intensities
|
30
|
+
end
|
31
|
+
|
32
|
+
class Intensity
|
33
|
+
include DataMapper::Resource
|
34
|
+
|
35
|
+
property :id, Integer, :serial => true
|
36
|
+
property :probe_id, Integer
|
37
|
+
property :individual_id, Integer
|
38
|
+
property :value, Float
|
39
|
+
|
40
|
+
belongs_to :probe
|
41
|
+
belongs_to :individual
|
42
|
+
end
|
43
|
+
|
44
|
+
task :default => [:calculate_averages]
|
45
|
+
|
46
|
+
desc "Create the database"
|
47
|
+
event :create_database do
|
48
|
+
STDERR.puts "Creating the database..."
|
49
|
+
Probe.auto_migrate!
|
50
|
+
Individual.auto_migrate!
|
51
|
+
Intensity.auto_migrate!
|
52
|
+
end
|
53
|
+
|
54
|
+
desc 'Rebuild'
|
55
|
+
task :rebuild => [:clean, :calculate_averages] do
|
56
|
+
STDERR.puts "Rebuilding the whole dataset..."
|
57
|
+
end
|
58
|
+
|
59
|
+
desc "start from scratch"
|
60
|
+
task :clean do
|
61
|
+
rm_rf "sample.sqlite3"
|
62
|
+
Rake::EventTask.clean
|
63
|
+
end
|
64
|
+
|
65
|
+
desc 'Load probes from file'
|
66
|
+
event :load_probes => ['probes.txt',:create_database] do
|
67
|
+
STDERR.puts "Loading probes..."
|
68
|
+
Probe.all.destroy!
|
69
|
+
File.open('probes.txt').each do |line|
|
70
|
+
probe_name = line.chomp
|
71
|
+
Probe.new(:name => probe_name).save
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
desc 'Load individuals from file'
|
76
|
+
event :load_individuals => ['individuals.txt',:create_database] do
|
77
|
+
STDERR.puts "Loading individuals..."
|
78
|
+
Individual.all.destroy!
|
79
|
+
File.open('individuals.txt').each do |line|
|
80
|
+
ind_name = line.chomp
|
81
|
+
Individual.new(:name => ind_name).save
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
desc 'Load intensities from file'
|
86
|
+
event :load_intensities => ['intensities.txt', :load_probes, :load_individuals] do
|
87
|
+
STDERR.puts "Loading intensities..."
|
88
|
+
Intensity.all.destroy!
|
89
|
+
File.open('intensities.txt').each do |line|
|
90
|
+
probe_name, ind_name, value = line.chomp.split(/\t/)
|
91
|
+
probe = Probe.first(:name => probe_name)
|
92
|
+
ind = Individual.first(:name => ind_name)
|
93
|
+
Intensity.new(:probe_id => probe.id, :individual_id => ind.id, :value => value.to_f).save
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
desc 'Calculate averages for probes'
|
98
|
+
event :calculate_averages => [:load_intensities] do
|
99
|
+
STDERR.puts "Calculating averages..."
|
100
|
+
Probe.all.each do |probe|
|
101
|
+
sum = 0
|
102
|
+
probe.intensities.each do |i|
|
103
|
+
sum += i.value
|
104
|
+
end
|
105
|
+
probe.avg = sum.to_f/probe.intensities.length.to_f
|
106
|
+
probe.save
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
probe_1 ind_1 836
|
2
|
+
probe_1 ind_2 729
|
3
|
+
probe_1 ind_3 872
|
4
|
+
probe_1 ind_4 534
|
5
|
+
probe_1 ind_5 739
|
6
|
+
probe_2 ind_1 152
|
7
|
+
probe_2 ind_2 630
|
8
|
+
probe_2 ind_3 826
|
9
|
+
probe_2 ind_4 917
|
10
|
+
probe_2 ind_5 1027
|
11
|
+
probe_3 ind_1 710
|
12
|
+
probe_3 ind_2 720
|
13
|
+
probe_3 ind_3 891
|
14
|
+
probe_3 ind_4 710
|
15
|
+
probe_3 ind_5 280
|
16
|
+
probe_4 ind_1 820
|
17
|
+
probe_4 ind_2 720
|
18
|
+
probe_4 ind_3 918
|
19
|
+
probe_4 ind_4 812
|
20
|
+
probe_4 ind_5 710
|
21
|
+
probe_5 ind_1 928
|
22
|
+
probe_5 ind_2 817
|
23
|
+
probe_5 ind_3 918
|
24
|
+
probe_5 ind_4 710
|
25
|
+
probe_5 ind_5 918
|
data/sample/probes.txt
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'stringio'
|
4
|
+
|
5
|
+
# Mix-in for capturing standard output.
|
6
|
+
module CaptureStdout
|
7
|
+
def capture_stdout
|
8
|
+
s = StringIO.new
|
9
|
+
oldstdout = $stdout
|
10
|
+
$stdout = s
|
11
|
+
yield
|
12
|
+
s.string
|
13
|
+
ensure
|
14
|
+
$stdout = oldstdout
|
15
|
+
end
|
16
|
+
|
17
|
+
def capture_stderr
|
18
|
+
s = StringIO.new
|
19
|
+
oldstderr = $stderr
|
20
|
+
$stderr = s
|
21
|
+
yield
|
22
|
+
s.string
|
23
|
+
ensure
|
24
|
+
$stderr = oldstderr
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module EventTaskCreation
|
2
|
+
|
3
|
+
def create_timed_event_tasks(old_task, *new_tasks)
|
4
|
+
return if File.exist?(".rake/"+old_task.to_s) && new_tasks.all? { |new_task| File.exist?(new_task) }
|
5
|
+
|
6
|
+
old_time = create_task(old_task)
|
7
|
+
new_tasks.each do |new_task|
|
8
|
+
while create_task(new_task) <= old_time + 0.00001
|
9
|
+
sleep(0.5)
|
10
|
+
delete_task(new_task.to_s)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def create_task(name)
|
16
|
+
Rake::EventTask.touch(name.to_s)
|
17
|
+
return File.mtime(".rake/"+name.to_s)
|
18
|
+
end
|
19
|
+
|
20
|
+
def delete_task(name)
|
21
|
+
File.delete(".rake/"+name.to_s) rescue nil
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
module FileCreation
|
4
|
+
OLDFILE = "testdata/old"
|
5
|
+
NEWFILE = "testdata/new"
|
6
|
+
|
7
|
+
def create_timed_files(oldfile, *newfiles)
|
8
|
+
return if File.exist?(oldfile) && newfiles.all? { |newfile| File.exist?(newfile) }
|
9
|
+
old_time = create_file(oldfile)
|
10
|
+
newfiles.each do |newfile|
|
11
|
+
while create_file(newfile) <= old_time
|
12
|
+
sleep(0.1)
|
13
|
+
File.delete(newfile) rescue nil
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def create_dir(dirname)
|
19
|
+
FileUtils.mkdir_p(dirname) unless File.exist?(dirname)
|
20
|
+
File.stat(dirname).mtime
|
21
|
+
end
|
22
|
+
|
23
|
+
def create_file(name)
|
24
|
+
create_dir(File.dirname(name))
|
25
|
+
FileUtils.touch(name) unless File.exist?(name)
|
26
|
+
File.stat(name).mtime
|
27
|
+
end
|
28
|
+
|
29
|
+
def delete_file(name)
|
30
|
+
File.delete(name) rescue nil
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,384 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'pathname'
|
3
|
+
|
4
|
+
require 'test/unit'
|
5
|
+
#require 'fileutils'
|
6
|
+
require '../lib/biorake'
|
7
|
+
require 'file_creation.rb'
|
8
|
+
require 'capture_stdout.rb'
|
9
|
+
require 'event_task_creation'
|
10
|
+
|
11
|
+
|
12
|
+
module Interning
|
13
|
+
private
|
14
|
+
|
15
|
+
def intern(name, *args)
|
16
|
+
Rake.application.define_task(Rake::EventTask, name, *args)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
######################################################################
|
21
|
+
class EventTestTask < Test::Unit::TestCase
|
22
|
+
include CaptureStdout
|
23
|
+
include Rake
|
24
|
+
include Interning
|
25
|
+
include EventTaskCreation
|
26
|
+
|
27
|
+
def setup
|
28
|
+
EventTask.clear
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_create
|
32
|
+
arg = nil
|
33
|
+
t = intern(:name).enhance { |task| arg = task; 1234 }
|
34
|
+
assert_equal "name", t.name
|
35
|
+
assert_equal [], t.prerequisites
|
36
|
+
assert t.prerequisites.is_a?(FileList)
|
37
|
+
assert t.needed?
|
38
|
+
t.execute(0)
|
39
|
+
assert_equal t, arg
|
40
|
+
assert_nil t.source
|
41
|
+
assert_equal [], t.sources
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_inspect
|
45
|
+
t = intern(:foo).enhance([:bar, :baz])
|
46
|
+
assert_equal "<Rake::EventTask foo => [bar, baz]>", t.inspect
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_invoke
|
50
|
+
runlist = []
|
51
|
+
t1 = intern(:t1).enhance([:t2, :t3]) { |t| runlist << t.name; 3321 }
|
52
|
+
t2 = intern(:t2).enhance { |t| runlist << t.name }
|
53
|
+
t3 = intern(:t3).enhance { |t| runlist << t.name }
|
54
|
+
assert_equal [:t2, :t3], t1.prerequisites
|
55
|
+
t1.invoke
|
56
|
+
assert_equal ["t2", "t3", "t1"], runlist
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_invoke_with_circular_dependencies
|
60
|
+
runlist = []
|
61
|
+
t1 = intern(:t1).enhance([:t2]) { |t| runlist << t.name; 3321 }
|
62
|
+
t2 = intern(:t2).enhance([:t1]) { |t| runlist << t.name }
|
63
|
+
assert_equal [:t2], t1.prerequisites
|
64
|
+
assert_equal [:t1], t2.prerequisites
|
65
|
+
ex = assert_raise RuntimeError do
|
66
|
+
t1.invoke
|
67
|
+
end
|
68
|
+
assert_match(/circular dependency/i, ex.message)
|
69
|
+
assert_match(/t1 => t2 => t1/, ex.message)
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_dry_run_prevents_actions
|
73
|
+
Rake.application.options.dryrun = true
|
74
|
+
runlist = []
|
75
|
+
t1 = intern(:t1).enhance { |t| runlist << t.name; 3321 }
|
76
|
+
out = capture_stdout { t1.invoke }
|
77
|
+
assert_match(/execute .*t1/i, out)
|
78
|
+
assert_match(/dry run/i, out)
|
79
|
+
assert_no_match(/invoke/i, out)
|
80
|
+
assert_equal [], runlist
|
81
|
+
ensure
|
82
|
+
Rake.application.options.dryrun = false
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_tasks_can_be_traced
|
86
|
+
Rake.application.options.trace = true
|
87
|
+
t1 = intern(:t1) { |t| runlist << t.name; 3321 }
|
88
|
+
out = capture_stdout {
|
89
|
+
t1.invoke
|
90
|
+
}
|
91
|
+
assert_match(/invoke t1/i, out)
|
92
|
+
assert_match(/execute t1/i, out)
|
93
|
+
ensure
|
94
|
+
Rake.application.options.trace = false
|
95
|
+
end
|
96
|
+
|
97
|
+
def test_no_double_invoke
|
98
|
+
runlist = []
|
99
|
+
t1 = intern(:t1).enhance([:t2, :t3]) { |t| runlist << t.name; 3321 }
|
100
|
+
t2 = intern(:t2).enhance([:t3]) { |t| runlist << t.name }
|
101
|
+
t3 = intern(:t3).enhance { |t| runlist << t.name }
|
102
|
+
t1.invoke
|
103
|
+
assert_equal ["t3", "t2", "t1"], runlist
|
104
|
+
end
|
105
|
+
|
106
|
+
def test_find
|
107
|
+
event :tfind
|
108
|
+
assert_equal "tfind", EventTask[:tfind].name
|
109
|
+
ex = assert_raise(RuntimeError) { EventTask[:leaves] }
|
110
|
+
assert_equal "Don't know how to build task 'leaves'", ex.message
|
111
|
+
delete_task(:tfind)
|
112
|
+
end
|
113
|
+
|
114
|
+
def test_defined
|
115
|
+
assert ! EventTask.task_defined?(:a)
|
116
|
+
event :a
|
117
|
+
assert EventTask.task_defined?(:a)
|
118
|
+
delete_task :a
|
119
|
+
end
|
120
|
+
|
121
|
+
def test_multi_invocations
|
122
|
+
runs = []
|
123
|
+
p = proc do |t| runs << t.name end
|
124
|
+
event({:t1=>[:t2,:t3]}, &p)
|
125
|
+
event({:t2=>[:t3]}, &p)
|
126
|
+
event(:t3, &p)
|
127
|
+
EventTask[:t1].invoke
|
128
|
+
assert_equal ["t1", "t2", "t3"], runs.sort
|
129
|
+
delete_task :t1
|
130
|
+
delete_task :t2
|
131
|
+
delete_task :t3
|
132
|
+
end
|
133
|
+
|
134
|
+
def test_task_list
|
135
|
+
task :t2
|
136
|
+
task :t1 => [:t2]
|
137
|
+
assert_equal ["t1", "t2"], Task.tasks.collect {|t| t.name}
|
138
|
+
end
|
139
|
+
|
140
|
+
def test_task_gives_name_on_to_s
|
141
|
+
task :abc
|
142
|
+
assert_equal "abc", Task[:abc].to_s
|
143
|
+
end
|
144
|
+
|
145
|
+
def test_symbols_can_be_prerequisites
|
146
|
+
task :a => :b
|
147
|
+
assert_equal ["b"], Task[:a].prerequisites
|
148
|
+
end
|
149
|
+
|
150
|
+
def test_strings_can_be_prerequisites
|
151
|
+
task :a => "b"
|
152
|
+
assert_equal ["b"], Task[:a].prerequisites
|
153
|
+
end
|
154
|
+
|
155
|
+
def test_arrays_can_be_prerequisites
|
156
|
+
task :a => ["b", "c"]
|
157
|
+
assert_equal ["b", "c"], Task[:a].prerequisites
|
158
|
+
end
|
159
|
+
|
160
|
+
def test_filelists_can_be_prerequisites
|
161
|
+
task :a => FileList.new.include("b", "c")
|
162
|
+
assert_equal ["b", "c"], Task[:a].prerequisites
|
163
|
+
end
|
164
|
+
|
165
|
+
def test_investigation_output
|
166
|
+
t1 = intern(:t1).enhance([:t2, :t3]) { |t| runlist << t.name; 3321 }
|
167
|
+
intern(:t2)
|
168
|
+
intern(:t3)
|
169
|
+
out = t1.investigation
|
170
|
+
assert_match(/class:\s*Rake::EventTask/, out)
|
171
|
+
assert_match(/needed:\s*true/, out)
|
172
|
+
assert_match(/pre-requisites:\s*--t2/, out)
|
173
|
+
end
|
174
|
+
|
175
|
+
|
176
|
+
def test_extended_comments
|
177
|
+
desc %{
|
178
|
+
This is a comment.
|
179
|
+
|
180
|
+
And this is the extended comment.
|
181
|
+
name -- Name of task to execute.
|
182
|
+
rev -- Software revision to use.
|
183
|
+
}
|
184
|
+
t = intern(:t, :name, :rev)
|
185
|
+
assert_equal "[name,rev]", t.arg_description
|
186
|
+
assert_equal "This is a comment.", t.comment
|
187
|
+
assert_match(/^\s*name -- Name/, t.full_comment)
|
188
|
+
assert_match(/^\s*rev -- Software/, t.full_comment)
|
189
|
+
assert_match(/\A\s*This is a comment\.$/, t.full_comment)
|
190
|
+
end
|
191
|
+
|
192
|
+
def test_multiple_comments
|
193
|
+
desc "line one"
|
194
|
+
t = intern(:t)
|
195
|
+
desc "line two"
|
196
|
+
intern(:t)
|
197
|
+
assert_equal "line one / line two", t.comment
|
198
|
+
end
|
199
|
+
|
200
|
+
def test_settable_comments
|
201
|
+
t = intern(:t)
|
202
|
+
t.comment = "HI"
|
203
|
+
assert_equal "HI", t.comment
|
204
|
+
end
|
205
|
+
|
206
|
+
def teardown
|
207
|
+
EventTask.clean
|
208
|
+
end
|
209
|
+
|
210
|
+
end
|
211
|
+
|
212
|
+
#####################################################################
|
213
|
+
class TestTaskWithArguments < Test::Unit::TestCase
|
214
|
+
include CaptureStdout
|
215
|
+
include Rake
|
216
|
+
include Interning
|
217
|
+
include EventTaskCreation
|
218
|
+
|
219
|
+
def setup
|
220
|
+
EventTask.clear
|
221
|
+
end
|
222
|
+
|
223
|
+
def test_no_args_given
|
224
|
+
t = event :t
|
225
|
+
assert_equal [], t.arg_names
|
226
|
+
delete_task :t
|
227
|
+
end
|
228
|
+
|
229
|
+
def test_args_given
|
230
|
+
t = event :t, :a, :b
|
231
|
+
assert_equal [:a, :b], t.arg_names
|
232
|
+
delete_task :t
|
233
|
+
end
|
234
|
+
|
235
|
+
def test_name_and_needs
|
236
|
+
t = event(:t => [:pre])
|
237
|
+
assert_equal "t", t.name
|
238
|
+
assert_equal [], t.arg_names
|
239
|
+
assert_equal ["pre"], t.prerequisites
|
240
|
+
delete_task :t
|
241
|
+
delete_task :pre
|
242
|
+
end
|
243
|
+
|
244
|
+
def test_name_and_explicit_needs
|
245
|
+
t = event(:t, :needs => [:pre])
|
246
|
+
assert_equal "t", t.name
|
247
|
+
assert_equal [], t.arg_names
|
248
|
+
assert_equal ["pre"], t.prerequisites
|
249
|
+
delete_task :t
|
250
|
+
end
|
251
|
+
|
252
|
+
def test_name_args_and_explicit_needs
|
253
|
+
t = event(:t, :x, :y, :needs => [:pre])
|
254
|
+
assert_equal "t", t.name
|
255
|
+
assert_equal [:x, :y], t.arg_names
|
256
|
+
assert_equal ["pre"], t.prerequisites
|
257
|
+
delete_task :t
|
258
|
+
delete_task :pre
|
259
|
+
end
|
260
|
+
|
261
|
+
def test_illegal_keys_in_task_name_hash
|
262
|
+
assert_raise RuntimeError do
|
263
|
+
t = event(:t, :x, :y => 1, :needs => [:pre])
|
264
|
+
end
|
265
|
+
delete_task :pre
|
266
|
+
end
|
267
|
+
|
268
|
+
def test_arg_list_is_empty_if_no_args_given
|
269
|
+
t = intern(:t).enhance do |tt, args|
|
270
|
+
assert_equal({}, args.to_hash)
|
271
|
+
end
|
272
|
+
t.invoke(1, 2, 3)
|
273
|
+
end
|
274
|
+
|
275
|
+
def test_tasks_can_access_arguments_as_hash
|
276
|
+
t = event :t, :a, :b, :c do |tt, args|
|
277
|
+
assert_equal({:a => 1, :b => 2, :c => 3}, args.to_hash)
|
278
|
+
assert_equal 1, args[:a]
|
279
|
+
assert_equal 2, args[:b]
|
280
|
+
assert_equal 3, args[:c]
|
281
|
+
assert_equal 1, args.a
|
282
|
+
assert_equal 2, args.b
|
283
|
+
assert_equal 3, args.c
|
284
|
+
end
|
285
|
+
t.invoke(1, 2, 3)
|
286
|
+
delete_task :t
|
287
|
+
end
|
288
|
+
|
289
|
+
def test_actions_of_various_arity_are_ok_with_args
|
290
|
+
notes = []
|
291
|
+
t = intern(:t, :x).enhance do
|
292
|
+
notes << :a
|
293
|
+
end
|
294
|
+
t.enhance do | |
|
295
|
+
notes << :b
|
296
|
+
end
|
297
|
+
t.enhance do |task|
|
298
|
+
notes << :c
|
299
|
+
assert_kind_of EventTask, task
|
300
|
+
end
|
301
|
+
t.enhance do |t2, args|
|
302
|
+
notes << :d
|
303
|
+
assert_equal t, t2
|
304
|
+
assert_equal({:x => 1}, args.to_hash)
|
305
|
+
end
|
306
|
+
assert_nothing_raised do t.invoke(1) end
|
307
|
+
assert_equal [:a, :b, :c, :d], notes
|
308
|
+
end
|
309
|
+
|
310
|
+
def test_arguments_are_passed_to_block
|
311
|
+
t = intern(:t, :a, :b).enhance { |tt, args|
|
312
|
+
assert_equal( { :a => 1, :b => 2 }, args.to_hash )
|
313
|
+
}
|
314
|
+
t.invoke(1, 2)
|
315
|
+
end
|
316
|
+
|
317
|
+
def test_extra_parameters_are_ignored
|
318
|
+
t = intern(:t, :a).enhance { |tt, args|
|
319
|
+
assert_equal 1, args.a
|
320
|
+
assert_nil args[2]
|
321
|
+
}
|
322
|
+
t.invoke(1, 2)
|
323
|
+
end
|
324
|
+
|
325
|
+
def test_arguments_are_passed_to_all_blocks
|
326
|
+
counter = 0
|
327
|
+
t = event :t, :a
|
328
|
+
event :t do |tt, args|
|
329
|
+
assert_equal 1, args.a
|
330
|
+
counter += 1
|
331
|
+
end
|
332
|
+
event :t do |tt, args|
|
333
|
+
assert_equal 1, args.a
|
334
|
+
counter += 1
|
335
|
+
end
|
336
|
+
t.invoke(1)
|
337
|
+
assert_equal 2, counter
|
338
|
+
delete_task :t
|
339
|
+
end
|
340
|
+
|
341
|
+
def test_block_with_no_parameters_is_ok
|
342
|
+
t = intern(:t).enhance { }
|
343
|
+
t.invoke(1, 2)
|
344
|
+
end
|
345
|
+
|
346
|
+
def test_name_with_args
|
347
|
+
desc "T"
|
348
|
+
t = intern(:tt, :a, :b)
|
349
|
+
assert_equal "tt", t.name
|
350
|
+
assert_equal "T", t.comment
|
351
|
+
assert_equal "[a,b]", t.arg_description
|
352
|
+
assert_equal "tt[a,b]", t.name_with_args
|
353
|
+
assert_equal [:a, :b],t.arg_names
|
354
|
+
end
|
355
|
+
|
356
|
+
def test_named_args_are_passed_to_prereqs
|
357
|
+
value = nil
|
358
|
+
pre = intern(:pre, :rev).enhance { |t, args| value = args.rev }
|
359
|
+
t = intern(:t, :name, :rev).enhance([:pre])
|
360
|
+
t.invoke("bill", "1.2")
|
361
|
+
assert_equal "1.2", value
|
362
|
+
end
|
363
|
+
|
364
|
+
def test_args_not_passed_if_no_prereq_names
|
365
|
+
pre = intern(:pre).enhance { |t, args|
|
366
|
+
assert_equal({}, args.to_hash)
|
367
|
+
assert_equal "bill", args.name
|
368
|
+
}
|
369
|
+
t = intern(:t, :name, :rev).enhance([:pre])
|
370
|
+
t.invoke("bill", "1.2")
|
371
|
+
end
|
372
|
+
|
373
|
+
def test_args_not_passed_if_no_arg_names
|
374
|
+
pre = intern(:pre, :rev).enhance { |t, args|
|
375
|
+
assert_equal({ :rev => nil }, args.to_hash)
|
376
|
+
}
|
377
|
+
t = intern(:t).enhance([:pre])
|
378
|
+
t.invoke("bill", "1.2")
|
379
|
+
end
|
380
|
+
|
381
|
+
def teardown
|
382
|
+
EventTask.clean
|
383
|
+
end
|
384
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
require '../lib/biorake'
|
5
|
+
require 'event_task_creation'
|
6
|
+
require 'yaml'
|
7
|
+
|
8
|
+
class TestEventTaskTimeStamp < Test::Unit::TestCase
|
9
|
+
include Rake
|
10
|
+
include EventTaskCreation
|
11
|
+
|
12
|
+
def setup
|
13
|
+
EventTask.clear
|
14
|
+
@runs = Array.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_names
|
18
|
+
event :names_1
|
19
|
+
event :names_2 => :names_1
|
20
|
+
event_task_1 = EventTask[:names_1]
|
21
|
+
event_task_2 = EventTask[:names_2]
|
22
|
+
assert_equal 'names_1', event_task_1.name
|
23
|
+
assert_equal 'names_2', event_task_2.name
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_event_need
|
27
|
+
name_1 = :event_need_1
|
28
|
+
event name_1
|
29
|
+
event_task = EventTask[name_1]
|
30
|
+
assert_equal name_1.to_s, event_task.name
|
31
|
+
|
32
|
+
name_2 = :event_need_2
|
33
|
+
event name_2
|
34
|
+
event_task = EventTask[name_2]
|
35
|
+
assert event_task.needed?, 'task should be needed'
|
36
|
+
|
37
|
+
EventTask.touch(name_2.to_s)
|
38
|
+
assert_equal nil, event_task.prerequisites.collect{|n| EventTask[n].timestamp}.max
|
39
|
+
assert ! event_task.needed?, "task should not be needed"
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_task_times_new_depends_on_old
|
43
|
+
create_timed_event_tasks(:old_task_times_new_depends_on_old, :new_task_times_new_depends_on_old)
|
44
|
+
|
45
|
+
t1 = Rake.application.intern(EventTask, :new_task_times_new_depends_on_old).enhance([:old_task_times_new_depends_on_old])
|
46
|
+
t2 = Rake.application.intern(EventTask, :old_task_times_new_depends_on_old)
|
47
|
+
assert ! t2.needed?, "Should not need to build old task"
|
48
|
+
assert ! t1.needed?, "Should not need to rebuild new task because of old"
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_task_times_old_depends_on_new
|
52
|
+
create_timed_event_tasks(:old_task_times_old_depends_on_new, :new_task_times_old_depends_on_new)
|
53
|
+
|
54
|
+
t1 = Rake.application.intern(EventTask, :old_task_times_old_depends_on_new).enhance([:new_task_times_old_depends_on_new])
|
55
|
+
t2 = Rake.application.intern(EventTask, :new_task_times_old_depends_on_new)
|
56
|
+
assert ! t2.needed?, "Should not need to build new task"
|
57
|
+
preq_stamp = t1.prerequisites.collect{|t| EventTask[t].timestamp}.max
|
58
|
+
assert_equal t2.timestamp, preq_stamp
|
59
|
+
assert t1.timestamp < preq_stamp, "T1 should be older"
|
60
|
+
assert t1.needed?, "Should need to rebuild old task because of new"
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_event_depends_on_task_depend_on_data
|
64
|
+
create_timed_event_tasks(:old_event_depends_on_task_depend_on_event, :new_event_depends_on_task_depend_on_event)
|
65
|
+
|
66
|
+
event :new_event_depends_on_task_depend_on_event => [:obj] do |t| @runs << t.name end
|
67
|
+
task :obj => [:old_event_depends_on_task_depend_on_event] do |t| @runs << t.name end
|
68
|
+
event :old_event_depends_on_task_depend_on_event do |t| @runs << t.name end
|
69
|
+
|
70
|
+
Task[:obj].invoke
|
71
|
+
EventTask[:new_event_depends_on_task_depend_on_event].invoke
|
72
|
+
assert ! @runs.include?(:new_event_depends_on_task_depend_on_event)
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_existing_event_task_depends_on_non_existing_event_task
|
76
|
+
create_task(:old_existing_event_task_depends_on_non_existing_event_task)
|
77
|
+
delete_task(:new_existing_event_task_depends_on_non_existing_event_task)
|
78
|
+
event :new_existing_event_task_depends_on_non_existing_event_task
|
79
|
+
event :old_existing_event_task_depends_on_non_existing_event_task => :new_existing_event_task_depends_on_non_existing_event_task
|
80
|
+
assert_nothing_raised do EventTask[:old_existing_event_task_depends_on_non_existing_event_task].invoke end
|
81
|
+
end
|
82
|
+
|
83
|
+
def teardown
|
84
|
+
EventTask.clean
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'pathname'
|
3
|
+
|
4
|
+
require 'test/unit'
|
5
|
+
#require 'fileutils'
|
6
|
+
require '../lib/biorake'
|
7
|
+
require 'file_creation.rb'
|
8
|
+
require 'capture_stdout.rb'
|
9
|
+
require 'event_task_creation'
|
10
|
+
|
11
|
+
|
12
|
+
module Interning
|
13
|
+
private
|
14
|
+
|
15
|
+
def event_intern(name, *args)
|
16
|
+
Rake.application.define_task(Rake::EventTask, name, *args)
|
17
|
+
end
|
18
|
+
|
19
|
+
def file_intern(name, *args)
|
20
|
+
Rake.application.define_task(Rake::FileTask, name, *args)
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
######################################################################
|
26
|
+
class EventTestTask < Test::Unit::TestCase
|
27
|
+
include CaptureStdout
|
28
|
+
include Rake
|
29
|
+
include Interning
|
30
|
+
include EventTaskCreation
|
31
|
+
|
32
|
+
def setup
|
33
|
+
EventTask.clear
|
34
|
+
FileTask.clear
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_invoke
|
38
|
+
runlist = []
|
39
|
+
t1 = event_intern(:t1).enhance([:t2, :t3]) { |t| runlist << t.name; 3321 }
|
40
|
+
t2 = file_intern(:t2).enhance { |t| runlist << t.name }
|
41
|
+
t3 = event_intern(:t3).enhance { |t| runlist << t.name }
|
42
|
+
assert_equal [:t2, :t3], t1.prerequisites
|
43
|
+
t1.invoke
|
44
|
+
assert_equal ["t2", "t3", "t1"], runlist
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_invoke_with_circular_dependencies
|
48
|
+
runlist = []
|
49
|
+
t1 = event_intern(:t1).enhance([:t2]) { |t| runlist << t.name; 3321 }
|
50
|
+
t2 = file_intern(:t2).enhance([:t1]) { |t| runlist << t.name }
|
51
|
+
assert_equal [:t2], t1.prerequisites
|
52
|
+
assert_equal [:t1], t2.prerequisites
|
53
|
+
ex = assert_raise RuntimeError do
|
54
|
+
t1.invoke
|
55
|
+
end
|
56
|
+
assert_match(/circular dependency/i, ex.message)
|
57
|
+
assert_match(/t1 => t2 => t1/, ex.message)
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_no_double_invoke
|
61
|
+
runlist = []
|
62
|
+
t1 = event_intern(:t1).enhance([:t2, :t3]) { |t| runlist << t.name; 3321 }
|
63
|
+
t2 = event_intern(:t2).enhance([:t3]) { |t| runlist << t.name }
|
64
|
+
t3 = event_intern(:t3).enhance { |t| runlist << t.name }
|
65
|
+
t1.invoke
|
66
|
+
assert_equal ["t3", "t2", "t1"], runlist
|
67
|
+
end
|
68
|
+
|
69
|
+
def teardown
|
70
|
+
Rake::EventTask.clean
|
71
|
+
end
|
72
|
+
end
|
metadata
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: jandot-biorake
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: "1.0"
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jan Aerts
|
8
|
+
- Charles Comstock
|
9
|
+
autorequire: rake
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2008-08-13 00:00:00 -07:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: rake
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
description: Extension to rake to allow for timestamp usage on tasks that don't alter files (e.g. loading data in a database)
|
26
|
+
email: jan.aerts at gmail.com
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files: []
|
32
|
+
|
33
|
+
files:
|
34
|
+
- biorake.gemspec
|
35
|
+
- lib/biorake.rb
|
36
|
+
- Rakefile
|
37
|
+
- README.textile
|
38
|
+
- sample/individuals.txt
|
39
|
+
- sample/intensities.txt
|
40
|
+
- sample/probes.txt
|
41
|
+
- sample/Rakefile
|
42
|
+
- test/capture_stdout.rb
|
43
|
+
- test/event_task_creation.rb
|
44
|
+
- test/file_creation.rb
|
45
|
+
- test/test_event_task.rb
|
46
|
+
- test/test_event_task_timestamps.rb
|
47
|
+
- test/test_event_task_with_file.rb
|
48
|
+
has_rdoc: false
|
49
|
+
homepage: http://github.com/jandot/biorake
|
50
|
+
post_install_message:
|
51
|
+
rdoc_options: []
|
52
|
+
|
53
|
+
require_paths:
|
54
|
+
- lib
|
55
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: "0"
|
60
|
+
version:
|
61
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: "0"
|
66
|
+
version:
|
67
|
+
requirements: []
|
68
|
+
|
69
|
+
rubyforge_project:
|
70
|
+
rubygems_version: 1.2.0
|
71
|
+
signing_key:
|
72
|
+
specification_version: 2
|
73
|
+
summary: Extension to rake to allow for timestamp usage on tasks that don't alter files
|
74
|
+
test_files:
|
75
|
+
- test/test_event_task.rb
|
76
|
+
- test/test_event_task_timestamps.rb
|
77
|
+
- test/test_event_task_with_file.rb
|