Skip to content

Commit effe551

Browse files
committed
Support zipping empty directories
1 parent 36e00e0 commit effe551

File tree

2 files changed

+42
-21
lines changed

2 files changed

+42
-21
lines changed

os/src/ZipOps.scala

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ object zip {
8989
dest
9090
}
9191

92+
private def isEmptyDir(p: os.Path) = os.walk.stream(p).headOption.isEmpty
93+
9294
private def createNewZip0(
9395
sources: Seq[ZipSource],
9496
excludePatterns: Seq[Regex],
@@ -97,8 +99,14 @@ object zip {
9799
): Unit = {
98100
sources.foreach { source =>
99101
if (os.isDir(source.src)) {
100-
for (path <- os.walk(source.src)) {
101-
if (os.isFile(path) && shouldInclude(path.toString, excludePatterns, includePatterns)) {
102+
val contents = os.walk(source.src)
103+
if (contents.isEmpty)
104+
makeZipEntry0(source.src, source.dest.getOrElse(os.sub / source.src.last))
105+
for (path <- contents) {
106+
if (
107+
(os.isFile(path) && shouldInclude(path.toString, excludePatterns, includePatterns)) ||
108+
(os.isDir(path) && isEmptyDir(path))
109+
) {
102110
makeZipEntry0(path, source.dest.getOrElse(os.sub) / path.subRelativeTo(source.src))
103111
}
104112
}
@@ -125,6 +133,7 @@ object zip {
125133
includePatterns,
126134
(path, sub) => makeZipEntry(path, sub, preserveMtimes, zipOut)
127135
)
136+
zipOut.finish()
128137
} finally {
129138
zipOut.close()
130139
}
@@ -149,29 +158,22 @@ object zip {
149158
preserveMtimes: Boolean,
150159
zipOut: ZipOutputStream
151160
) = {
161+
val name =
162+
if (os.isDir(file))
163+
sub.toString() + java.io.File.separator
164+
else sub.toString()
165+
val zipEntry = new ZipEntry(name)
152166

153-
val mtimeOpt = if (preserveMtimes) Some(os.mtime(file)) else None
167+
val mtime = if (preserveMtimes) os.mtime(file) else 0
168+
zipEntry.setTime(mtime)
154169

155170
val fis = if (os.isFile(file)) Some(os.read.inputStream(file)) else None
156-
try makeZipEntry0(sub, fis, mtimeOpt, zipOut)
157-
finally fis.foreach(_.close())
158-
}
159171

160-
private def makeZipEntry0(
161-
sub: os.SubPath,
162-
is: Option[java.io.InputStream],
163-
preserveMtimes: Option[Long],
164-
zipOut: ZipOutputStream
165-
) = {
166-
val zipEntry = new ZipEntry(sub.toString)
167-
168-
preserveMtimes match {
169-
case Some(mtime) => zipEntry.setTime(mtime)
170-
case None => zipEntry.setTime(0)
171-
}
172-
173-
zipOut.putNextEntry(zipEntry)
174-
is.foreach(os.Internals.transfer(_, zipOut, close = false))
172+
try {
173+
zipOut.putNextEntry(zipEntry)
174+
fis.foreach(os.Internals.transfer(_, zipOut, close = false))
175+
zipOut.closeEntry()
176+
} finally fis.foreach(_.close())
175177
}
176178

177179
/**

os/test/src/ZipOpTests.scala

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,25 @@ object ZipOpTests extends TestSuite {
109109
assert(paths == expected)
110110
}
111111

112+
test("zipEmptyDir") - prep { wd =>
113+
val zipFileName = "zipEmptyDirs"
114+
115+
val emptyDir = wd / "empty1"
116+
os.makeDir(emptyDir)
117+
118+
val outerEmptyDir = wd / "outer"
119+
os.makeDir.all(outerEmptyDir)
120+
os.makeDir(outerEmptyDir / "empty2")
121+
122+
val zipped = os.zip(
123+
dest = wd / s"${zipFileName}.zip",
124+
sources = Seq(emptyDir, outerEmptyDir)
125+
)
126+
127+
val unzipped = os.unzip(zipped, wd / zipFileName)
128+
assert(List(unzipped / "empty1", unzipped / "empty2").forall(os.isDir))
129+
}
130+
112131
test("zipStream") - prep { wd =>
113132
val zipFileName = "zipStreamFunction.zip"
114133

0 commit comments

Comments
 (0)