Buildr 0.17.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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