plumr 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. data/Rakefile +43 -0
  2. data/lib/plumr.rb +295 -0
  3. data/lib/plumr/rake.rb +65 -0
  4. data/test/test_all.rb +0 -0
  5. metadata +56 -0
@@ -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
+
@@ -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
+
@@ -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
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: