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.
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: