Buildr 0.17.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.
@@ -0,0 +1,96 @@
1
+ module Buildr
2
+
3
+ class Filter < Rake::Task
4
+
5
+ # The target directory.
6
+ attr_accessor :target
7
+ # Filter to use.
8
+ attr_accessor :filter
9
+
10
+ def initialize(*args)
11
+ super
12
+ enhance do |task|
13
+ fail "No target directory specified" if !target || (File.exist?(target) && !File.directory?(target))
14
+ unless copy_map.empty?
15
+ verbose(Rake.application.options.trace || false) do
16
+ copy_map do |dest, src|
17
+ mkpath File.dirname(dest) rescue nil
18
+ case filter
19
+ when Proc, Method
20
+ filtered = filter.call(File.read(src))
21
+ File.open(dest, "w") { |file| file.write filtered }
22
+ when Hash
23
+ filtered = File.read(src).gsub(/\$\{.*\}/) { |str| filter[str[2..-2]] || str }
24
+ File.open(dest, "w") { |file| file.write filtered }
25
+ when nil
26
+ cp src, dest
27
+ else
28
+ fail "Filter can be a hash (key=>value), or a proc/method; I don't understand #{filter}"
29
+ end
30
+ end
31
+ touch target if File.exist?(target)
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ def include(*files)
38
+ prerequisites.include *files
39
+ self
40
+ end
41
+ alias :add :include
42
+
43
+ def exclude(*files)
44
+ prerequisites.exclude *files
45
+ self
46
+ end
47
+
48
+ def into(dir)
49
+ self.target = dir
50
+ self
51
+ end
52
+
53
+ def using(filter, &block)
54
+ self.filter = filter || block
55
+ self
56
+ end
57
+
58
+ protected
59
+
60
+ # Return a copy map of all the files that need copying: the key is
61
+ # the file to copy to, the value is the source file. If called with
62
+ # a block, yields with each dest/source pair.
63
+ def copy_map(&block)
64
+ # Create a map between the source file and the similarly named
65
+ # file in the target directory, including all files nested inside
66
+ # directories.
67
+ @copy_map ||= prerequisites.map(&:to_s).inject({}) do |map, path|
68
+ if File.directory?(path)
69
+ Dir[File.join(path, "**", "*")].each do |file|
70
+ map[file.sub(File.dirname(path), target)] = file unless
71
+ File.directory?(file) || prerequisites.exclude?(file)
72
+ end
73
+ elsif File.exist?(path)
74
+ map[File.join(target, File.basename(path))] = path
75
+ end
76
+ map
77
+ end.reject do |dest, src|
78
+ # ... while ignoring that which does not need updating.
79
+ File.exist?(dest) && File.stat(dest).mtime > File.stat(src).mtime
80
+ end
81
+ if block_given?
82
+ @copy_map.each(&block)
83
+ else
84
+ @copy_map
85
+ end
86
+ end
87
+
88
+ end
89
+
90
+ def filter(*files)
91
+ task = nil
92
+ namespace { task = Filter.define_task("filter").include *files }
93
+ task
94
+ end
95
+
96
+ end
@@ -0,0 +1,422 @@
1
+ require "zip/zip"
2
+ require "zip/zipfilesystem"
3
+
4
+
5
+ module Buildr
6
+
7
+ # The ZipTask creates a new ZIP file. You can include any number of files and
8
+ # and directories, use exclusion patterns, and include files into specific
9
+ # directories.
10
+ class ZipTask < Rake::FileTask
11
+
12
+ # :nodoc:
13
+ module IncludeFiles
14
+
15
+ # Include the specified files or directories.
16
+ def include(*files)
17
+ if Hash === files.last
18
+ options = files.pop
19
+ else
20
+ options = {}
21
+ end
22
+ files = files.flatten
23
+
24
+ if options[:path]
25
+ path(options[:path]).include *files +[ options.reject { |k,v| k == :path } ]
26
+ elsif options[:as]
27
+ raise "You can only use the :as option in combination with the :path option" unless options.keys.size == 1
28
+ raise "You can only use one file with the :as option" unless files.size == 1
29
+ include_as(files.first.to_s, options[:as])
30
+ elsif options[:merge]
31
+ raise "You can only use the :merge option in combination with the :path option" unless options.keys.size == 1
32
+ files.each { |file| merge file }
33
+ elsif options.keys.empty?
34
+ (@files ||= FileList[]).include files.map(&:to_s)
35
+ else
36
+ raise "Unrecognized option #{options.keys.join(", ")}"
37
+ end
38
+ self
39
+ end
40
+
41
+ # Exclude the specified file or directories.
42
+ def exclude(*files)
43
+ (@files ||= FileList[]).exclude *files
44
+ self
45
+ end
46
+ alias :add :include
47
+
48
+ def merge(*files)
49
+ if Hash === files.last
50
+ options = files.pop
51
+ else
52
+ options = {}
53
+ end
54
+
55
+ if options[:path]
56
+ path(options[:path]).merge *files +[ options.reject { |k,v| k == :path } ]
57
+ elsif options.keys.empty?
58
+ files.collect do |file|
59
+ @expand_sources << proc { file.to_s }
60
+ expander = ZipExpander.new(file)
61
+ @add_files << proc { |zip| expander.expand(zip, @path) }
62
+ expander
63
+ end.first
64
+ else
65
+ raise "Unrecognized option #{options.keys.join(", ")}"
66
+ end
67
+ end
68
+
69
+ protected
70
+
71
+ def setup_path(path = nil)
72
+ @path = "#{path}/" if path
73
+ expand_src = proc { (@files || []).map(&:to_s).uniq }
74
+ @expand_sources = [ expand_src ]
75
+ @add_files = [] << proc do |zip|
76
+ expand_src.call.each do |file|
77
+ if File.directory?(file)
78
+ in_directory(file, @files) do |file, rel_path|
79
+ puts "Adding #{@path}#{rel_path}" if Rake.application.options.trace
80
+ zip.add "#{@path}#{rel_path}", file
81
+ end
82
+ else
83
+ puts "Adding #{@path}#{File.basename(file)}" if Rake.application.options.trace
84
+ zip.add "#{@path}#{File.basename(file)}", file
85
+ end
86
+ end
87
+ end
88
+ end
89
+
90
+ def include_as(source, as)
91
+ @expand_sources << proc { source }
92
+ @add_files << proc do |zip|
93
+ file = source.to_s
94
+ if File.directory?(file)
95
+ in_directory(file) do |file, rel_path|
96
+ puts "Adding #{@path}#{as}/#{rel_path}" if Rake.application.options.trace
97
+ zip.add file, "#{@path}#{as}/#{rel_path}"
98
+ end
99
+ else
100
+ puts "Adding #{@path}#{as}" if Rake.application.options.trace
101
+ zip.add "#{@path}#{as}", file
102
+ end
103
+ end
104
+ end
105
+
106
+ def in_directory(dir, excludes = nil)
107
+ prefix = Regexp.new("^" + Regexp.escape(File.dirname(dir) + File::SEPARATOR))
108
+ Dir[File.join(dir, "**", "*")].
109
+ reject { |file| File.directory?(file) || (excludes && excludes.exclude?(file)) }.
110
+ each { |file| yield file, file.sub(prefix, "") }
111
+ end
112
+
113
+ def expand_sources()
114
+ @expand_sources.map(&:call).flatten
115
+ end
116
+
117
+ def add_file(zip)
118
+ @add_files.each { |action| action.call zip }
119
+ end
120
+
121
+ end
122
+
123
+
124
+ # Which files go where.
125
+ class Path
126
+
127
+ include IncludeFiles
128
+
129
+ def initialize(path)
130
+ setup_path path
131
+ end
132
+
133
+ end
134
+
135
+ include IncludeFiles
136
+
137
+ def initialize(*args)
138
+ super
139
+ @paths = { nil=>self }
140
+ setup_path
141
+ enhance do |task|
142
+ puts "Creating #{task.name}" if verbose
143
+ # We're here because the Zip file does not exist, or one of the files is
144
+ # newer than the Zip contents; in the later case, opening the Zip file
145
+ # will add to its contents instead of replacing it, so we want the Zip
146
+ # gone before we change it. We also don't want to see any partial updates.
147
+ rm task.name, :verbose=>false rescue nil
148
+ mkpath File.dirname(task.name), :verbose=>false
149
+ begin
150
+ Zip::ZipFile.open(task.name, Zip::ZipFile::CREATE) { |zip| create zip }
151
+ rescue
152
+ rm task.name, :verbose=>false rescue nil
153
+ raise
154
+ end
155
+ end
156
+ end
157
+
158
+
159
+ class ZipExpander
160
+
161
+ def initialize(zip_file)
162
+ @zip_file = zip_file.to_s
163
+ end
164
+
165
+ def include(*files)
166
+ (@includes ||= [])
167
+ @includes |= files
168
+ self
169
+ end
170
+
171
+ def exclude(*files)
172
+ (@excludes ||= [])
173
+ @excludes |= files
174
+ self
175
+ end
176
+
177
+ def expand(zip, path)
178
+ @includes ||= ["*"]
179
+ @excludes ||= []
180
+ Zip::ZipFile.open(@zip_file) do |source|
181
+ source.entries.reject { |entry| entry.directory? }.each do |entry|
182
+ if @includes.any? { |pattern| File.fnmatch(pattern, entry.name) } &&
183
+ !@excludes.any? { |pattern| File.fnmatch(pattern, entry.name) }
184
+ puts "Adding #{path}#{entry.name}" if Rake.application.options.trace
185
+ zip.get_output_stream("#{path}#{entry.name}") { |output| output.write source.read(entry) }
186
+ # TODO: read and write file
187
+ end
188
+ end
189
+ end
190
+ end
191
+
192
+ end
193
+
194
+
195
+ # Returns a path to which you can include/exclude files.
196
+ #
197
+ # zip(..).include("foo", :path=>"bar")
198
+ # is equivalen to:
199
+ # zip(..).path("bar").include("foo")
200
+ def path(path)
201
+ path.blank? ? @paths[nil] : (@paths[path] ||= Path.new(path))
202
+ end
203
+
204
+ # Pass options to the task. Returns self.
205
+ def with(options)
206
+ options.each do |key, value|
207
+ self[key] = value
208
+ end
209
+ self
210
+ end
211
+
212
+ def []=(key, value)
213
+ fail "#{self.class} does not support the attribute #{key}"
214
+ end
215
+
216
+ def invoke_prerequisites()
217
+ super
218
+ @paths.collect { |name, path| path.expand_sources }.flatten.each { |src| file(src).invoke }
219
+ end
220
+
221
+ def needed?()
222
+ return true unless File.exist?(name)
223
+ # You can do something like:
224
+ # include("foo", :path=>"foo").exclude("foo/bar", path=>"foo").
225
+ # include("foo/bar", :path=>"foo/bar")
226
+ # This will play havoc if we handled all the prerequisites together
227
+ # under the task, so instead we handle them individually for each path.
228
+ #
229
+ # We need to check that any file we include is not newer than the
230
+ # contents of the ZIP. The file itself but also the directory it's
231
+ # coming from, since some tasks touch the directory, e.g. when the
232
+ # content of target/classes is included into a WAR.
233
+ most_recent = @paths.collect { |name, path| path.expand_sources }.flatten.
234
+ each { |src| File.directory?(src) ? FileList[File.join(src, "**", "*")] | [src] : src }.flatten.
235
+ select { |file| File.exist?(file) }.collect { |file| File.stat(file).mtime }.max
236
+ File.stat(name).mtime < (most_recent || Rake::EARLY) || super
237
+ end
238
+
239
+ protected
240
+
241
+ def create(zip)
242
+ @paths.each { |name, obj| obj.add_file zip }
243
+ end
244
+
245
+ end
246
+
247
+ # The ZipTask creates a new ZIP file. You can include any number of files and
248
+ # and directories, use exclusion patterns, and include files into specific
249
+ # directories.
250
+ #
251
+ # For example:
252
+ # returning(zip("test.zip")) { |task|
253
+ # task.include "srcs"
254
+ # task.include "README", "LICENSE"
255
+ # end
256
+ def zip(file)
257
+ ZipTask.define_task(file)
258
+ end
259
+
260
+
261
+ # The UnzipTask expands the contents of a ZIP file into a target directory.
262
+ # You can include any number of files and directories, use exclusion patterns,
263
+ # and expand files from relative paths.
264
+ #
265
+ # The file(s) to unzip is the first prerequisite.
266
+ class UnzipTask < Rake::Task
267
+
268
+ # The target directory.
269
+ attr_accessor :target
270
+
271
+ def initialize(*args)
272
+ super
273
+ @paths = {}
274
+ enhance do |task|
275
+ fail "Where do you want the file unzipped" unless target
276
+
277
+ # If no paths specified, then no include/exclude patterns
278
+ # specified. Nothing will happen unless we include all files.
279
+ if @paths.empty?
280
+ @paths[nil] = FromPath.new(nil)
281
+ @paths[nil].include "*"
282
+ end
283
+
284
+ # Otherwise, empty unzip creates target as a file when touching.
285
+ mkpath target, :verbose=>false
286
+ prerequisites.each do |file|
287
+ Zip::ZipFile.open(file) do |zip|
288
+ entries = zip.collect
289
+ @paths.each do |path, patterns|
290
+ patterns.map(entries).each do |dest, entry|
291
+ next if entry.directory?
292
+ dest = File.expand_path(dest, target)
293
+ puts "Extracting #{dest}" if Rake.application.options.trace
294
+ mkpath File.dirname(dest), :verbose=>false rescue nil
295
+ entry.extract(dest) { true }
296
+ end
297
+ end
298
+ end
299
+ end
300
+ # Let other tasks know we updated the target directory.
301
+ touch target, :verbose=>false
302
+ end
303
+ end
304
+
305
+ # Specifies directory to unzip to and return self.
306
+ def into(target)
307
+ self.target = target
308
+ self
309
+ end
310
+
311
+ # Include all files that match the patterns and returns self.
312
+ #
313
+ # Use include if you only want to unzip some of the files, by specifying
314
+ # them instead of using exclusion. You can use #include in combination
315
+ # with #exclude.
316
+ def include(*files)
317
+ if Hash === files.last
318
+ from_path(files.pop[:path]).include *files
319
+ else
320
+ from_path(nil).include *files
321
+ end
322
+ self
323
+ end
324
+ alias :add :include
325
+
326
+ # Exclude all files that match the patterns and return self.
327
+ #
328
+ # Use exclude to unzip all files except those that match the pattern.
329
+ # You can use #exclude in combination with #include.
330
+ def exclude(*files)
331
+ if Hash === files.last
332
+ from_path(files.pop[:path]).exclude *files
333
+ else
334
+ from_path(nil).exclude *files
335
+ end
336
+ self
337
+ end
338
+
339
+ # Allows you to unzip from a path. Returns an object you can use to
340
+ # specify which files to include/exclude relative to that path.
341
+ # Expands the file relative to that path.
342
+ #
343
+ # For example:
344
+ # unzip("test.jar").into(Dir.pwd).from_path("etc").include("LICENSE")
345
+ # will unzip etc/LICENSE into ./LICENSE.
346
+ #
347
+ # This is different from:
348
+ # unzip("test.jar").into(Dir.pwd).include("etc/LICENSE")
349
+ # which unzips etc/LICENSE into ./etc/LICENSE.
350
+ def from_path(path)
351
+ @paths[path] ||= FromPath.new(path)
352
+ end
353
+
354
+ def needed?()
355
+ #return true unless target && File.exist?(target)
356
+ #return true if prerequisites.any? { |prereq| File.stat(prereq).mtime > File.stat(target).mtime }
357
+ #false
358
+ true
359
+ end
360
+
361
+ # :nodoc:
362
+ class FromPath
363
+
364
+ def initialize(path)
365
+ if path
366
+ @path = path[-1] == ?/ ? path : path + "/"
367
+ else
368
+ @path = ""
369
+ end
370
+ end
371
+
372
+ # See UnzipTask#include
373
+ def include(*files)
374
+ @include ||= []
375
+ @include |= files
376
+ self
377
+ end
378
+
379
+ # See UnzipTask#exclude
380
+ def exclude(*files)
381
+ @exclude ||= []
382
+ @exclude |= files
383
+ self
384
+ end
385
+
386
+ # :nodoc:
387
+ def map(entries)
388
+ includes = @include || ["*"]
389
+ excludes = @exclude || []
390
+ entries.inject({}) do |map, entry|
391
+ short = entry.name.sub(@path, "")
392
+ if includes.any? { |pat| File.fnmatch(pat, short) } &&
393
+ !excludes.any? { |pat| File.fnmatch(pat, short) }
394
+ map[short] = entry
395
+ end
396
+ map
397
+ end
398
+ end
399
+
400
+ end
401
+
402
+ end
403
+
404
+ # Defines a task that will unzip the specified file, into the directory
405
+ # specified by calling #into. It is the second call to into that creates
406
+ # and returns the task.
407
+ #
408
+ # You can unzip only some files by specifying an inclusion or exclusion
409
+ # pattern, and unzip files from a path in the ZIP file. See UnzipTask
410
+ # for more information.
411
+ #
412
+ # For example:
413
+ # unzip("test.zip").into("test")
414
+ # unzip("test.zip").into("etc").include("README", "LICENSE")
415
+ # unzip("test.zip").into("src").from_path("srcs")
416
+ def unzip(file)
417
+ task = nil
418
+ namespace { task = UnzipTask.define_task("unzip"=>file) }
419
+ task
420
+ end
421
+
422
+ end