package zip
The grizzled.zip
package contains classes and functions to make it easier
to operate on zip and jar files.
- Alphabetic
- By Inheritance
- zip
- AnyRef
- Any
- Hide All
- Show All
- Public
- All
Type Members
-
class
Zipper extends AnyRef
The
Zipper
class provides a convenient mechanism for writing zip and jar files; it's a simplifying layer that sits on top of the existing Zip and Jar classes provided by the JDK.Zipper: Write zip and jar files more easily
The
Zipper
class provides a convenient mechanism for writing zip and jar files; it's a simplifying layer that sits on top of the existing Zip and Jar classes provided by the JDK. AZipper
object behaves somewhat like an immutable Scala collection, into which you can dropFile
objects,InputStream
objects,Reader
objects,Source
objects, URLs and pathnames. When you callwriteZip
orwriteJar
, the objects inZipper
are written to the actual underlying zip or jar file.A
Zipper
can either preserve pathnames or flatten the paths down to single components. When preserving pathnames, aZipper
object converts absolute paths to relative paths by stripping any leading "file system mount points." On Unix-like systems, this means stripping the leading "/"; on Windows, it means stripping any leading drive letter and the leading "\". (See java.io.File.listRoots() for more information.) For instance, if you're not flattening pathnames, and you addC:\Temp\hello.txt
to aZipper
on Windows, theZipper
will strip theC:\
, addingTemp/hello.txt
. to the zip or jar file. If you're on a Unix-like system, including Mac OS X, and you add/tmp/foo/bar.txt
, theZipper
will addtmp/foo/bar.txt
to the file.Directories
You can explicitly add directory entries to a
Zipper
, usingaddZipDirectory()
. When you're not flattening entries, aZipper
object will also ensure that any intermediate directories in a pathname are created in the zip file. For instance, if you add file/tmp/foo/bar/baz.txt
to aZipper
, without flattening it, theZipper
will create the following entries in the underlying zip file:tmp
(directory)tmp/foo
(directory)tmp/foo/bar
(directory)tmp/foo/bar/baz.txt
(the entry)
If you use the JDK's zip or jar classes directly, you have to create those intermediate directory entries yourself. In addition, you have to be careful not to create a directory more than once; doing so will cause an error.
Zipper
automatically creating unique intermediate directories for you.Constructing a Zipper object
The class constructor is private; use the companion object's
apply()
functions to instantiateZipper
objects.Using a Zipper object
The
addFile()
methods all returnTry
objects, and they do not modify the originalZipper
object. On success, they return aSuccess
object that contains a newZipper
.Because the
addFile()
methods returnTry
, they are unsuitable for use in traditional "builder" patterns. For instance, the following will not work:// Will NOT work val zipper = Zipper() zipper.addFile("/tmp/foo/bar.txt").addFile("/tmp/baz.txt")
There are other patterns you can use, however. Since
Try
is monadic, afor
comprehension works nicely:val zipper = Zipper() val newZipper = for { z1 <- zipper.addFile("/tmp/foo/bar.txt") z2 <- z1.addFile("/tmp/baz.txt") z3 <- z2.addFile("hello.txt") } yield z3 // newZipper is a Try[Zipper]
If you're trying to add a collection of objects, a
for
comprehension can be problematic. If you're not averse to using a localvar
, you can just use a traditional imperative loop:val zipper = Zipper() var z = zipper val paths: List[String] = ... for (path <- paths) { val t = z.addFile(path) z = t.get // will throw an exception if the add failed }
You can also avoid a
var
usingfoldLeft()
, though you still have to contend with a thrown exception. (You can always wrap the code in aTry
.)val zipper = Zipper() val paths: List[String] = ... paths.foldLeft(zipper) { case (z, path) => z.addFile(path).get // throws an exception if the add fails }
Finally, to avoid the exception and the
var
, use tail-recursion:import scala.annnotation.tailrec import scala.util.{Failure, Success, Try} @tailrec def addNext(paths: List[String], currentZipper: Zipper): Try[Zipper] = { paths match { case Nil => Success(currentZipper) case path :: rest => // Can't use currentZipper.addFile(path).map(), because the recursion // will then be invoked within the lambda, violating tail-recursion. currentZipper.addFile(path) match { case Failure(ex) => Failure(ex) case Success(z) => addNext(rest, z) } } } val paths: List[String] = ... val zipper = addNext(paths, Zipper())
Notes
A
Zipper
is not a true Scala collection. It does not support extensively querying its contents, looping over them, or transforming them. It is simply a container to be filled and then written.The
Zipper
class currently provides no support for storing uncompressed (i.e., fully inflated) entries. All data stored in the underlying zip is compressed, even though the JDK-supplied zip classes support both compressed and uncompressed entries. If necessary, theZipper
class can be extended to support storing uncompressed data.