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.
Name Total lines Lines of code Total coverage Code coverage
lib/buildr/packaging/archive.rb 488 274
95.7%  
93.4%  
  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.

Valid XHTML 1.0! Valid CSS!