plumr 0.1
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/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:
|