lib/archive/zip.rb
def extract(destination, options = {})
      raise IOError, 'non-readable archive' unless readable?
      raise IOError, 'closed archive' if closed?

      # Ensure that unspecified options have default values.
      options[:directories] = true  unless options.has_key?(:directories)
      options[:symlinks]    = false unless options.has_key?(:symlinks)
      options[:overwrite]   = :all  unless options[:overwrite] == :older ||
                                           options[:overwrite] == :never
      options[:create]      = true  unless options.has_key?(:create)
      options[:flatten]     = false unless options.has_key?(:flatten)

      # Flattening the archive structure implies that directory entries are
      # skipped.
      options[:directories] = false if options[:flatten]

      # First extract all non-directory entries.
      directories = []
      each do |entry|
        # Compute the target file path.
        file_path = entry.zip_path
        file_path = File.basename(file_path) if options[:flatten]
        file_path = File.join(destination, file_path)

        # Cache some information about the file path.
        file_exists = File.exist?(file_path)
        file_mtime = File.mtime(file_path) if file_exists

        begin
          # Skip this entry if so directed.
          if (! file_exists && ! options[:create]) ||
             (file_exists &&
              (options[:overwrite] == :never ||
               options[:overwrite] == :older && entry.mtime <= file_mtime)) ||
             (! options[:exclude].nil? && options[:exclude][entry]) then
            next
          end

          # Set the decryption key for the entry.
          if options[:password].kind_of?(String) then
            entry.password = options[:password]
          elsif ! options[:password].nil? then
            entry.password = options[:password][entry]
          end

          if entry.directory? then
            # Record the directories as they are encountered.
            directories << entry
          elsif entry.file? || (entry.symlink? && options[:symlinks]) then
            # Extract files and symlinks.
            entry.extract(
              options.merge(:file_path => file_path)
            )
          end
        rescue StandardError => error
          unless options[:on_error].nil? then
            case options[:on_error][entry, error]
            when :retry
              retry
            when :skip
            else
              raise
            end
          else
            raise
          end
        end
      end

      if options[:directories] then
        # Then extract the directory entries in depth first order so that time
        # stamps, ownerships, and permissions can be properly restored.
        directories.sort { |a, b| b.zip_path <=> a.zip_path }.each do |entry|
          begin
            entry.extract(
              options.merge(
                :file_path => File.join(destination, entry.zip_path)
              )
            )
          rescue StandardError => error
            unless options[:on_error].nil? then
              case options[:on_error][entry, error]
              when :retry
                retry
              when :skip
              else
                raise
              end
            else
              raise
            end
          end
        end
      end

      nil
    end
def extract(destination, options = {})
      raise IOError, 'non-readable archive' unless readable?
      raise IOError, 'closed archive' if closed?

      # Ensure that unspecified options have default values.
      options[:directories] = true  unless options.has_key?(:directories)
      options[:symlinks]    = false unless options.has_key?(:symlinks)
      options[:overwrite]   = :all  unless options[:overwrite] == :older ||
                                           options[:overwrite] == :never
      options[:create]      = true  unless options.has_key?(:create)
      options[:flatten]     = false unless options.has_key?(:flatten)

      # Flattening the archive structure implies that directory entries are
      # skipped.
      options[:directories] = false if options[:flatten]

      # First extract all non-directory entries.
      directories = []
      each do |entry|
        # Compute the target file path.
        file_path = entry.zip_path
        file_path = File.basename(file_path) if options[:flatten]
        file_path = File.join(destination, file_path)

        # Cache some information about the file path.
        file_exists = File.exist?(file_path)
        file_mtime = File.mtime(file_path) if file_exists

        begin
          # Skip this entry if so directed.
          if (! file_exists && ! options[:create]) ||
             (file_exists &&
              (options[:overwrite] == :never ||
               options[:overwrite] == :older && entry.mtime <= file_mtime)) ||
             (! options[:exclude].nil? && options[:exclude][entry]) then
            next
          end

          # Set the decryption key for the entry.
          if options[:password].kind_of?(String) then
            entry.password = options[:password]
          elsif ! options[:password].nil? then
            entry.password = options[:password][entry]
          end

          if entry.directory? then
            # Record the directories as they are encountered.
            directories << entry
          elsif entry.file? || (entry.symlink? && options[:symlinks]) then
            # Extract files and symlinks.
            entry.extract(
              options.merge(:file_path => file_path)
            )
          end
        rescue StandardError => error
          unless options[:on_error].nil? then
            case options[:on_error][entry, error]
            when :retry
              retry
            when :skip
            else
              raise
            end
          else
            raise
          end
        end
      end

      if options[:directories] then
        # Then extract the directory entries in depth first order so that time
        # stamps, ownerships, and permissions can be properly restored.
        directories.sort { |a, b| b.zip_path <=> a.zip_path }.each do |entry|
          begin
            entry.extract(
              options.merge(
                :file_path => File.join(destination, entry.zip_path)
              )
            )
          rescue StandardError => error
            unless options[:on_error].nil? then
              case options[:on_error][entry, error]
              when :retry
                retry
              when :skip
              else
                raise
              end
            else
              raise
            end
          end
        end
      end

      nil
    end
Extracts the contents of the archive to _destination_, where _destination_
 is a path to a directory which will contain the contents of the archive.
 The destination path will be created if it does not already exist.

 _options_ is a Hash optionally containing the following:
 <b>:directories</b>::
   When set to +true+ (the default), entries representing directories in
   the archive are extracted.  This happens after all non-directory entries
   are extracted so that directory metadata can be properly updated.
 <b>:symlinks</b>::
   When set to +false+ (the default), entries representing symlinks in the
   archive are skipped.  When set to +true+, such entries are extracted.
   Exceptions may be raised on plaforms/file systems which do not support
   symlinks.
 <b>:overwrite</b>::
   When set to <tt>:all</tt> (the default), files which already exist will
   be replaced.  When set to <tt>:older</tt>, such files will only be
   replaced if they are older according to their last modified times than
   the zip entry which would replace them.  When set to <tt>:none</tt>,
   such files will never be replaced.  Any other value is the same as
   <tt>:all</tt>.
 <b>:create</b>::
   When set to +true+ (the default), files and directories which do not
   already exist will be extracted.  When set to +false+, only files and
   directories which already exist will be extracted (depending on the
   setting of <b>:overwrite</b>).
 <b>:flatten</b>::
   When set to +false+ (the default), the directory paths containing
   extracted files will be created within +destination+ in order to contain
   the files.  When set to +true+, files are extracted directly to
   +destination+ and directory entries are skipped.
 <b>:exclude</b>::
   Specifies a proc or lambda which takes a single argument containing a
   zip entry and returns +true+ if the entry should be skipped during
   extraction and +false+ if it should be extracted.
 <b>:password</b>::
   Specifies a proc, lambda, or a String.  If a proc or lambda is used, it
   must take a single argument containing a zip entry and return a String
   to be used as a decryption key for the entry.  If a String is used, it
   will be used as a decryption key for all encrypted entries.
 <b>:on_error</b>::
   Specifies a proc or lambda which is called when an exception is raised
   during the extraction of an entry.  It takes two arguments, a zip entry
   and an exception object generated while attempting to extract the entry.
   If <tt>:retry</tt> is returned, extraction of the entry is attempted
   again.  If <tt>:skip</tt> is returned, the entry is skipped.  Otherwise,
   the exception is raised.
 Any other options which are supported by Archive::Zip::Entry#extract are
 also supported.

 Raises Archive::Zip::IOError if called on a non-readable archive or after
 the archive is closed.

 == Example

 An archive, <tt>archive.zip</tt>, contains:
   zip-test/
   zip-test/dir1/
   zip-test/dir1/file2.txt
   zip-test/dir2/
   zip-test/file1.txt

 A directory, <tt>extract4</tt>, contains:
   zip-test
   +- dir1
   +- file1.txt

 Extract the archive:
   Archive::Zip.open('archive.zip') do |z|
     z.extract('extract1')
   end

   Archive::Zip.open('archive.zip') do |z|
     z.extract('extract2', :flatten => true)
   end

   Archive::Zip.open('archive.zip') do |z|
     z.extract('extract3', :create => false)
   end

   Archive::Zip.open('archive.zip') do |z|
     z.extract('extract3', :create => true)
   end

   Archive::Zip.open('archive.zip') do |z|
     z.extract( 'extract5', :exclude => lambda { |e| e.file? })
   end

 The directories contain:
   extract1 -> zip-test
               +- dir1
               |  +- file2.txt
               +- dir2
               +- file1.txt

   extract2 -> file2.txt
               file1.txt

   extract3 -> <empty>

   extract4 -> zip-test
               +- dir2
               +- file1.txt       <- from archive contents

   extract5 -> zip-test
               +- dir1
               +- dir2