Skip to content

Commit a70ef6f

Browse files
committed
Change function name to archive
1 parent a2636a7 commit a70ef6f

2 files changed

Lines changed: 82 additions & 28 deletions

File tree

Sources/ContainerizationArchive/ArchiveWriter.swift

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -306,28 +306,32 @@ extension ArchiveWriter {
306306
}
307307
}
308308

309-
public func archiveURLs(_ urls: [URL], base: URL) throws {
309+
public func archive(_ paths: [FilePath], base: FilePath) throws {
310310
let fm = FileManager.default
311+
let base = base.lexicallyNormalized()
311312

312-
for url in urls {
313-
guard url.path.starts(with: base.path) else {
314-
throw ArchiveError.failedToCreateArchive("'\(url.path)' is not under '\(base.path)'")
313+
for path in paths {
314+
guard path.starts(with: base) else {
315+
throw ArchiveError.failedToCreateArchive("'\(path.string)' is not under '\(base.string)'")
315316
}
316317

317-
let basePathCount = base.path == "/" ? 1 : (base.path.count + 1)
318-
let path = FilePath(String(url.path.dropFirst(basePathCount)))
319-
if url.isDirectory {
320-
guard let enumerator = fm.enumerator(atPath: url.path) else {
318+
let relativePath = path.components.dropFirst(base.components.count)
319+
.reduce(into: FilePath("")) { $0.append($1) }
320+
321+
var isDir: ObjCBool = false
322+
fm.fileExists(atPath: path.string, isDirectory: &isDir)
323+
if isDir.boolValue {
324+
guard let enumerator = fm.enumerator(atPath: path.string) else {
321325
throw POSIXError(.ENOTDIR)
322326
}
323327

324328
for case let child as String in enumerator {
325-
let relativePath = path.appending(child)
329+
let childPath = relativePath.appending(child)
326330

327-
try archive(relativePath, dirPath: FilePath(base.path))
331+
try archive(childPath, dirPath: base)
328332
}
329333
} else {
330-
try archive(path, dirPath: FilePath(base.path))
334+
try archive(relativePath, dirPath: base)
331335
}
332336
}
333337
}

Tests/ContainerizationArchiveTests/ArchiveTests.swift

Lines changed: 67 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
//
1818

1919
import Foundation
20+
import SystemPackage
2021
import Testing
2122

2223
@testable import ContainerizationArchive
@@ -559,7 +560,7 @@ struct ArchiveTests {
559560
#expect(try String(contentsOf: extractDir.appendingPathComponent("only.txt"), encoding: .utf8) == "only file")
560561
}
561562

562-
// MARK: - archiveURLs tests
563+
// MARK: - archive tests
563564

564565
@Test func archiveURLsBasic() throws {
565566
let testDir = createTemporaryDirectory(baseName: "ArchiveTests.archiveURLsBasic")!
@@ -568,13 +569,13 @@ struct ArchiveTests {
568569
try "alpha content".write(to: testDir.appendingPathComponent("alpha.txt"), atomically: true, encoding: .utf8)
569570
try "beta content".write(to: testDir.appendingPathComponent("beta.txt"), atomically: true, encoding: .utf8)
570571

571-
let files = [
572-
testDir.appendingPathComponent("alpha.txt"),
573-
testDir.appendingPathComponent("beta.txt"),
572+
let files: [FilePath] = [
573+
FilePath(testDir.appendingPathComponent("alpha.txt").path),
574+
FilePath(testDir.appendingPathComponent("beta.txt").path),
574575
]
575576
let archiveURL = testDir.appendingPathComponent("test.tar.gz")
576577
let writer = try ArchiveWriter(format: .pax, filter: .gzip, file: archiveURL)
577-
try writer.archiveURLs(files, base: testDir)
578+
try writer.archive(files, base: FilePath(testDir.path))
578579
try writer.finishEncoding()
579580

580581
var entries: [String: String] = [:]
@@ -595,7 +596,7 @@ struct ArchiveTests {
595596
let archiveURL = testDir.appendingPathComponent("test.tar.gz")
596597
let writer = try ArchiveWriter(format: .pax, filter: .gzip, file: archiveURL)
597598
#expect(throws: Never.self) {
598-
try writer.archiveURLs([], base: testDir)
599+
try writer.archive([], base: FilePath(testDir.path))
599600
}
600601
try writer.finishEncoding()
601602

@@ -613,7 +614,7 @@ struct ArchiveTests {
613614

614615
let archiveURL = testDir.appendingPathComponent("test.tar.gz")
615616
let writer = try ArchiveWriter(format: .pax, filter: .gzip, file: archiveURL)
616-
try writer.archiveURLs([testDir.appendingPathComponent("only.txt")], base: testDir)
617+
try writer.archive([FilePath(testDir.appendingPathComponent("only.txt").path)], base: FilePath(testDir.path))
617618
try writer.finishEncoding()
618619

619620
var entries: [String: String] = [:]
@@ -637,7 +638,7 @@ struct ArchiveTests {
637638

638639
let archiveURL = testDir.appendingPathComponent("test.tar.gz")
639640
let writer = try ArchiveWriter(format: .pax, filter: .gzip, file: archiveURL)
640-
try writer.archiveURLs([execFile], base: testDir)
641+
try writer.archive([FilePath(execFile.path)], base: FilePath(testDir.path))
641642
try writer.finishEncoding()
642643

643644
let extractDir = testDir.appendingPathComponent("extract")
@@ -661,14 +662,14 @@ struct ArchiveTests {
661662
try "a content".write(to: sourceDir.appendingPathComponent("a/deep.txt"), atomically: true, encoding: .utf8)
662663
try "nested content".write(to: sourceDir.appendingPathComponent("b/c/nested.txt"), atomically: true, encoding: .utf8)
663664

664-
let files = [
665-
sourceDir.appendingPathComponent("top.txt"),
666-
sourceDir.appendingPathComponent("a/deep.txt"),
667-
sourceDir.appendingPathComponent("b/c/nested.txt"),
665+
let files: [FilePath] = [
666+
FilePath(sourceDir.appendingPathComponent("top.txt").path),
667+
FilePath(sourceDir.appendingPathComponent("a/deep.txt").path),
668+
FilePath(sourceDir.appendingPathComponent("b/c/nested.txt").path),
668669
]
669670
let archiveURL = testDir.appendingPathComponent("test.tar.gz")
670671
let writer = try ArchiveWriter(format: .pax, filter: .gzip, file: archiveURL)
671-
try writer.archiveURLs(files, base: sourceDir)
672+
try writer.archive(files, base: FilePath(sourceDir.path))
672673
try writer.finishEncoding()
673674

674675
let extractDir = testDir.appendingPathComponent("extract")
@@ -694,13 +695,13 @@ struct ArchiveTests {
694695
try "sub content".write(to: subDir.appendingPathComponent("sub.txt"), atomically: true, encoding: .utf8)
695696
try "deep content".write(to: subDir.appendingPathComponent("nested/deep.txt"), atomically: true, encoding: .utf8)
696697

697-
let urls = [
698-
sourceDir.appendingPathComponent("top.txt"),
699-
subDir,
698+
let urls: [FilePath] = [
699+
FilePath(sourceDir.appendingPathComponent("top.txt").path),
700+
FilePath(subDir.path),
700701
]
701702
let archiveURL = testDir.appendingPathComponent("test.tar.gz")
702703
let writer = try ArchiveWriter(format: .pax, filter: .gzip, file: archiveURL)
703-
try writer.archiveURLs(urls, base: sourceDir)
704+
try writer.archive(urls, base: FilePath(sourceDir.path))
704705
try writer.finishEncoding()
705706

706707
let extractDir = testDir.appendingPathComponent("extract")
@@ -713,6 +714,55 @@ struct ArchiveTests {
713714
#expect(try String(contentsOf: extractDir.appendingPathComponent("subdir/nested/deep.txt"), encoding: .utf8) == "deep content")
714715
}
715716

717+
@Test func archiveURLsSymlinks() throws {
718+
let testDir = createTemporaryDirectory(baseName: "ArchiveTests.archiveURLsSymlinks")!
719+
defer { try? FileManager.default.removeItem(at: testDir) }
720+
721+
let sourceDir = testDir.appendingPathComponent("source")
722+
try FileManager.default.createDirectory(at: sourceDir, withIntermediateDirectories: true)
723+
724+
let fileURL = sourceDir.appendingPathComponent("file.txt")
725+
try "symlink content".write(to: fileURL, atomically: true, encoding: .utf8)
726+
727+
try FileManager.default.createSymbolicLink(
728+
atPath: sourceDir.appendingPathComponent("absolute").path,
729+
withDestinationPath: fileURL.path
730+
)
731+
try FileManager.default.createSymbolicLink(
732+
atPath: sourceDir.appendingPathComponent("relative").path,
733+
withDestinationPath: "file.txt"
734+
)
735+
736+
let archiveURL = testDir.appendingPathComponent("test.tar.gz")
737+
let writer = try ArchiveWriter(format: .pax, filter: .gzip, file: archiveURL)
738+
try writer.archive([FilePath(sourceDir.path)], base: FilePath(testDir.path))
739+
try writer.finishEncoding()
740+
741+
let extractDir = testDir.appendingPathComponent("extract")
742+
let reader = try ArchiveReader(file: archiveURL)
743+
let rejected = try reader.extractContents(to: extractDir)
744+
745+
#expect(rejected.isEmpty)
746+
747+
let extractedSource = extractDir.appendingPathComponent("source")
748+
#expect(
749+
try String(contentsOf: extractedSource.appendingPathComponent("file.txt"), encoding: .utf8)
750+
== "symlink content")
751+
752+
let relTarget = try FileManager.default.destinationOfSymbolicLink(
753+
atPath: extractedSource.appendingPathComponent("relative").path)
754+
#expect(relTarget == "file.txt")
755+
#expect(
756+
try String(contentsOf: extractedSource.appendingPathComponent("relative"), encoding: .utf8)
757+
== "symlink content")
758+
759+
let absTarget = try FileManager.default.destinationOfSymbolicLink(
760+
atPath: extractedSource.appendingPathComponent("absolute").path)
761+
762+
print("absTarget: \(absTarget), fileURL: \(fileURL.path)")
763+
#expect(absTarget == fileURL.path)
764+
}
765+
716766
@Test func archiveDirectorySymlinkRelativeSubdir() throws {
717767
let testDir = createTemporaryDirectory(baseName: "ArchiveTests.archiveDirSymlinkRelSubdir")!
718768
defer { try? FileManager.default.removeItem(at: testDir) }

0 commit comments

Comments
 (0)