lib/tasks/zip.rb in buildr-0.18.0 vs lib/tasks/zip.rb in buildr-0.19.0
- old
+ new
@@ -5,16 +5,47 @@
module Buildr
# The ZipTask creates a new ZIP file. You can include any number of files and
# and directories, use exclusion patterns, and include files into specific
# directories.
+ #
+ # For example:
+ # zip("test.zip").tap do |task|
+ # task.include "srcs"
+ # task.include "README", "LICENSE"
+ # end
+ #
+ # See Buildr#zip.
class ZipTask < Rake::FileTask
- # :nodoc:
- module IncludeFiles
+ # Which files go where. All the rules for including, excluding and merging files
+ # are handled by this object. A zip has at least one path.
+ class Path #:nodoc:
- # Include the specified files or directories.
+ attr_reader :actions
+
+ def initialize(zip, path)
+ @zip = zip
+ @path = "#{path}/" if path
+ expand_src = proc { (@files || []).map(&:to_s).uniq }
+ @sources = [ expand_src ]
+ @actions = [] << proc do |zip|
+ expand_src.call.each do |file|
+ if File.directory?(file)
+ in_directory(file, @files) do |file, rel_path|
+ puts "Adding #{@path}#{rel_path}" if Rake.application.options.trace
+ zip.add("#{@path}#{rel_path}", file) { true }
+ end
+ else
+ puts "Adding #{@path}#{File.basename(file)}" if Rake.application.options.trace
+ zip.add("#{@path}#{File.basename(file)}", file) { true }
+ end
+ end
+ end
+ end
+
+ # Documented in ZipTask.
def include(*files)
if Hash === files.last
options = files.pop
else
options = {}
@@ -35,18 +66,19 @@
else
raise "Unrecognized option #{options.keys.join(", ")}"
end
self
end
+ alias :add :include
- # Exclude the specified file or directories.
+ # Documented in ZipTask.
def exclude(*files)
(@files ||= FileList[]).exclude *files
self
end
- alias :add :include
+ # Documented in ZipTask.
def merge(*files)
if Hash === files.last
options = files.pop
else
options = {}
@@ -54,112 +86,70 @@
if options[:path]
path(options[:path]).merge *files +[ options.reject { |k,v| k == :path } ]
elsif options.keys.empty?
files.collect do |file|
- @expand_sources << proc { file.to_s }
+ @sources << proc { file.to_s }
expander = ZipExpander.new(file)
- @add_files << proc { |zip| expander.expand(zip, @path) }
+ @actions << proc { |zip| expander.expand(zip, @path) }
expander
end.first
else
raise "Unrecognized option #{options.keys.join(", ")}"
end
end
- protected
+ # Documented in ZipTask.
+ def path(path)
+ path.blank? ? self : @zip.path("#{@path}#{path}")
+ end
- def setup_path(path = nil)
- @path = "#{path}/" if path
- expand_src = proc { (@files || []).map(&:to_s).uniq }
- @expand_sources = [ expand_src ]
- @add_files = [] << proc do |zip|
- expand_src.call.each do |file|
- if File.directory?(file)
- in_directory(file, @files) do |file, rel_path|
- puts "Adding #{@path}#{rel_path}" if Rake.application.options.trace
- zip.add "#{@path}#{rel_path}", file
- end
- else
- puts "Adding #{@path}#{File.basename(file)}" if Rake.application.options.trace
- zip.add "#{@path}#{File.basename(file)}", file
- end
- end
- end
+ # Documented in ZipTask.
+ def root()
+ @zip
end
+ # Returns all the source files.
+ def sources()
+ @sources.map(&:call).flatten
+ end
+
+ protected
+
def include_as(source, as)
- @expand_sources << proc { source }
- @add_files << proc do |zip|
+ @sources << proc { source }
+ @actions << proc do |zip|
file = source.to_s
if File.directory?(file)
in_directory(file) do |file, rel_path|
- puts "Adding #{@path}#{as}/#{rel_path}" if Rake.application.options.trace
- zip.add file, "#{@path}#{as}/#{rel_path}"
+ if as == "."
+ dest = (@path || "") + rel_path.split("/")[1..-1].join("/")
+ else
+ dest = "#{@path}#{as}#{rel_path}"
+ end
+ puts "Adding #{dest}" if Rake.application.options.trace
+ zip.add(dest, file) { true }
end
else
puts "Adding #{@path}#{as}" if Rake.application.options.trace
- zip.add "#{@path}#{as}", file
+ zip.add("#{@path}#{as}", file) { true }
end
end
end
def in_directory(dir, excludes = nil)
prefix = Regexp.new("^" + Regexp.escape(File.dirname(dir) + File::SEPARATOR))
- Dir[File.join(dir, "**", "*")].
+ Dir["#{dir}/**/*"].
reject { |file| File.directory?(file) || (excludes && excludes.exclude?(file)) }.
each { |file| yield file, file.sub(prefix, "") }
end
- def expand_sources()
- @expand_sources.map(&:call).flatten
- end
-
- def add_file(zip)
- @add_files.each { |action| action.call zip }
- end
-
end
+ # Extend one Zip file into another.
+ class ZipExpander #:nodoc:
- # Which files go where.
- class Path
-
- include IncludeFiles
-
- def initialize(path)
- setup_path path
- end
-
- end
-
- include IncludeFiles
-
- def initialize(*args)
- super
- @paths = { nil=>self }
- setup_path
- enhance do |task|
- puts "Creating #{task.name}" if verbose
- # We're here because the Zip file does not exist, or one of the files is
- # newer than the Zip contents; in the later case, opening the Zip file
- # will add to its contents instead of replacing it, so we want the Zip
- # gone before we change it. We also don't want to see any partial updates.
- rm task.name, :verbose=>false rescue nil
- mkpath File.dirname(task.name), :verbose=>false
- begin
- Zip::ZipFile.open(task.name, Zip::ZipFile::CREATE) { |zip| create zip }
- rescue
- rm task.name, :verbose=>false rescue nil
- raise
- end
- end
- end
-
-
- class ZipExpander
-
def initialize(zip_file)
@zip_file = zip_file.to_s
end
def include(*files)
@@ -181,46 +171,157 @@
source.entries.reject { |entry| entry.directory? }.each do |entry|
if @includes.any? { |pattern| File.fnmatch(pattern, entry.name) } &&
!@excludes.any? { |pattern| File.fnmatch(pattern, entry.name) }
puts "Adding #{path}#{entry.name}" if Rake.application.options.trace
zip.get_output_stream("#{path}#{entry.name}") { |output| output.write source.read(entry) }
- # TODO: read and write file
end
end
end
end
end
+ def initialize(*args) #:nodoc:
+ super
+ @paths = { nil=>Path.new(self, nil) }
+ enhance do |task|
+ puts "Creating #{task.name}" if verbose
+ # We're here because the Zip file does not exist, or one of the files is
+ # newer than the Zip contents; in the later case, opening the Zip file
+ # will add to its contents instead of replacing it, so we want the Zip
+ # gone before we change it. We also don't want to see any partial updates.
+ rm task.name, :verbose=>false rescue nil
+ mkpath File.dirname(task.name), :verbose=>false
+ begin
+ Zip::ZipFile.open(task.name, Zip::ZipFile::CREATE) do |zip|
+ zip.restore_permissions = true
+ create zip
+ end
+ rescue
+ rm task.name, :verbose=>false rescue nil
+ raise
+ end
+ end
+ end
- # Returns a path to which you can include/exclude files.
+ # :call-seq:
+ # include(*files) => self
+ # include(*files, :path=>path) => self
+ # include(file, :as=>name) => self
+ # include(*zips, :merge=>true) => self
#
+ # Include files in the ZIP (or current path) and returns self.
+ #
+ # This method accepts three options. You can use :path to include files under
+ # a specific path, for example:
# zip(..).include("foo", :path=>"bar")
- # is equivalen to:
+ # includes the file bar as foo/bar. See also #path.
+ #
+ # You can use :as to include a file under a different name, for example:
+ # zip(..).include("foo", :as=>"bar")
+ # You can use the :as option in combination with the :path option, but only with
+ # a single file at a time.
+ #
+ # As a special case, you can include the entire contents of a directory by including
+ # the directory and using :as=>".". This:
+ # zip(..).include("srcs", :as=>".")
+ # will include all the source files, using the directory as a prerequisite. This:
+ # zip(..).include("srcs/*")
+ # includes all the same source files, using the source files as a prerequisite.
+ #
+ # You can use :merge option to include the contents of another ZIP file, for example:
+ # zip(..).include("foo.zip", :merge=>true)
+ # You can use the :merge option in combination with the :path option. See also #merge.
+ def include(*files)
+ @paths[nil].include *files
+ self
+ end
+ alias :add :include
+
+ # :call-seq:
+ # exclude(*files) => self
+ #
+ # Excludes files and returns self. Can be used in combination with include to
+ # prevent some files from being included.
+ def exclude(*files)
+ @paths[nil].exclude *files
+ self
+ end
+
+ # :call-seq:
+ # merge(*files) => Merge
+ # merge(*files, :path=>name) => Merge
+ #
+ # Merges ZIP files and returns a merge object. The contents of the merged ZIP file is
+ # extracted into this ZIP file (or current path).
+ #
+ # The returned object supports two methods: include and exclude. You can use these to
+ # merge only specific files from the ZIP. For example:
+ # zip(..).merge("src.zip").include("module1/*")
+ #
+ # This differs from include with the :merge option, which returns self.
+ def merge(*files)
+ @paths[nil].merge *files
+ end
+
+ # :call-seq:
+ # path(name) => Path
+ #
+ # Returns a path object. You can use the path object to include files in a given
+ # path inside the ZIP file. The path object implements the include, exclude, merge,
+ # path and root methods.
+ #
+ # For example:
# zip(..).path("bar").include("foo")
- def path(path)
- path.blank? ? @paths[nil] : (@paths[path] ||= Path.new(path))
+ # Will add the file foo under the name bar/foo.
+ #
+ # As a shorthand, you can also use the :path option:
+ # zip(..).include("foo", :path=>"bar")
+ def path(name)
+ name.blank? ? @paths[nil] : (@paths[name] ||= Path.new(self, name))
end
- # Pass options to the task. Returns self.
+ # :call-seq:
+ # root() => ZipTask
+ #
+ # Returns the root path, essentially the ZipTask object itself. In case you are wondering
+ # down paths and want to go back.
+ def root()
+ self
+ end
+
+ # :call-seq:
+ # with(options) => self
+ #
+ # Pass options to the task. Returns self. ZipTask itself does not support any options,
+ # but other tasks (e.g. JarTask, WarTask) do.
+ #
+ # For example:
+ # package(:jar).with(:manifest=>"MANIFEST_MF")
def with(options)
options.each do |key, value|
self[key] = value
end
self
end
+ # :call-seq:
+ # [name] = value
+ #
+ # Used by with method to set specific options. For example:
+ # package(:jar).with(:manifest=>"MANIFEST_MF")
+ # Or:
+ # package(:jar)[:manifest] = "MANIFEST_MF"
def []=(key, value)
fail "#{self.class} does not support the attribute #{key}"
end
-
- def invoke_prerequisites()
- super
- @paths.collect { |name, path| path.expand_sources }.flatten.each { |src| file(src).invoke }
- end
- def needed?()
+ def prerequisites() #:nodoc:
+ super + @paths.collect { |name, path| path.sources }.flatten.each { |src| file(src) }
+ end
+
+ def needed?() #:nodoc:
return true unless File.exist?(name)
# You can do something like:
# include("foo", :path=>"foo").exclude("foo/bar", path=>"foo").
# include("foo/bar", :path=>"foo/bar")
# This will play havoc if we handled all the prerequisites together
@@ -228,88 +329,105 @@
#
# We need to check that any file we include is not newer than the
# contents of the ZIP. The file itself but also the directory it's
# coming from, since some tasks touch the directory, e.g. when the
# content of target/classes is included into a WAR.
- most_recent = @paths.collect { |name, path| path.expand_sources }.flatten.
- each { |src| File.directory?(src) ? FileList[File.join(src, "**", "*")] | [src] : src }.flatten.
+ most_recent = @paths.collect { |name, path| path.sources }.flatten.
+ each { |src| File.directory?(src) ? FileList["#{src}/**/*"] | [src] : src }.flatten.
select { |file| File.exist?(file) }.collect { |file| File.stat(file).mtime }.max
File.stat(name).mtime < (most_recent || Rake::EARLY) || super
end
protected
+ # Sub-classes override this method to perform additional creation tasks,
+ # e.g. creating a manifest file in a JAR.
def create(zip)
- @paths.each { |name, obj| obj.add_file zip }
+ @paths.each { |name, obj| obj.actions.each { |action| action[zip] } }
end
end
+ # :call-seq:
+ # zip(file) => ZipTask
+ #
# The ZipTask creates a new ZIP file. You can include any number of files and
# and directories, use exclusion patterns, and include files into specific
# directories.
#
# For example:
- # returning(zip("test.zip")) { |task|
+ # zip("test.zip").tap do |task|
# task.include "srcs"
# task.include "README", "LICENSE"
# end
def zip(file)
ZipTask.define_task(file)
end
- # The UnzipTask expands the contents of a ZIP file into a target directory.
- # You can include any number of files and directories, use exclusion patterns,
- # and expand files from relative paths.
+ # An object for unzipping a file into a target directory. You can tell it to include
+ # or exclude only specific files and directories, and also to map files from particular
+ # paths inside the zip file into the target directory. Once ready, call #extract.
#
- # The file(s) to unzip is the first prerequisite.
- class UnzipTask < Rake::Task
+ # Usually it is more convenient to create a file task for extracting the zip file
+ # (see #unzip) and pass this object as a prerequisite to other tasks.
+ #
+ # See Buildr#unzip.
+ class Unzip
- # The target directory.
+ # The zip file to extract.
+ attr_accessor :zip_file
+ # The target directory to extract to.
attr_accessor :target
- def initialize(*args)
- super
+ # Initialize with hash argument of the form target=>zip_file.
+ def initialize(args)
+ @target, @zip_file = Rake.application.resolve_args(args)
@paths = {}
- enhance do |task|
- fail "Where do you want the file unzipped" unless target
+ end
- # If no paths specified, then no include/exclude patterns
- # specified. Nothing will happen unless we include all files.
- if @paths.empty?
- @paths[nil] = FromPath.new(nil)
- @paths[nil].include "*"
- end
+ # :call-seq:
+ # extract()
+ #
+ # Extract the zip file into the target directory.
+ #
+ # You can call this method directly. However, if you are using the #unzip method,
+ # it creates a file task for the target directory: use that task instead as a
+ # prerequisite. For example:
+ # build unzip(dir=>zip_file)
+ # Or:
+ # unzip(dir=>zip_file).target.invoke
+ def extract()
+ # If no paths specified, then no include/exclude patterns
+ # specified. Nothing will happen unless we include all files.
+ if @paths.empty?
+ @paths[nil] = FromPath.new(nil)
+ @paths[nil].include "*"
+ end
- # Otherwise, empty unzip creates target as a file when touching.
- mkpath target, :verbose=>false
- prerequisites.each do |file|
- Zip::ZipFile.open(file) do |zip|
- entries = zip.collect
- @paths.each do |path, patterns|
- patterns.map(entries).each do |dest, entry|
- next if entry.directory?
- dest = File.expand_path(dest, target)
- puts "Extracting #{dest}" if Rake.application.options.trace
- mkpath File.dirname(dest), :verbose=>false rescue nil
- entry.extract(dest) { true }
- end
- end
+ # Otherwise, empty unzip creates target as a file when touching.
+ mkpath target.to_s, :verbose=>false
+ Zip::ZipFile.open(zip_file.to_s) do |zip|
+ entries = zip.collect
+ @paths.each do |path, patterns|
+ patterns.map(entries).each do |dest, entry|
+ next if entry.directory?
+ dest = File.expand_path(dest, target.to_s)
+ puts "Extracting #{dest}" if Rake.application.options.trace
+ mkpath File.dirname(dest), :verbose=>false rescue nil
+ entry.extract(dest) { true }
end
end
- # Let other tasks know we updated the target directory.
- touch target, :verbose=>false
end
+ # Let other tasks know we updated the target directory.
+ touch target.to_s, :verbose=>false
end
- # Specifies directory to unzip to and return self.
- def into(target)
- self.target = target
- self
- end
-
+ # :call-seq:
+ # include(*files) => self
+ # include(*files, :path=>name) => self
+ #
# Include all files that match the patterns and returns self.
#
# Use include if you only want to unzip some of the files, by specifying
# them instead of using exclusion. You can use #include in combination
# with #exclude.
@@ -321,10 +439,13 @@
end
self
end
alias :add :include
+ # :call-seq:
+ # exclude(*files) => self
+ #
# Exclude all files that match the patterns and return self.
#
# Use exclude to unzip all files except those that match the pattern.
# You can use #exclude in combination with #include.
def exclude(*files)
@@ -334,10 +455,13 @@
from_path(nil).exclude *files
end
self
end
+ # :call-seq:
+ # from_path(name) => Path
+ #
# Allows you to unzip from a path. Returns an object you can use to
# specify which files to include/exclude relative to that path.
# Expands the file relative to that path.
#
# For example:
@@ -345,47 +469,43 @@
# will unzip etc/LICENSE into ./LICENSE.
#
# This is different from:
# unzip("test.jar").into(Dir.pwd).include("etc/LICENSE")
# which unzips etc/LICENSE into ./etc/LICENSE.
- def from_path(path)
- @paths[path] ||= FromPath.new(path)
+ def from_path(name)
+ @paths[name] ||= FromPath.new(name)
end
- def needed?()
- #return true unless target && File.exist?(target)
- #return true if prerequisites.any? { |prereq| File.stat(prereq).mtime > File.stat(target).mtime }
- #false
- true
+ # Returns the path to the target directory.
+ def to_s()
+ target.to_s
end
- # :nodoc:
- class FromPath
+ class FromPath #:nodoc:
def initialize(path)
if path
@path = path[-1] == ?/ ? path : path + "/"
else
@path = ""
end
end
# See UnzipTask#include
- def include(*files)
+ def include(*files) #:doc:
@include ||= []
@include |= files
self
end
# See UnzipTask#exclude
- def exclude(*files)
+ def exclude(*files) #:doc:
@exclude ||= []
@exclude |= files
self
end
- # :nodoc:
def map(entries)
includes = @include || ["*"]
excludes = @exclude || []
entries.inject({}) do |map, entry|
short = entry.name.sub(@path, "")
@@ -399,24 +519,32 @@
end
end
- # Defines a task that will unzip the specified file, into the directory
- # specified by calling #into. It is the second call to into that creates
- # and returns the task.
+ # :call-seq:
+ # unzip(to_dir=>zip_file) => Zip
#
- # You can unzip only some files by specifying an inclusion or exclusion
- # pattern, and unzip files from a path in the ZIP file. See UnzipTask
- # for more information.
+ # Creates a task that will unzip a file into the target directory. The task name
+ # is the target directory, the prerequisite is the file to unzip.
#
+ # This method creates a file task to expand the zip file. It returns an Unzip object
+ # that specifies how the file will be extracted. You can include or exclude specific
+ # files from within the zip, and map to different paths.
+ #
+ # The Unzip object's to_s method return the path to the target directory, so you can
+ # use it as a prerequisite. By keeping the Unzip object separate from the file task,
+ # you overlay additional work on top of the file task.
+ #
# For example:
- # unzip("test.zip").into("test")
- # unzip("test.zip").into("etc").include("README", "LICENSE")
- # unzip("test.zip").into("src").from_path("srcs")
- def unzip(file)
- task = nil
- namespace { task = UnzipTask.define_task("unzip"=>file) }
- task
+ # unzip("all"=>"test.zip")
+ # unzip("src"=>"test.zip").include("README", "LICENSE")
+ # unzip("libs"=>"test.zip").from_path("libs")
+ def unzip(args)
+ target, zip_file = Rake.application.resolve_args(args)
+ task = file(File.expand_path(target.to_s)=>zip_file)
+ Unzip.new(task=>zip_file).tap do |setup|
+ task.enhance { setup.extract }
+ end
end
end