Added a bunch of documentation

This commit is contained in:
thomas 2005-02-17 20:27:02 +00:00
parent 93b7add002
commit 3e1aa6d7e5
6 changed files with 175 additions and 47 deletions

31
README
View File

@ -4,29 +4,25 @@ rubyzip is a ruby library for reading and writing zip (pkzip format)
files, with the restriction that only uncompressed and deflated zip files, with the restriction that only uncompressed and deflated zip
entries are supported. All this library does is handling of the zip entries are supported. All this library does is handling of the zip
file format. the actual compression/decompression is handled by file format. the actual compression/decompression is handled by
zlib. zlib is accessible from ruby thanks to ruby/zlib (see below) zlib. zlib is accessible from ruby thanks to ruby/zlib.
To run the unit tests you need to have rubyunit or test::unit To run the unit tests you need to have test::unit installed.
installed.
= Install = Install
ruby install.rb To install from source run
ruby install.rb
= Prerequisites If you have Rake installed you can build a rubyzip gem with
This library requires ruby/zlib version 0.5.0 or newer. ruby/zlib is rake package
included in most recent ruby distributions.
* zlib http://www.gzip.org/zlib/
* ruby-zlib: http://www.blue.sky.or.jp/atelier/ruby/
= Documentation = Documentation
There is more than one way to access or create a zip archive with There is more than one way to access or create a zip archive with
rubyzip. The basic API is modelled after the classes in rubyzip. The basic API is modeled after the classes in
java.util.zip.* from the Java SDK. This means there are classes such java.util.zip.* from the Java SDK. This means there are classes such
as Zip::ZipInputStream, Zip::ZipOutputStream and as Zip::ZipInputStream, Zip::ZipOutputStream and
Zip::ZipFile. Zip::ZipInputStream provides a basic interface for Zip::ZipFile. Zip::ZipInputStream provides a basic interface for
@ -40,9 +36,9 @@ java.util.zip.ZipFile rubyzip's Zip::ZipFile is mutable, which means
it can be used to change zip files as well. it can be used to change zip files as well.
Another way to access a zip archive with rubyzip is to use rubyzip's Another way to access a zip archive with rubyzip is to use rubyzip's
lib/zip/zipfilesystem.rb API. Using this API files can be read from and written Zip::ZipFileSystem API. Using this API files can be read from and
to the archive in much the same manner as ruby's builtin classes written to the archive in much the same manner as ruby's builtin
allows files to be read from and written to the file system. classes allows files to be read from and written to the file system.
The samples/ directory is a good place to start to get a feel for The samples/ directory is a good place to start to get a feel for
using the library. For details about the specific behaviour of classes using the library. For details about the specific behaviour of classes
@ -59,9 +55,12 @@ http://www.ruby-lang.org/en/LICENSE.txt
http://rubyzip.sourceforge.net http://rubyzip.sourceforge.net
== Download (tarballs and gems)
= Author http://sourceforge.net/project/showfiles.php?group_id=43107&package_id=35377
Thomas Sondergaard (thomas at thomassondergaard.com) = Authors
Thomas Sondergaard (thomas at sondergaard.cc)
extra-field support contributed by Tatsuki Sugiura (sugi at nemui.org) extra-field support contributed by Tatsuki Sugiura (sugi at nemui.org)

View File

@ -60,7 +60,7 @@ end
Rake::RDocTask.new do |rd| Rake::RDocTask.new do |rd|
rd.main = "README" rd.main = "README"
rd.rdoc_files.add %W{ README NEWS TODO lib/** } rd.rdoc_files.add %W{ lib/zip/*.rb README NEWS TODO }
rd.options << "--title 'rubyzip documentation' --webcvs http://cvs.sourceforge.net/viewcvs.py/rubyzip/rubyzip/" rd.options << "--title 'rubyzip documentation' --webcvs http://cvs.sourceforge.net/viewcvs.py/rubyzip/rubyzip/"
# rd.options << "--all" # rd.options << "--all"
end end

5
TODO
View File

@ -1,9 +1,4 @@
* Add an extensively documented sample in samples/ that shows most of
what rubyzip does
* Add doc target to Rakefile that builds rdoc documentation and which
turns the samples in the sample directory into html which is included
in the output site
* Add web target to Rakefile to update rubyzip.sourceforge.net website * Add web target to Rakefile to update rubyzip.sourceforge.net website
* Add upload target which uploads dist to sourceforge and gem to where-ever * Add upload target which uploads dist to sourceforge and gem to where-ever
* Release 0.5.7 * Release 0.5.7

View File

@ -24,7 +24,7 @@ unless Object.method_defined?(:object_id)
end end
unless File.respond_to?(:read) unless File.respond_to?(:read)
class File class File # :nodoc:all
# singleton method read does not exist in 1.6.x # singleton method read does not exist in 1.6.x
def self.read(fileName) def self.read(fileName)
open(fileName) { |f| f.read } open(fileName) { |f| f.read }

View File

@ -735,7 +735,7 @@ module Zip
end end
class ZipCentralDirectory #:nodoc:all class ZipCentralDirectory
include Enumerable include Enumerable
END_OF_CENTRAL_DIRECTORY_SIGNATURE = 0x06054b50 END_OF_CENTRAL_DIRECTORY_SIGNATURE = 0x06054b50
@ -743,24 +743,25 @@ module Zip
STATIC_EOCD_SIZE = 22 STATIC_EOCD_SIZE = 22
attr_reader :comment attr_reader :comment
# Returns an Enumerable containing the entries.
def entries def entries
@entrySet.entries @entrySet.entries
end end
def initialize(entries = ZipEntrySet.new, comment = "") def initialize(entries = ZipEntrySet.new, comment = "") #:nodoc:
super() super()
@entrySet = entries.kind_of?(ZipEntrySet) ? entries : ZipEntrySet.new(entries) @entrySet = entries.kind_of?(ZipEntrySet) ? entries : ZipEntrySet.new(entries)
@comment = comment @comment = comment
end end
def write_to_stream(io) def write_to_stream(io) #:nodoc:
offset = io.tell offset = io.tell
@entrySet.each { |entry| entry.write_c_dir_entry(io) } @entrySet.each { |entry| entry.write_c_dir_entry(io) }
write_e_o_c_d(io, offset) write_e_o_c_d(io, offset)
end end
def write_e_o_c_d(io, offset) def write_e_o_c_d(io, offset) #:nodoc:
io << io <<
[END_OF_CENTRAL_DIRECTORY_SIGNATURE, [END_OF_CENTRAL_DIRECTORY_SIGNATURE,
0 , # @numberOfThisDisk 0 , # @numberOfThisDisk
@ -774,13 +775,13 @@ module Zip
end end
private :write_e_o_c_d private :write_e_o_c_d
def cdir_size def cdir_size #:nodoc:
# does not include eocd # does not include eocd
@entrySet.inject(0) { |value, entry| entry.cdir_header_size + value } @entrySet.inject(0) { |value, entry| entry.cdir_header_size + value }
end end
private :cdir_size private :cdir_size
def read_e_o_c_d(io) def read_e_o_c_d(io) #:nodoc:
buf = get_e_o_c_d(io) buf = get_e_o_c_d(io)
@numberOfThisDisk = ZipEntry::read_zip_short(buf) @numberOfThisDisk = ZipEntry::read_zip_short(buf)
@numberOfDiskWithStartOfCDir = ZipEntry::read_zip_short(buf) @numberOfDiskWithStartOfCDir = ZipEntry::read_zip_short(buf)
@ -793,7 +794,7 @@ module Zip
raise ZipError, "Zip consistency problem while reading eocd structure" unless buf.size == 0 raise ZipError, "Zip consistency problem while reading eocd structure" unless buf.size == 0
end end
def read_central_directory_entries(io) def read_central_directory_entries(io) #:nodoc:
begin begin
io.seek(@cdirOffset, IO::SEEK_SET) io.seek(@cdirOffset, IO::SEEK_SET)
rescue Errno::EINVAL rescue Errno::EINVAL
@ -805,12 +806,12 @@ module Zip
} }
end end
def read_from_stream(io) def read_from_stream(io) #:nodoc:
read_e_o_c_d(io) read_e_o_c_d(io)
read_central_directory_entries(io) read_central_directory_entries(io)
end end
def get_e_o_c_d(io) def get_e_o_c_d(io) #:nodoc:
begin begin
io.seek(-MAX_END_OF_CENTRAL_DIRECTORY_STRUCTURE_SIZE, IO::SEEK_END) io.seek(-MAX_END_OF_CENTRAL_DIRECTORY_STRUCTURE_SIZE, IO::SEEK_END)
rescue Errno::EINVAL rescue Errno::EINVAL
@ -827,16 +828,19 @@ module Zip
end end
return buf return buf
end end
# For iterating over the entries.
def each(&proc) def each(&proc)
@entrySet.each(&proc) @entrySet.each(&proc)
end end
# Returns the number of entries in the central directory (and
# consequently in the zip archive).
def size def size
@entrySet.size @entrySet.size
end end
def ZipCentralDirectory.read_from_stream(io) def ZipCentralDirectory.read_from_stream(io) #:nodoc:
cdir = new cdir = new
cdir.read_from_stream(io) cdir.read_from_stream(io)
return cdir return cdir
@ -844,7 +848,7 @@ module Zip
return nil return nil
end end
def == (other) def == (other) #:nodoc:
return false unless other.kind_of?(ZipCentralDirectory) return false unless other.kind_of?(ZipCentralDirectory)
@entrySet.entries.sort == other.entries.sort && comment == other.comment @entrySet.entries.sort == other.entries.sort && comment == other.comment
end end
@ -858,19 +862,64 @@ module Zip
class ZipCompressionMethodError < ZipError; end class ZipCompressionMethodError < ZipError; end
class ZipEntryNameError < ZipError; end class ZipEntryNameError < ZipError; end
# ZipFile is modeled after java.util.zip.ZipFile from the Java SDK.
# The most important methods are those inherited from
# ZipCentralDirectory for accessing information about the entries in
# the archive and methods such as get_input_stream and
# get_output_stream for reading from and writing entries to the
# archive. The class includes a few convenience methods such as
# #extract for extracting entries to the filesystem, and #remove,
# #replace, #rename and #mkdir for making simple modifications to
# the archive.
#
# Modifications to a zip archive are not committed until #commit or
# #close is called. The method #open accepts a block following
# the pattern from File.open offering a simple way to
# automatically close the archive when the block returns.
#
# The following example opens zip archive <code>my.zip</code>
# (creating it if it doesn't exist) and adds an entry
# <code>first.txt</code> and a directory entry <code>a_dir</code>
# to it.
#
# require 'zip/zip'
#
# Zip::ZipFile.open("my.zip", Zip::ZipFile::CREATE) {
# |zipfile|
# zipfile.get_output_stream("first.txt") { |f| f.puts "Hello from ZipFile" }
# zipfile.mkdir("a_dir")
# }
#
# The next example reopens <code>my.zip</code> writes the contents of
# <code>first.txt</code> to standard out and deletes the entry from
# the archive.
#
# require 'zip/zip'
#
# Zip::ZipFile.open("my.zip", Zip::ZipFile::CREATE) {
# |zipfile|
# puts zipfile.read("first.txt")
# zipfile.remove("first.txt")
# }
#
# ZipFileSystem offers an alternative API that emulates ruby's
# interface for accessing the filesystem, ie. the File and Dir classes.
class ZipFile < ZipCentralDirectory class ZipFile < ZipCentralDirectory
CREATE = 1 CREATE = 1
attr_reader :name attr_reader :name
# Opens a zip archive. Pass true as the second parameter to create
# a new archive if it doesn't exist already.
def initialize(fileName, create = nil) def initialize(fileName, create = nil)
super() super()
@name = fileName @name = fileName
@comment = "" @comment = ""
if (File.exists?(fileName)) if (File.exists?(fileName))
File.open(name, "rb") { |f| read_from_stream(f) } File.open(name, "rb") { |f| read_from_stream(f) }
elsif (create == ZipFile::CREATE) elsif (create)
@entrySet = ZipEntrySet.new @entrySet = ZipEntrySet.new
else else
raise ZipError, "File #{fileName} not found" raise ZipError, "File #{fileName} not found"
@ -878,7 +927,10 @@ module Zip
@create = create @create = create
@storedEntries = @entrySet.dup @storedEntries = @entrySet.dup
end end
# Same as #new. If a block is passed the ZipFile object is passed
# to the block and is automatically closed afterwards just as with
# ruby's builtin File.open method.
def ZipFile.open(fileName, create = nil) def ZipFile.open(fileName, create = nil)
zf = ZipFile.new(fileName, create) zf = ZipFile.new(fileName, create)
if block_given? if block_given?
@ -892,8 +944,15 @@ module Zip
end end
end end
# Returns the zip files comment, if it has one
attr_accessor :comment attr_accessor :comment
# Iterates over the contents of the ZipFile. This is more efficient
# than using a ZipInputStream since this methods simply iterates
# through the entries in the central directory structure in the archive
# whereas ZipInputStream jumps through the entire archive accessing the
# local entry headers (which contain the same information as the
# central directory).
def ZipFile.foreach(aZipFileName, &block) def ZipFile.foreach(aZipFileName, &block)
ZipFile.open(aZipFileName) { ZipFile.open(aZipFileName) {
|zipFile| |zipFile|
@ -901,10 +960,16 @@ module Zip
} }
end end
# Returns an input stream to the specified entry. If a block is passed
# the stream object is passed to the block and the stream is automatically
# closed afterwards just as with ruby's builtin File.open method.
def get_input_stream(entry, &aProc) def get_input_stream(entry, &aProc)
get_entry(entry).get_input_stream(&aProc) get_entry(entry).get_input_stream(&aProc)
end end
# Returns an output stream to the specified entry. If a block is passed
# the stream object is passed to the block and the stream is automatically
# closed afterwards just as with ruby's builtin File.open method.
def get_output_stream(entry, &aProc) def get_output_stream(entry, &aProc)
newEntry = entry.kind_of?(ZipEntry) ? entry : ZipEntry.new(@name, entry.to_s) newEntry = entry.kind_of?(ZipEntry) ? entry : ZipEntry.new(@name, entry.to_s)
if newEntry.directory? if newEntry.directory?
@ -916,14 +981,17 @@ module Zip
zipStreamableEntry.get_output_stream(&aProc) zipStreamableEntry.get_output_stream(&aProc)
end end
# Returns the name of the zip archive
def to_s def to_s
@name @name
end end
# Returns a string containing the contents of the specified entry
def read(entry) def read(entry)
get_input_stream(entry) { |is| is.read } get_input_stream(entry) { |is| is.read }
end end
# Convenience method for adding the contents of a file to the archive
def add(entry, srcPath, &continueOnExistsProc) def add(entry, srcPath, &continueOnExistsProc)
continueOnExistsProc ||= proc { false } continueOnExistsProc ||= proc { false }
check_entry_exists(entry, continueOnExistsProc, "add") check_entry_exists(entry, continueOnExistsProc, "add")
@ -935,21 +1003,26 @@ module Zip
end end
end end
# Removes the specified entry.
def remove(entry) def remove(entry)
@entrySet.delete(get_entry(entry)) @entrySet.delete(get_entry(entry))
end end
# Renames the specified entry.
def rename(entry, newName, &continueOnExistsProc) def rename(entry, newName, &continueOnExistsProc)
foundEntry = get_entry(entry) foundEntry = get_entry(entry)
check_entry_exists(newName, continueOnExistsProc, "rename") check_entry_exists(newName, continueOnExistsProc, "rename")
foundEntry.name=newName foundEntry.name=newName
end end
# Replaces the specified entry with the contents of srcPath (from
# the file system).
def replace(entry, srcPath) def replace(entry, srcPath)
check_file(srcPath) check_file(srcPath)
add(remove(entry), srcPath) add(remove(entry), srcPath)
end end
# Extracts entry to file destPath.
def extract(entry, destPath, &onExistsProc) def extract(entry, destPath, &onExistsProc)
onExistsProc ||= proc { false } onExistsProc ||= proc { false }
foundEntry = get_entry(entry) foundEntry = get_entry(entry)
@ -959,7 +1032,9 @@ module Zip
write_file(foundEntry, destPath, &onExistsProc) write_file(foundEntry, destPath, &onExistsProc)
end end
end end
# Commits changes that has been made since the previous commit to
# the zip archive.
def commit def commit
return if ! commit_required? return if ! commit_required?
on_success_replace(name) { on_success_replace(name) {
@ -974,22 +1049,29 @@ module Zip
} }
initialize(name) initialize(name)
end end
# Closes the zip file committing any changes that has been made.
def close def close
commit commit
end end
# Returns true if any changes has been made to this archive since
# the previous commit
def commit_required? def commit_required?
return @entrySet != @storedEntries || @create == ZipFile::CREATE return @entrySet != @storedEntries || @create == ZipFile::CREATE
end end
# Searches for entry with the specified name. Returns nil if
# no entry is found. See also get_entry
def find_entry(entry) def find_entry(entry)
@entrySet.detect { @entrySet.detect {
|e| |e|
e.name.sub(/\/$/, "") == entry.to_s.sub(/\/$/, "") e.name.sub(/\/$/, "") == entry.to_s.sub(/\/$/, "")
} }
end end
# Searches for an entry just as find_entry, but throws Errno::ENOENT
# if no entry is found.
def get_entry(entry) def get_entry(entry)
selectedEntry = find_entry(entry) selectedEntry = find_entry(entry)
unless selectedEntry unless selectedEntry
@ -998,6 +1080,7 @@ module Zip
return selectedEntry return selectedEntry
end end
# Creates a directory
def mkdir(entryName, permissionInt = 0) #permissionInt ignored def mkdir(entryName, permissionInt = 0) #permissionInt ignored
if find_entry(entryName) if find_entry(entryName)
raise Errno::EEXIST, "File exists - #{entryName}" raise Errno::EEXIST, "File exists - #{entryName}"

View File

@ -1,9 +1,42 @@
require 'zip/zip' require 'zip/zip'
module Zip module Zip
# The ZipFileSystem API provides an API for accessing entries in
# a zip archive that is similar to ruby's builtin File and Dir
# classes.
#
# Requiring 'zip/zipfilesystem' includes this module in ZipFile
# making the methods in this module available on ZipFile objects.
#
# Using this API the following example creates a new zip file
# <code>my.zip</code> containing a normal entry with the name
# <code>first.txt</code>, a directory entry named <code>mydir</code>
# and finally another normal entry named <code>second.txt</code>
#
# require 'zip/zipfilesystem'
#
# Zip::ZipFile.open("my.zip", Zip::ZipFile::CREATE) {
# |zipfile|
# zipfile.file.open("first.txt", "w") { |f| f.puts "Hello world" }
# zipfile.dir.mkdir("mydir")
# zipfile.file.open("mydir/second.txt", "w") { |f| f.puts "Hello again" }
# }
#
# Reading is as easy as writing, as the following example shows. The
# example writes the contents of <code>first.txt</code> from zip archive
# <code>my.zip</code> to standard out.
#
# require 'zip/zipfilesystem'
#
# Zip::ZipFile.open("my.zip") {
# |zipfile|
# puts zipfile.file.read("first.txt")
# }
module ZipFileSystem module ZipFileSystem
def initialize def initialize # :nodoc:
mappedZip = ZipFileNameMapper.new(self) mappedZip = ZipFileNameMapper.new(self)
@zipFsDir = ZipFsDir.new(mappedZip) @zipFsDir = ZipFsDir.new(mappedZip)
@zipFsFile = ZipFsFile.new(mappedZip) @zipFsFile = ZipFsFile.new(mappedZip)
@ -11,14 +44,26 @@ module Zip
@zipFsFile.dir = @zipFsDir @zipFsFile.dir = @zipFsDir
end end
# Returns a ZipFsDir which is much like ruby's builtin Dir (class)
# object, except it works on the ZipFile on which this method is
# invoked
def dir def dir
@zipFsDir @zipFsDir
end end
# Returns a ZipFsFile which is much like ruby's builtin File (class)
# object, except it works on the ZipFile on which this method is
# invoked
def file def file
@zipFsFile @zipFsFile
end end
# Instances of this class are normally accessed via the accessor
# ZipFile::file. An instance of ZipFsFile behaves like ruby's
# builtin File (class) object, except it works on ZipFile entries.
#
# The individual methods are not documented due to their
# similarity with the methods in File
class ZipFsFile class ZipFsFile
attr_writer :dir attr_writer :dir
@ -198,7 +243,7 @@ module Zip
@mappedZip.get_entry(fileName).size @mappedZip.get_entry(fileName).size
end end
# nil for not found and nil for directories # Returns nil for not found and nil for directories
def size?(fileName) def size?(fileName)
entry = @mappedZip.find_entry(fileName) entry = @mappedZip.find_entry(fileName)
return (entry == nil || entry.directory?) ? nil : entry.size return (entry == nil || entry.directory?) ? nil : entry.size
@ -367,6 +412,12 @@ module Zip
end end
end end
# Instances of this class are normally accessed via the accessor
# ZipFile::dir. An instance of ZipFsDir behaves like ruby's
# builtin Dir (class) object, except it works on ZipFile entries.
#
# The individual methods are not documented due to their
# similarity with the methods in Dir
class ZipFsDir class ZipFsDir
def initialize(mappedZip) def initialize(mappedZip)
@ -441,7 +492,7 @@ module Zip
end end
class ZipFsDirIterator class ZipFsDirIterator # :nodoc:all
include Enumerable include Enumerable
def initialize(arrayOfFileNames) def initialize(arrayOfFileNames)
@ -481,7 +532,7 @@ module Zip
# All access to ZipFile from ZipFsFile and ZipFsDir goes through a # All access to ZipFile from ZipFsFile and ZipFsDir goes through a
# ZipFileNameMapper, which has one responsibility: ensure # ZipFileNameMapper, which has one responsibility: ensure
class ZipFileNameMapper class ZipFileNameMapper # :nodoc:all
include Enumerable include Enumerable
def initialize(zipFile) def initialize(zipFile)