jandot-biorake 1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|