plumr 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +43 -0
- data/lib/plumr.rb +295 -0
- data/lib/plumr/rake.rb +65 -0
- data/test/test_all.rb +0 -0
- metadata +56 -0
data/Rakefile
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
require 'rake/gempackagetask'
|
5
|
+
require 'rake/clean'
|
6
|
+
|
7
|
+
GEM_VERSION = "0.1"
|
8
|
+
|
9
|
+
Rake::RDocTask.new do |task|
|
10
|
+
task.rdoc_files.add [ 'lib/**/*.rb' ]
|
11
|
+
end
|
12
|
+
|
13
|
+
task :clobber => [ :clean ]
|
14
|
+
|
15
|
+
Rake::TestTask.new do |task|
|
16
|
+
task.ruby_opts << '-rrubygems'
|
17
|
+
task.libs << 'lib'
|
18
|
+
task.libs << 'test'
|
19
|
+
task.test_files = [ "test/test_all.rb" ]
|
20
|
+
task.verbose = true
|
21
|
+
end
|
22
|
+
|
23
|
+
gemspec = Gem::Specification.new do |gemspec|
|
24
|
+
gemspec.name = "plumr"
|
25
|
+
gemspec.version = GEM_VERSION
|
26
|
+
gemspec.author = "MenTaLguY <mental@rydia.net>"
|
27
|
+
gemspec.summary = "A builder for shell pipelines with Rake integration"
|
28
|
+
gemspec.test_file = 'test/test_all.rb'
|
29
|
+
gemspec.files = FileList[ 'Rakefile', 'test/*.rb', 'lib/**/*.rb' ]
|
30
|
+
gemspec.require_paths = [ 'lib' ]
|
31
|
+
gemspec.has_rdoc = true
|
32
|
+
gemspec.add_dependency 'rake'
|
33
|
+
gemspec.platform = Gem::Platform::RUBY
|
34
|
+
end
|
35
|
+
|
36
|
+
task :package => [ :clean, :test ]
|
37
|
+
Rake::GemPackageTask.new( gemspec ) do |task|
|
38
|
+
task.gem_spec = gemspec
|
39
|
+
task.need_tar = true
|
40
|
+
end
|
41
|
+
|
42
|
+
task :default => [ :clean, :test ]
|
43
|
+
|
data/lib/plumr.rb
ADDED
@@ -0,0 +1,295 @@
|
|
1
|
+
# plumr - shell pipeline builder
|
2
|
+
#
|
3
|
+
# Copyright 2007 MenTaLguY <mental@rydia.net>
|
4
|
+
#
|
5
|
+
# All rights reserved.
|
6
|
+
#
|
7
|
+
# Redistribution and use in source and binary forms, with or without
|
8
|
+
# modification, are permitted provided that the following conditions are met:
|
9
|
+
#
|
10
|
+
# * Redistributions of source code must retain the above copyright notice,
|
11
|
+
# this list of conditions and the following disclaimer.
|
12
|
+
# * Redistributions in binary form must reproduce the above copyright notice
|
13
|
+
# this list of conditions and the following disclaimer in the documentation
|
14
|
+
# and/or other materials provided with the distribution.
|
15
|
+
# * The names of the authors may not be used to endorse or promote products
|
16
|
+
# derived from this software without specific prior written permission.
|
17
|
+
#
|
18
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
19
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
20
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
21
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
22
|
+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
23
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
24
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
25
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
26
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
27
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
28
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
29
|
+
|
30
|
+
require 'thread'
|
31
|
+
require 'fileutils'
|
32
|
+
require 'set'
|
33
|
+
|
34
|
+
module Plumr
|
35
|
+
# OUTPUT is replaced with the filename of
|
36
|
+
OUTPUT = Object.new
|
37
|
+
|
38
|
+
# Raised when command execution fails
|
39
|
+
class CommandError < RuntimeError
|
40
|
+
end
|
41
|
+
|
42
|
+
# The base interface for a Plumr "fitting", for instance Plumr::File
|
43
|
+
# or Plumr::Command
|
44
|
+
module Fitting
|
45
|
+
# Dumps the fitting's output to the file named by +output_file+
|
46
|
+
def generate(output_file)
|
47
|
+
raise NotImplementedError, "#{self.class}#generate not implemented"
|
48
|
+
end
|
49
|
+
|
50
|
+
# Disposes of any temporary files or resources that we know how to
|
51
|
+
# recreate later.
|
52
|
+
def cleanup
|
53
|
+
raise NotImplementedError, "#{self.class}#cleanup not implemented"
|
54
|
+
end
|
55
|
+
|
56
|
+
# Generates a temporary file; hints for the temporary filename are
|
57
|
+
# provided in the form of a +prefix+ and a numeric +index+; the actual
|
58
|
+
# name of created file is returned. The file should not be deleted
|
59
|
+
# except via calling cleanup_temp.
|
60
|
+
def generate_temp(prefix, index)
|
61
|
+
raise NotImplementedError, "#{self.class}#generate_temp not implemented"
|
62
|
+
end
|
63
|
+
|
64
|
+
# Cleans up a temporary file created by generate_temp, if it is
|
65
|
+
# appropriate to do so; should be called on the same fixture that
|
66
|
+
# generated the file.
|
67
|
+
def cleanup_temp(filename)
|
68
|
+
raise NotImplementedError, "#{self.class}#cleanup_temp not implemented"
|
69
|
+
end
|
70
|
+
|
71
|
+
# Returns true if the fitting's output is up-to-date with respect to
|
72
|
+
# its inputs
|
73
|
+
def up_to_date?
|
74
|
+
raise NotImplementedError, "#{self.class}#up_to_date? not implemented"
|
75
|
+
end
|
76
|
+
|
77
|
+
# Returns true if the fitting's output is up-to-date as of the given
|
78
|
+
# timestamp
|
79
|
+
def older_than?(timestamp)
|
80
|
+
raise NotImplementedError, "#{self.class}#older_than? not implemented"
|
81
|
+
end
|
82
|
+
|
83
|
+
# Returns an "isolated" version of a fitting with any input Plumr::File
|
84
|
+
# objects replaced by their output filenames. Returns a pair consisting
|
85
|
+
# of a source and a set of additional dependencies. The source and
|
86
|
+
# dependencies will be either filenames or Fittings.
|
87
|
+
def isolate
|
88
|
+
raise NotImplementedError, "#{self.class}#isolate not implemented"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# A fitting which constructs a specific file
|
93
|
+
class File
|
94
|
+
include Fitting
|
95
|
+
|
96
|
+
attr_reader :filename # the output filename
|
97
|
+
attr_reader :source # the plumr fitting which generates the data
|
98
|
+
# any Files this file depends on, directly or indirectly
|
99
|
+
attr_reader :dependencies
|
100
|
+
|
101
|
+
# Creates a new Plumr::File object to generate the file +filename+;
|
102
|
+
# +source+ is the fixture which provides the file content; +dependencies+
|
103
|
+
# are any other fixtures which must be up-to-date before the file can
|
104
|
+
# be generated and are not part of the dependencies of +source+.
|
105
|
+
# Explicitly specifying dependencies is not required.
|
106
|
+
#
|
107
|
+
def initialize(filename, source=nil, *dependencies)
|
108
|
+
@lock = Mutex.new
|
109
|
+
@filename = filename.dup.freeze
|
110
|
+
@source = source
|
111
|
+
dependencies.unshift source if source
|
112
|
+
@dependencies = dependencies.to_set.freeze
|
113
|
+
end
|
114
|
+
|
115
|
+
# See Fixture#generate
|
116
|
+
def generate(output_file)
|
117
|
+
build
|
118
|
+
FileUtils.cp(@filename, output_file) if output_file != @filename
|
119
|
+
self
|
120
|
+
end
|
121
|
+
|
122
|
+
# Generates the file which this Plumr::File object represents, if
|
123
|
+
# it is not already up-to-date
|
124
|
+
def build
|
125
|
+
@dependencies.each { |d| d.build if d != @source }
|
126
|
+
@lock.synchronize do
|
127
|
+
unless up_to_date?
|
128
|
+
if @source
|
129
|
+
FileUtils.mkdir_p(::File.dirname(@filename))
|
130
|
+
@source.generate(@filename)
|
131
|
+
else
|
132
|
+
raise RuntimeError, "Don't know how to generate #{@filename}"
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
self
|
137
|
+
end
|
138
|
+
|
139
|
+
# Deletes the file and any dependencies which we know how to recreate.
|
140
|
+
# See Fixture#cleanup.
|
141
|
+
def cleanup
|
142
|
+
# delete only files we know how to recreate
|
143
|
+
return self unless @source
|
144
|
+
begin
|
145
|
+
::File.unlink(@filename)
|
146
|
+
@dependencies.each { |d| d.cleanup }
|
147
|
+
rescue Errno::ENOENT
|
148
|
+
end
|
149
|
+
self
|
150
|
+
end
|
151
|
+
|
152
|
+
def generate_temp(base_name, index)
|
153
|
+
build
|
154
|
+
@filename
|
155
|
+
end
|
156
|
+
|
157
|
+
def cleanup_temp(filename)
|
158
|
+
self
|
159
|
+
end
|
160
|
+
|
161
|
+
def up_to_date?
|
162
|
+
begin
|
163
|
+
timestamp = ::File.mtime @filename
|
164
|
+
rescue Errno::ENOENT
|
165
|
+
return false
|
166
|
+
end
|
167
|
+
@dependencies.all? { |d| d.older_than? timestamp }
|
168
|
+
end
|
169
|
+
|
170
|
+
def older_than?(other_timestamp)
|
171
|
+
begin
|
172
|
+
timestamp = ::File.mtime @filename
|
173
|
+
rescue Errno::ENOENT
|
174
|
+
return false
|
175
|
+
end
|
176
|
+
timestamp < other_timestamp
|
177
|
+
end
|
178
|
+
|
179
|
+
def isolate
|
180
|
+
[ @filename, Set[self] ]
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
# A fitting which represents a shell command
|
185
|
+
class Command
|
186
|
+
include Fitting
|
187
|
+
|
188
|
+
attr_reader :command
|
189
|
+
attr_reader :args
|
190
|
+
|
191
|
+
# Creates a Command which represents the specified +command+ and
|
192
|
+
# a list of arguments +args+. Arguments may be strings, fittings,
|
193
|
+
# or Plumr::OUTPUT. Plumr::OUTPUT will be replaced by the name
|
194
|
+
# of the output file when the command is executed.
|
195
|
+
def initialize(command, *args) #:nodoc:
|
196
|
+
@command = command.dup.freeze
|
197
|
+
@args = args.freeze
|
198
|
+
@sources = @args.flatten.select { |arg| Fitting === arg }.uniq
|
199
|
+
end
|
200
|
+
|
201
|
+
def generate(output_file)
|
202
|
+
input_files = {}
|
203
|
+
ok = false
|
204
|
+
begin
|
205
|
+
@sources.each_with_index do |fitting, index|
|
206
|
+
input_files[fitting] = fitting.generate_temp(output_file, index)
|
207
|
+
end
|
208
|
+
ok = system(@command, *substitute_args(output_file, input_files, @args))
|
209
|
+
raise CommandError, "#{@command} exited with status #{$?}" unless ok
|
210
|
+
ensure
|
211
|
+
cleanup_temp(output_file) unless ok
|
212
|
+
input_files.each_pair do |fitting, input_file|
|
213
|
+
fitting.cleanup_temp(input_file)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
self
|
217
|
+
end
|
218
|
+
|
219
|
+
def cleanup
|
220
|
+
@sources.each { |source| source.cleanup }
|
221
|
+
self
|
222
|
+
end
|
223
|
+
|
224
|
+
def generate_temp(prefix, index)
|
225
|
+
# FIXME: need better tempfile generation, but I'm loath to use stdlib's
|
226
|
+
# Thread.critical-based tempfile stuff
|
227
|
+
output_file = "#{prefix.sub(/\.plumr-temp$/,'')}.#{index}.plumr-temp"
|
228
|
+
generate(output_file)
|
229
|
+
output_file
|
230
|
+
end
|
231
|
+
|
232
|
+
def cleanup_temp(filename)
|
233
|
+
begin
|
234
|
+
::File.unlink(filename)
|
235
|
+
rescue Exception
|
236
|
+
end
|
237
|
+
self
|
238
|
+
end
|
239
|
+
|
240
|
+
def up_to_date?
|
241
|
+
@sources.all? { |source| source.up_to_date? }
|
242
|
+
end
|
243
|
+
|
244
|
+
def older_than?(timestamp)
|
245
|
+
@sources.all? { |source| source.older_than? timestamp }
|
246
|
+
end
|
247
|
+
|
248
|
+
def isolate
|
249
|
+
dependencies = Set.new
|
250
|
+
args = []
|
251
|
+
@args.each do |arg|
|
252
|
+
case arg
|
253
|
+
when Fitting
|
254
|
+
arg_source, arg_dependencies = arg.isolate
|
255
|
+
dependencies.merge arg_dependencies
|
256
|
+
args.push arg_source
|
257
|
+
else
|
258
|
+
args.push arg
|
259
|
+
end
|
260
|
+
end
|
261
|
+
[ with_replaced_args(*args), dependencies ]
|
262
|
+
end
|
263
|
+
|
264
|
+
private
|
265
|
+
def with_replaced_args(*args) #:nodoc:
|
266
|
+
self.class.new(@command, *args)
|
267
|
+
end
|
268
|
+
|
269
|
+
def substitute_args(output_file, input_files, args) #:nodoc:
|
270
|
+
args.map { |arg|
|
271
|
+
case arg
|
272
|
+
when OUTPUT
|
273
|
+
output_file
|
274
|
+
when Fitting
|
275
|
+
input_files[arg]
|
276
|
+
when Array
|
277
|
+
substitute_args(output_file, input_files, arg).to_s
|
278
|
+
else
|
279
|
+
arg
|
280
|
+
end
|
281
|
+
}
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
# Creates a new Plumr::File
|
286
|
+
def self.file(*args)
|
287
|
+
Plumr::File.new(*args)
|
288
|
+
end
|
289
|
+
|
290
|
+
# Creates a new Plumr::Command
|
291
|
+
def self.command(*args)
|
292
|
+
Plumr::Command.new(*args)
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
data/lib/plumr/rake.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
# plumr/rake - Rake integration for Plumr
|
2
|
+
#
|
3
|
+
# Copyright 2007-2008 MenTaLguY <mental@rydia.net>
|
4
|
+
#
|
5
|
+
# All rights reserved.
|
6
|
+
#
|
7
|
+
# Redistribution and use in source and binary forms, with or without
|
8
|
+
# modification, are permitted provided that the following conditions are met:
|
9
|
+
#
|
10
|
+
# * Redistributions of source code must retain the above copyright notice,
|
11
|
+
# this list of conditions and the following disclaimer.
|
12
|
+
# * Redistributions in binary form must reproduce the above copyright notice
|
13
|
+
# this list of conditions and the following disclaimer in the documentation
|
14
|
+
# and/or other materials provided with the distribution.
|
15
|
+
# * The names of the authors may not be used to endorse or promote products
|
16
|
+
# derived from this software without specific prior written permission.
|
17
|
+
#
|
18
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
19
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
20
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
21
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
22
|
+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
23
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
24
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
25
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
26
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
27
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
28
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
29
|
+
|
30
|
+
require 'plumr'
|
31
|
+
require 'set'
|
32
|
+
require 'rake'
|
33
|
+
|
34
|
+
module Plumr
|
35
|
+
|
36
|
+
class File
|
37
|
+
@rake_tasks = Set.new
|
38
|
+
def self.rake_tasks ; @rake_tasks ; end
|
39
|
+
|
40
|
+
def rakeify
|
41
|
+
rake_tasks = File.rake_tasks
|
42
|
+
return self if rake_tasks.include? @filename
|
43
|
+
|
44
|
+
if @source
|
45
|
+
rake_tasks.add @filename
|
46
|
+
source, dependencies = @source.isolate
|
47
|
+
dependencies.each { |d| d.rakeify }
|
48
|
+
dep_files = dependencies.map { |d| d.filename }
|
49
|
+
case source
|
50
|
+
when String
|
51
|
+
Rake::FileTask.define_task(@filename, *dep_files) do
|
52
|
+
cp source, @filename
|
53
|
+
end
|
54
|
+
else
|
55
|
+
Rake::FileTask.define_task(@filename, *dep_files) do
|
56
|
+
source.generate @filename
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
self
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
data/test/test_all.rb
ADDED
File without changes
|
metadata
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.8.11
|
3
|
+
specification_version: 1
|
4
|
+
name: plumr
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: "0.1"
|
7
|
+
date: 2008-02-06 00:00:00 -05:00
|
8
|
+
summary: A builder for shell pipelines with Rake integration
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email:
|
12
|
+
homepage:
|
13
|
+
rubyforge_project:
|
14
|
+
description:
|
15
|
+
autorequire:
|
16
|
+
default_executable:
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: true
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.0.0
|
24
|
+
version:
|
25
|
+
platform: ruby
|
26
|
+
signing_key:
|
27
|
+
cert_chain:
|
28
|
+
authors:
|
29
|
+
- MenTaLguY <mental@rydia.net>
|
30
|
+
files:
|
31
|
+
- Rakefile
|
32
|
+
- test/test_all.rb
|
33
|
+
- lib/plumr.rb
|
34
|
+
- lib/plumr/rake.rb
|
35
|
+
test_files:
|
36
|
+
- test/test_all.rb
|
37
|
+
rdoc_options: []
|
38
|
+
|
39
|
+
extra_rdoc_files: []
|
40
|
+
|
41
|
+
executables: []
|
42
|
+
|
43
|
+
extensions: []
|
44
|
+
|
45
|
+
requirements: []
|
46
|
+
|
47
|
+
dependencies:
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: rake
|
50
|
+
version_requirement:
|
51
|
+
version_requirements: !ruby/object:Gem::Version::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">"
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: 0.0.0
|
56
|
+
version:
|