C0 code coverage information
Generated on Wed Oct 07 08:34:03 -0700 2009 with rcov 0.8.2.1
Code reported as executed by Ruby looks like this...
and this: this line is also marked as covered.
Lines considered as run by rcov, but not reported by Ruby, look like this,
and this: these lines were inferred by rcov (using simple heuristics).
Finally, here's a line marked as not executed.
1 # Licensed to the Apache Software Foundation (ASF) under one or more
2 # contributor license agreements. See the NOTICE file distributed with this
3 # work for additional information regarding copyright ownership. The ASF
4 # licenses this file to you under the Apache License, Version 2.0 (the
5 # "License"); you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 # http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 # License for the specific language governing permissions and limitations under
14 # the License.
15
16
17 module Buildr
18
19 # Base class for ZipTask, TarTask and other archives.
20 class ArchiveTask < Rake::FileTask
21
22 # Which files go where. All the rules for including, excluding and merging files
23 # are handled by this object.
24 class Path #:nodoc:
25
26 # Returns the archive from this path.
27 attr_reader :root
28
29 def initialize(root, path)
30 @root = root
31 @path = path.empty? ? path : "#{path}/"
32 @includes = FileList[]
33 @excludes = []
34 # Expand source files added to this path.
35 expand_src = proc { @includes.map{ |file| file.to_s }.uniq }
36 @sources = [ expand_src ]
37 # Add files and directories added to this path.
38 @actions = [] << proc do |file_map|
39 expand_src.call.each do |path|
40 unless excluded?(path)
41 if File.directory?(path)
42 in_directory path do |file, rel_path|
43 dest = "#{@path}#{rel_path}"
44 trace "Adding #{dest}"
45 file_map[dest] = file
46 end
47 else
48 trace "Adding #{@path}#{File.basename(path)}"
49 file_map["#{@path}#{File.basename(path)}"] = path
50 end
51 end
52 end
53 end
54 end
55
56 # :call-seq:
57 # include(*files) => self
58 # include(*files, :path=>path) => self
59 # include(file, :as=>name) => self
60 # include(:from=>path) => self
61 # include(*files, :merge=>true) => self
62 def include(*args)
63 options = args.pop if Hash === args.last
64 files = args.flatten
65
66 if options.nil? || options.empty?
67 @includes.include *files.flatten
68 elsif options[:path]
69 sans_path = options.reject { |k,v| k == :path }
70 path(options[:path]).include *files + [sans_path]
71 elsif options[:as]
72 raise 'You can only use the :as option in combination with the :path option' unless options.size == 1
73 raise 'You can only use one file with the :as option' unless files.size == 1
74 include_as files.first.to_s, options[:as]
75 elsif options[:from]
76 raise 'You can only use the :from option in combination with the :path option' unless options.size == 1
77 raise 'You canont use the :from option with file names' unless files.empty?
78 [options[:from]].flatten.each { |path| include_as path.to_s, '.' }
79 elsif options[:merge]
80 raise 'You can only use the :merge option in combination with the :path option' unless options.size == 1
81 files.each { |file| merge file }
82 else
83 raise "Unrecognized option #{options.keys.join(', ')}"
84 end
85 self
86 end
87 alias :add :include
88 alias :<< :include
89
90 # :call-seq:
91 # exclude(*files) => self
92 def exclude(*files)
93 files = files.flatten.map(&:to_s)
94 @excludes |= files
95 @excludes |= files.reject { |f| f =~ /\*$/ }.map { |f| "#{f}/*" }
96 self
97 end
98
99 # :call-seq:
100 # merge(*files) => Merge
101 # merge(*files, :path=>name) => Merge
102 def merge(*args)
103 options = Hash === args.last ? args.pop : {}
104 files = args.flatten
105 rake_check_options options, :path
106 raise ArgumentError, "Expected at least one file to merge" if files.empty?
107 path = options[:path] || @path
108 expanders = files.collect do |file|
109 @sources << proc { file.to_s }
110 expander = ZipExpander.new(file)
111 @actions << proc { |file_map| expander.expand(file_map, path) }
112 expander
113 end
114 Merge.new(expanders)
115 end
116
117 # Returns a Path relative to this one.
118 def path(path)
119 return self if path.nil?
120 return root.path(path[1..-1]) if path[0] == ?/
121 root.path("#{@path}#{path}")
122 end
123
124 # Returns all the source files.
125 def sources #:nodoc:
126 @sources.map{ |source| source.call }.flatten
127 end
128
129 def add_files(file_map) #:nodoc:
130 @actions.each { |action| action.call(file_map) }
131 end
132
133 # :call-seq:
134 # exist => boolean
135 #
136 # Returns true if this path exists. This only works if the path has any entries in it,
137 # so exist on path happens to be the opposite of empty.
138 def exist?
139 !entries.empty?
140 end
141
142 # :call-seq:
143 # empty? => boolean
144 #
145 # Returns true if this path is empty (has no other entries inside).
146 def empty?
147 entries.all? { |entry| entry.empty? }
148 end
149
150 # :call-seq:
151 # contain(file*) => boolean
152 #
153 # Returns true if this ZIP file path contains all the specified files. You can use relative
154 # file names and glob patterns (using *, **, etc).
155 def contain?(*files)
156 files.all? { |file| entries.detect { |entry| File.fnmatch(file, entry.to_s) } }
157 end
158
159 # :call-seq:
160 # entry(name) => ZipEntry
161 #
162 # Returns a ZIP file entry. You can use this to check if the entry exists and its contents,
163 # for example:
164 # package(:jar).path("META-INF").entry("LICENSE").should contain(/Apache Software License/)
165 def entry(name)
166 root.entry("#{@path}#{name}")
167 end
168
169 def to_s
170 @path
171 end
172
173 protected
174
175 def include_as(source, as)
176 @sources << proc { source }
177 @actions << proc do |file_map|
178 file = source.to_s
179 unless excluded?(file)
180 if File.directory?(file)
181 in_directory file do |file, rel_path|
182 path = rel_path.split('/')[1..-1]
183 path.unshift as unless as == '.'
184 dest = "#{@path}#{path.join('/')}"
185 trace "Adding #{dest}"
186 file_map[dest] = file
187 end
188 else
189 trace "Adding #{@path}#{as}"
190 file_map["#{@path}#{as}"] = file
191 end
192 end
193 end
194 end
195
196 def in_directory(dir)
197 prefix = Regexp.new('^' + Regexp.escape(File.dirname(dir) + File::SEPARATOR))
198 Util.recursive_with_dot_files(dir).reject { |file| excluded?(file) }.
199 each { |file| yield file, file.sub(prefix, '') }
200 end
201
202 def excluded?(file)
203 @excludes.any? { |exclude| File.fnmatch(exclude, file) }
204 end
205
206 def entries #:nodoc:
207 return root.entries unless @path
208 @entries ||= root.entries.inject([]) { |selected, entry|
209 selected << entry.name.sub(@path, "") if entry.name.index(@path) == 0
210 selected
211 }
212 end
213
214 end
215
216
217 class Merge
218 def initialize(expanders)
219 @expanders = expanders
220 end
221
222 def include(*files)
223 @expanders.each { |expander| expander.include(*files) }
224 self
225 end
226 alias :<< :include
227
228 def exclude(*files)
229 @expanders.each { |expander| expander.exclude(*files) }
230 self
231 end
232 end
233
234
235 # Extend one Zip file into another.
236 class ZipExpander #:nodoc:
237
238 def initialize(zip_file)
239 @zip_file = zip_file.to_s
240 @includes = []
241 @excludes = []
242 end
243
244 def include(*files)
245 @includes |= files
246 self
247 end
248 alias :<< :include
249
250 def exclude(*files)
251 @excludes |= files
252 self
253 end
254
255 def expand(file_map, path)
256 @includes = ['*'] if @includes.empty?
257 Zip::ZipFile.open(@zip_file) do |source|
258 source.entries.reject { |entry| entry.directory? }.each do |entry|
259 if @includes.any? { |pattern| File.fnmatch(pattern, entry.name) } &&
260 !@excludes.any? { |pattern| File.fnmatch(pattern, entry.name) }
261 dest = path =~ /^\/?$/ ? entry.name : Util.relative_path(path + "/" + entry.name)
262 trace "Adding #{dest}"
263 file_map[dest] = lambda { |output| output.write source.read(entry) }
264 end
265 end
266 end
267 end
268
269 end
270
271
272 def initialize(*args) #:nodoc:
273 super
274 clean
275
276 # Make sure we're the last enhancements, so other enhancements can add content.
277 enhance do
278 @file_map = {}
279 enhance do
280 send 'create' if respond_to?(:create)
281 # We're here because the archive file does not exist, or one of the files is newer than the archive contents;
282 # we need to make sure the archive doesn't exist (e.g. opening an existing Zip will add instead of create).
283 # We also want to protect against partial updates.
284 rm name rescue nil
285 mkpath File.dirname(name)
286 begin
287 @paths.each do |name, object|
288 @file_map[name] = nil unless name.empty?
289 object.add_files(@file_map)
290 end
291 create_from @file_map
292 rescue
293 rm name rescue nil
294 raise
295 end
296 end
297 end
298 end
299
300 # :call-seq:
301 # clean => self
302 #
303 # Removes all previously added content from this archive.
304 # Use this method if you want to remove default content from a package.
305 # For example, package(:jar) by default includes compiled classes and resources,
306 # using this method, you can create an empty jar and afterwards add the
307 # desired content to it.
308 #
309 # package(:jar).clean.include path_to('desired/content')
310 def clean
311 @paths = { '' => Path.new(self, '') }
312 @prepares = []
313 self
314 end
315
316 # :call-seq:
317 # include(*files) => self
318 # include(*files, :path=>path) => self
319 # include(file, :as=>name) => self
320 # include(:from=>path) => self
321 # include(*files, :merge=>true) => self
322 #
323 # Include files in this archive, or when called on a path, within that path. Returns self.
324 #
325 # The first form accepts a list of files, directories and glob patterns and adds them to the archive.
326 # For example, to include the file foo, directory bar (including all files in there) and all files under baz:
327 # zip(..).include('foo', 'bar', 'baz/*')
328 #
329 # The second form is similar but adds files/directories under the specified path. For example,
330 # to add foo as bar/foo:
331 # zip(..).include('foo', :path=>'bar')
332 # The :path option is the same as using the path method:
333 # zip(..).path('bar').include('foo')
334 # All other options can be used in combination with the :path option.
335 #
336 # The third form adds a file or directory under a different name. For example, to add the file foo under the
337 # name bar:
338 # zip(..).include('foo', :as=>'bar')
339 #
340 # The fourth form adds the contents of a directory using the directory as a prerequisite:
341 # zip(..).include(:from=>'foo')
342 # Unlike <code>include('foo')</code> it includes the contents of the directory, not the directory itself.
343 # Unlike <code>include('foo/*')</code>, it uses the directory timestamp for dependency management.
344 #
345 # The fifth form includes the contents of another archive by expanding it into this archive. For example:
346 # zip(..).include('foo.zip', :merge=>true).include('bar.zip')
347 # You can also use the method #merge.
348 def include(*files)
349 @paths[''].include *files
350 self
351 end
352 alias :add :include
353 alias :<< :include
354
355 # :call-seq:
356 # exclude(*files) => self
357 #
358 # Excludes files and returns self. Can be used in combination with include to prevent some files from being included.
359 def exclude(*files)
360 @paths[''].exclude *files
361 self
362 end
363
364 # :call-seq:
365 # merge(*files) => Merge
366 # merge(*files, :path=>name) => Merge
367 #
368 # Merges another archive into this one by including the individual files from the merged archive.
369 #
370 # Returns an object that supports two methods: include and exclude. You can use these methods to merge
371 # only specific files. For example:
372 # zip(..).merge('src.zip').include('module1/*')
373 def merge(*files)
374 @paths[''].merge *files
375 end
376
377 # :call-seq:
378 # path(name) => Path
379 #
380 # Returns a path object. Use the path object to include files under a path, for example, to include
381 # the file 'foo' as 'bar/foo':
382 # zip(..).path('bar').include('foo')
383 #
384 # Returns a Path object. The Path object implements all the same methods, like include, exclude, merge
385 # and so forth. It also implements path and root, so that:
386 # path('foo').path('bar') == path('foo/bar')
387 # path('foo').root == root
388 def path(name)
389 return @paths[''] if name.nil?
390 normalized = name.split('/').inject([]) do |path, part|
391 case part
392 when '.', nil, ''
393 path
394 when '..'
395 path[0...-1]
396 else
397 path << part
398 end
399 end.join('/')
400 @paths[normalized] ||= Path.new(self, normalized)
401 end
402
403 # :call-seq:
404 # root => ArchiveTask
405 #
406 # Call this on an archive to return itself, and on a path to return the archive.
407 def root
408 self
409 end
410
411 # :call-seq:
412 # with(options) => self
413 #
414 # Passes options to the task and returns self. Some tasks support additional options, for example,
415 # the WarTask supports options like :manifest, :libs and :classes.
416 #
417 # For example:
418 # package(:jar).with(:manifest=>'MANIFEST_MF')
419 def with(options)
420 options.each do |key, value|
421 begin
422 send "#{key}=", value
423 rescue NoMethodError
424 raise ArgumentError, "#{self.class.name} does not support the option #{key}"
425 end
426 end
427 self
428 end
429
430 def invoke_prerequisites(args, chain) #:nodoc:
431 @prepares.each { |prepare| prepare.call(self) }
432 @prepares.clear
433 @prerequisites |= @paths.collect { |name, path| path.sources }.flatten
434 super
435 end
436
437 def needed? #:nodoc:
438 return true unless File.exist?(name)
439 # You can do something like:
440 # include('foo', :path=>'foo').exclude('foo/bar', path=>'foo').
441 # include('foo/bar', :path=>'foo/bar')
442 # This will play havoc if we handled all the prerequisites together
443 # under the task, so instead we handle them individually for each path.
444 #
445 # We need to check that any file we include is not newer than the
446 # contents of the Zip. The file itself but also the directory it's
447 # coming from, since some tasks touch the directory, e.g. when the
448 # content of target/classes is included into a WAR.
449 most_recent = @paths.collect { |name, path| path.sources }.flatten.
450 select { |file| File.exist?(file) }.collect { |file| File.stat(file).mtime }.max
451 File.stat(name).mtime < (most_recent || Rake::EARLY) || super
452 end
453
454 # :call-seq:
455 # empty? => boolean
456 #
457 # Returns true if this ZIP file is empty (has no other entries inside).
458 def empty?
459 path("").empty
460 end
461
462 # :call-seq:
463 # contain(file*) => boolean
464 #
465 # Returns true if this ZIP file contains all the specified files. You can use absolute
466 # file names and glob patterns (using *, **, etc).
467 def contain?(*files)
468 path("").contain?(*files)
469 end
470
471 protected
472
473 # Adds a prepare block. These blocks are called early on for adding more content to
474 # the archive, before invoking prerequsities. Anything you add here will be invoked
475 # as a prerequisite and used to determine whether or not to generate this archive.
476 # In contrast, enhance blocks are evaluated after it was decided to create this archive.
477 def prepare(&block)
478 @prepares << block
479 end
480
481 def []=(key, value) #:nodoc:
482 raise ArgumentError, "This task does not support the option #{key}."
483 end
484
485 end
486
487
488 end
Generated using the rcov code coverage analysis tool for Ruby
version 0.8.2.1.