Class: Archive::Zip
- Inherits:
-
Object
- Object
- Archive::Zip
- Includes:
- Enumerable
- Defined in:
- lib/archive/zip.rb,
lib/archive/zip/codec.rb,
lib/archive/zip/entry.rb,
lib/archive/zip/entry.rb,
lib/archive/zip/entry.rb,
lib/archive/zip/entry.rb,
lib/archive/zip/error.rb,
lib/archive/zip/version.rb,
lib/archive/zip/codec/store.rb,
lib/archive/zip/extra_field.rb,
lib/archive/zip/codec/deflate.rb,
lib/archive/zip/data_descriptor.rb,
lib/archive/zip/extra_field/raw.rb,
lib/archive/zip/extra_field/unix.rb,
lib/archive/zip/codec/null_encryption.rb,
lib/archive/zip/codec/traditional_encryption.rb,
lib/archive/zip/extra_field/extended_timestamp.rb
Overview
Archive::Zip represents a ZIP archive compatible with InfoZip tools and the archives they generate. It currently supports both stored and deflated ZIP entries, directory entries, file entries, and symlink entries. File and directory accessed and modified times, POSIX permissions, and ownerships can be archived and restored as well depending on platform support for such metadata. Traditional (weak) encryption is also supported.
Zip64, digital signatures, and strong encryption are not supported. ZIP archives can only be read from seekable kinds of IO, such as files; reading archives from pipes or any other non-seekable kind of IO is not supported. However, writing to such IO objects IS supported.
Defined Under Namespace
Modules: Codec, Entry, ExtraField Classes: DataDescriptor, EntryError, Error, ExtraFieldError, IOError, UnzipError
Constant Summary collapse
- EOCD_SIGNATURE =
The lead-in marker for the end of central directory record.
"PK\x5\x6"
- DS_SIGNATURE =
The lead-in marker for the digital signature record.
"PK\x5\x5"
- Z64EOCD_SIGNATURE =
The lead-in marker for the ZIP64 end of central directory record.
"PK\x6\x6"
- Z64EOCDL_SIGNATURE =
The lead-in marker for the ZIP64 end of central directory locator record.
"PK\x6\x7"
- CFH_SIGNATURE =
The lead-in marker for a central file record.
"PK\x1\x2"
- LFH_SIGNATURE =
The lead-in marker for a local file record.
"PK\x3\x4"
- DD_SIGNATURE =
The lead-in marker for data descriptor record.
"PK\x7\x8"
- VERSION =
The current version of this gem.
'0.12.0'
Instance Attribute Summary collapse
-
#comment ⇒ Object
A comment string for the ZIP archive.
Class Method Summary collapse
-
.archive(archive, paths, options = {}) ⇒ Object
Creates or possibly updates an archive using paths for new contents.
-
.extract(archive, destination, options = {}) ⇒ Object
Extracts the entries from an archive to destination.
-
.open(archive, mode = :r) ⇒ Object
Calls #new with the given arguments and yields the resulting Zip instance to the given block.
Instance Method Summary collapse
-
#add_entry(entry) ⇒ Object
(also: #<<)
Adds entry into a writable ZIP archive.
-
#archive(paths, options = {}) ⇒ Object
Adds paths to the archive.
-
#close ⇒ Object
Closes the archive.
-
#closed? ⇒ Boolean
Returns
true
if the ZIP archive is closed,false
otherwise. -
#dump(io) ⇒ Object
private
Writes all the entries of this archive to io.
-
#each(&b) ⇒ Object
Iterates through each entry of a readable ZIP archive in turn yielding each one to the given block.
-
#extract(destination, options = {}) ⇒ Object
Extracts the contents of the archive to destination, where destination is a path to a directory which will contain the contents of the archive.
-
#find_central_directory(io) ⇒ Object
private
Returns the file offset of the first record in the central directory.
-
#initialize(archive, mode = :r) ⇒ Zip
constructor
Opens an existing archive and/or creates a new archive.
-
#parse(io) ⇒ Object
private
NOTE: For now io MUST be seekable.
-
#readable? ⇒ Boolean
Returns
true
if the ZIP archive is readable,false
otherwise. -
#writable? ⇒ Boolean
Returns
true
if the ZIP archive is writable,false
otherwise.
Constructor Details
#initialize(archive, mode = :r) ⇒ Zip
Opens an existing archive and/or creates a new archive.
If archive is a String, it will be treated as a file path; otherwise, it is assumed to be an IO-like object with the necessary read or write support depending on the setting of mode. IO-like objects are not closed when the archive is closed, but files opened from file paths are. Set mode to :r
or "r"
to read the archive, and set it to :w
or "w"
to write the archive.
NOTE: The #close method must be called in order to save any modifications to the archive. Due to limitations in the Ruby finalization capabilities, the #close method is not automatically called when this object is garbage collected. Make sure to call #close when finished with this object.
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
# File 'lib/archive/zip.rb', line 133 def initialize(archive, mode = :r) @archive = archive mode = mode.to_sym if mode == :r || mode == :w then @mode = mode else raise ArgumentError, "illegal access mode #{mode}" end @close_delegate = false if @archive.kind_of?(String) then @close_delegate = true if mode == :r then @archive = File.open(@archive, 'rb') else @archive = File.open(@archive, 'wb') end end @entries = [] @comment = '' @closed = false @parse_complete = false end |
Instance Attribute Details
#comment ⇒ Object
A comment string for the ZIP archive.
158 159 160 |
# File 'lib/archive/zip.rb', line 158 def comment @comment end |
Class Method Details
.archive(archive, paths, options = {}) ⇒ Object
Creates or possibly updates an archive using paths for new contents.
If archive is a String, it is treated as a file path which will receive the archive contents. If the file already exists, it is assumed to be an archive and will be updated “in place”. Otherwise, a new archive is created. The archive will be closed once written.
If archive has any other kind of value, it is treated as a writable IO-like object which will be left open after the completion of this method.
NOTE: No attempt is made to prevent adding multiple entries with the same archive path.
See the instance method #archive for more information about paths and options.
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
# File 'lib/archive/zip.rb', line 62 def self.archive(archive, paths, = {}) if archive.kind_of?(String) && File.exist?(archive) then # Update the archive "in place". tmp_archive_path = nil File.open(archive) do |archive_in| Tempfile.open(*File.split(archive_in.path).reverse) do |archive_out| # Save off the path so that the temporary file can be renamed to the # archive file later. tmp_archive_path = archive_out.path # Ensure the file is in binary mode for Windows. archive_out.binmode # Update the archive. open(archive_in, :r) do |z_in| open(archive_out, :w) do |z_out| z_in.each { |entry| z_out << entry } z_out.archive(paths, ) end end end end # Set more reasonable permissions than those set by Tempfile. File.chmod(0666 & ~File.umask, tmp_archive_path) # Replace the input archive with the output archive. File.rename(tmp_archive_path, archive) else open(archive, :w) { |z| z.archive(paths, ) } end end |
.extract(archive, destination, options = {}) ⇒ Object
Extracts the entries from an archive to destination.
If archive is a String, it is treated as a file path pointing to an existing archive file. Otherwise, it is treated as a seekable and readable IO-like object.
See the instance method #extract for more information about destination and options.
99 100 101 |
# File 'lib/archive/zip.rb', line 99 def self.extract(archive, destination, = {}) open(archive, :r) { |z| z.extract(destination, ) } end |
.open(archive, mode = :r) ⇒ Object
Calls #new with the given arguments and yields the resulting Zip instance to the given block. Returns the result of the block and ensures that the Zip instance is closed.
This is a synonym for #new if no block is given.
108 109 110 111 112 113 114 115 116 117 |
# File 'lib/archive/zip.rb', line 108 def self.open(archive, mode = :r) zf = new(archive, mode) return zf unless block_given? begin yield(zf) ensure zf.close unless zf.closed? end end |
Instance Method Details
#add_entry(entry) ⇒ Object Also known as: <<
Adds entry into a writable ZIP archive.
NOTE: No attempt is made to prevent adding multiple entries with the same archive path.
Raises Archive::Zip::IOError if called on a non-writable archive or after the archive is closed.
223 224 225 226 227 228 229 230 231 232 |
# File 'lib/archive/zip.rb', line 223 def add_entry(entry) raise IOError, 'non-writable archive' unless writable? raise IOError, 'closed archive' if closed? unless entry.kind_of?(Entry) then raise ArgumentError, 'Archive::Zip::Entry instance required' end @entries << entry self end |
#archive(paths, options = {}) ⇒ Object
Adds paths to the archive. paths may be either a single path or an Array of paths. The files and directories referenced by paths are added using their respective basenames as their zip paths. The exception to this is when the basename for a path is either "."
or ".."
. In this case, the path is replaced with the paths to the contents of the directory it references.
options is a Hash optionally containing the following:
- :path_prefix
-
Specifies a prefix to be added to the zip_path attribute of each entry where ‘/’ is the file separator character. This defaults to the empty string. All values are passed through Archive::Zip::Entry.expand_path before use.
- :recursion
-
When set to
true
(the default), the contents of directories are recursively added to the archive. - :directories
-
When set to
true
(the default), entries are added to the archive for directories. Otherwise, the entries for directories will not be added; however, the contents of the directories will still be considered if the :recursion option istrue
. - :symlinks
-
When set to
false
(the default), entries for symlinks are excluded from the archive. Otherwise, they are included. NOTE: Unless :follow_symlinks is explicitly set, it will be set to the logical NOT of this option in calls to Archive::Zip::Entry.from_file. If symlinks should be completely ignored, set both this option and :follow_symlinks tofalse
. See Archive::Zip::Entry.from_file for details regarding :follow_symlinks. - :flatten
-
When set to
false
(the default), the directory paths containing archived files will be included in the zip paths of entries representing the files. When set totrue
, files are archived without any containing directory structure in the zip paths. Setting totrue
implies that :directories isfalse
and :path_prefix is empty. - :exclude
-
Specifies a proc or lambda which takes a single argument containing a prospective zip entry and returns
true
if the entry should be excluded from the archive andfalse
if it should be included. NOTE: If a directory is excluded in this way, the :recursion option has no effect for it. - :password
-
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 an encryption key for the entry. If a String is used, it will be used as an encryption key for all encrypted entries.
- :on_error
-
Specifies a proc or lambda which is called when an exception is raised during the archival of an entry. It takes two arguments, a file path and an exception object generated while attempting to archive the entry. If
:retry
is returned, archival of the entry is attempted again. If:skip
is returned, the entry is skipped. Otherwise, the exception is raised.
Any other options which are supported by Archive::Zip::Entry.from_file are also supported.
NOTE: No attempt is made to prevent adding multiple entries with the same archive path.
Raises Archive::Zip::IOError if called on a non-writable archive or after the archive is closed. Raises Archive::Zip::EntryError if the :on_error option is either unset or indicates that the error should be raised and Archive::Zip::Entry.from_file raises an error.
Example
A directory contains:
zip-test
+- dir1
| +- file2.txt
+- dir2
+- file1.txt
Create some archives:
Archive::Zip.open('zip-test1.zip') do |z|
z.archive('zip-test')
end
Archive::Zip.open('zip-test2.zip') do |z|
z.archive('zip-test/.', :path_prefix => 'a/b/c/d')
end
Archive::Zip.open('zip-test3.zip') do |z|
z.archive('zip-test', :directories => false)
end
Archive::Zip.open('zip-test4.zip') do |z|
z.archive('zip-test', :exclude => lambda { |e| e.file? })
end
The archives contain:
zip-test1.zip -> zip-test/
zip-test/dir1/
zip-test/dir1/file2.txt
zip-test/dir2/
zip-test/file1.txt
zip-test2.zip -> a/b/c/d/dir1/
a/b/c/d/dir1/file2.txt
a/b/c/d/dir2/
a/b/c/d/file1.txt
zip-test3.zip -> zip-test/dir1/file2.txt
zip-test/file1.txt
zip-test4.zip -> zip-test/
zip-test/dir1/
zip-test/dir2/
344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 |
# File 'lib/archive/zip.rb', line 344 def archive(paths, = {}) raise IOError, 'non-writable archive' unless writable? raise IOError, 'closed archive' if closed? # Ensure that paths is an enumerable. paths = [paths] unless paths.kind_of?(Enumerable) # If the basename of a path is '.' or '..', replace the path with the # paths of all the entries contained within the directory referenced by # the original path. paths = paths.collect do |path| basename = File.basename(path) if basename == '.' || basename == '..' then Dir.entries(path).reject do |e| e == '.' || e == '..' end.collect do |e| File.join(path, e) end else path end end.flatten.uniq # Ensure that unspecified options have default values. [:path_prefix] = '' unless .has_key?(:path_prefix) [:recursion] = true unless .has_key?(:recursion) [:directories] = true unless .has_key?(:directories) [:symlinks] = false unless .has_key?(:symlinks) [:flatten] = false unless .has_key?(:flatten) # Flattening the directory structure implies that directories are skipped # and that the path prefix should be ignored. if [:flatten] then [:path_prefix] = '' [:directories] = false end # Clean up the path prefix. [:path_prefix] = Entry.([:path_prefix].to_s) paths.each do |path| # Generate the zip path. zip_entry_path = File.basename(path) zip_entry_path += '/' if File.directory?(path) unless [:path_prefix].empty? then zip_entry_path = "#{options[:path_prefix]}/#{zip_entry_path}" end begin # Create the entry, but do not add it to the archive yet. zip_entry = Zip::Entry.from_file( path, .merge( :zip_path => zip_entry_path, :follow_symlinks => .has_key?(:follow_symlinks) ? [:follow_symlinks] : ! [:symlinks] ) ) rescue StandardError => error unless [:on_error].nil? then case [:on_error][path, error] when :retry retry when :skip next else raise end else raise end end # Skip this entry if so directed. if (zip_entry.symlink? && ! [:symlinks]) || (! [:exclude].nil? && [:exclude][zip_entry]) then next end # Set the encryption key for the entry. if [:password].kind_of?(String) then zip_entry.password = [:password] elsif ! [:password].nil? then zip_entry.password = [:password][zip_entry] end # Add entries for directories (if requested) and files/symlinks. if (! zip_entry.directory? || [:directories]) then add_entry(zip_entry) end # Recurse into subdirectories (if requested). if zip_entry.directory? && [:recursion] then archive( Dir.entries(path).reject do |e| e == '.' || e == '..' end.collect do |e| File.join(path, e) end, .merge(:path_prefix => zip_entry_path) ) end end nil end |
#close ⇒ Object
Closes the archive.
Failure to close the archive by calling this method may result in a loss of data for writable archives.
NOTE: The underlying stream is only closed if the archive was opened with a String for the archive parameter.
Raises Archive::Zip::IOError if called more than once.
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 |
# File 'lib/archive/zip.rb', line 169 def close raise IOError, 'closed archive' if closed? if writable? then # Write the new archive contents. dump(@archive) end # Note that we only close delegate streams which are opened by us so that # the user may do so for other delegate streams at his/her discretion. @archive.close if @close_delegate @closed = true nil end |
#closed? ⇒ Boolean
Returns true
if the ZIP archive is closed, false
otherwise.
186 187 188 |
# File 'lib/archive/zip.rb', line 186 def closed? @closed end |
#dump(io) ⇒ Object (private)
Writes all the entries of this archive to io. io must be a writable, IO-like object providing a write method. Returns the total number of bytes written.
711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 |
# File 'lib/archive/zip.rb', line 711 def dump(io) bytes_written = 0 @entries.each do |entry| bytes_written += entry.dump_local_file_record(io, bytes_written) end central_directory_offset = bytes_written @entries.each do |entry| bytes_written += entry.dump_central_file_record(io) end central_directory_length = bytes_written - central_directory_offset bytes_written += io.write(EOCD_SIGNATURE) bytes_written += io.write( [ 0, 0, @entries.length, @entries.length, central_directory_length, central_directory_offset, comment.bytesize ].pack('vvvvVVv') ) bytes_written += io.write(comment) bytes_written end |
#each(&b) ⇒ Object
Iterates through each entry of a readable ZIP archive in turn yielding each one to the given block.
Raises Archive::Zip::IOError if called on a non-readable archive or after the archive is closed.
205 206 207 208 209 210 211 212 213 214 |
# File 'lib/archive/zip.rb', line 205 def each(&b) raise IOError, 'non-readable archive' unless readable? raise IOError, 'closed archive' if closed? unless @parse_complete then parse(@archive) @parse_complete = true end @entries.each(&b) end |
#extract(destination, options = {}) ⇒ Object
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:
- :directories
-
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. - :symlinks
-
When set to
false
(the default), entries representing symlinks in the archive are skipped. When set totrue
, such entries are extracted. Exceptions may be raised on plaforms/file systems which do not support symlinks. - :overwrite
-
When set to
:all
(the default), files which already exist will be replaced. When set to:older
, 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:none
, such files will never be replaced. Any other value is the same as:all
. - :create
-
When set to
true
(the default), files and directories which do not already exist will be extracted. When set tofalse
, only files and directories which already exist will be extracted (depending on the setting of :overwrite). - :flatten
-
When set to
false
(the default), the directory paths containing extracted files will be created withindestination
in order to contain the files. When set totrue
, files are extracted directly todestination
and directory entries are skipped. - :exclude
-
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 andfalse
if it should be extracted. - :password
-
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.
- :on_error
-
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
:retry
is returned, extraction of the entry is attempted again. If:skip
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, archive.zip
, contains:
zip-test/
zip-test/dir1/
zip-test/dir1/file2.txt
zip-test/dir2/
zip-test/file1.txt
A directory, extract4
, 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
558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 |
# File 'lib/archive/zip.rb', line 558 def extract(destination, = {}) raise IOError, 'non-readable archive' unless readable? raise IOError, 'closed archive' if closed? # Ensure that unspecified options have default values. [:directories] = true unless .has_key?(:directories) [:symlinks] = false unless .has_key?(:symlinks) [:overwrite] = :all unless [:overwrite] == :older || [:overwrite] == :never [:create] = true unless .has_key?(:create) [:flatten] = false unless .has_key?(:flatten) # Flattening the archive structure implies that directory entries are # skipped. [:directories] = false if [: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 [: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 && ! [:create]) || (file_exists && ([:overwrite] == :never || [:overwrite] == :older && entry.mtime <= file_mtime)) || (! [:exclude].nil? && [:exclude][entry]) then next end # Set the decryption key for the entry. if [:password].kind_of?(String) then entry.password = [:password] elsif ! [:password].nil? then entry.password = [:password][entry] end if entry.directory? then # Record the directories as they are encountered. directories << entry elsif entry.file? || (entry.symlink? && [:symlinks]) then # Extract files and symlinks. entry.extract( .merge(:file_path => file_path) ) end rescue StandardError => error unless [:on_error].nil? then case [:on_error][entry, error] when :retry retry when :skip else raise end else raise end end end if [: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( .merge( :file_path => File.join(destination, entry.zip_path) ) ) rescue StandardError => error unless [:on_error].nil? then case [:on_error][entry, error] when :retry retry when :skip else raise end else raise end end end end nil end |
#find_central_directory(io) ⇒ Object (private)
Returns the file offset of the first record in the central directory. io must be a seekable, readable, IO-like object.
Raises Archive::Zip::UnzipError if the end of central directory signature is not found where expected or at all.
678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 |
# File 'lib/archive/zip.rb', line 678 def find_central_directory(io) # First find the offset to the end of central directory record. # It is expected that the variable length comment field will usually be # empty and as a result the initial value of eocd_offset is all that is # necessary. # # NOTE: A cleverly crafted comment could throw this thing off if the # comment itself looks like a valid end of central directory record. eocd_offset = -22 loop do io.seek(eocd_offset, IO::SEEK_END) if IOExtensions.read_exactly(io, 4) == EOCD_SIGNATURE then io.seek(16, IO::SEEK_CUR) if IOExtensions.read_exactly(io, 2).unpack('v')[0] == (eocd_offset + 22).abs then break end end eocd_offset -= 1 end # At this point, eocd_offset should point to the location of the end of # central directory record relative to the end of the archive. # Now, jump into the location in the record which contains a pointer to # the start of the central directory record and return the value. io.seek(eocd_offset + 16, IO::SEEK_END) return IOExtensions.read_exactly(io, 4).unpack('V')[0] rescue Errno::EINVAL raise Zip::UnzipError, 'unable to locate end-of-central-directory record' end |
#parse(io) ⇒ Object (private)
NOTE: For now io MUST be seekable.
659 660 661 662 663 664 665 666 667 668 669 670 671 |
# File 'lib/archive/zip.rb', line 659 def parse(io) socd_pos = find_central_directory(io) io.seek(socd_pos) # Parse each entry in the central directory. loop do signature = IOExtensions.read_exactly(io, 4) break unless signature == CFH_SIGNATURE @entries << Zip::Entry.parse(io) end # Maybe add support for digital signatures and ZIP64 records... Later nil end |
#readable? ⇒ Boolean
Returns true
if the ZIP archive is readable, false
otherwise.
191 192 193 |
# File 'lib/archive/zip.rb', line 191 def readable? @mode == :r end |
#writable? ⇒ Boolean
Returns true
if the ZIP archive is writable, false
otherwise.
196 197 198 |
# File 'lib/archive/zip.rb', line 196 def writable? @mode == :w end |