Skip to content

Commit e83e10e

Browse files
committed
Basics: support multiple formats with a single archiver (#6369)
In certain function signatures only a single archiver instance can be taken as an argument. We need to support multiple archive formats, such as `.zip` and `.tar.gz`, for example in cross-compilation destination bundles. Instead of updating those functions to take multiple archivers, creating a single type that can support multiple formats would be a better solution. Added `MultiFormatArchiver` that can handle multiple formats by delegating to other archivers it was initialized with. Since most uses of `Archiver` are either generic or take an existential, this allows us to pass `MultiFormatArchiver` to functions that previously supported only a single archive format. This is technically NFC as `MultiFormatArchiver` is not used anywhere yet. rdar://107485048
1 parent a0df005 commit e83e10e

File tree

13 files changed

+342
-3
lines changed

13 files changed

+342
-3
lines changed
File renamed without changes.
File renamed without changes.
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift open source project
4+
//
5+
// Copyright (c) 2014-2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See http://swift.org/LICENSE.txt for license information
9+
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import struct TSCBasic.AbsolutePath
14+
import protocol TSCBasic.FileSystem
15+
16+
/// An `Archiver` that handles multiple formats by delegating to other existing archivers each dedicated to its own
17+
/// format.
18+
public struct UniversalArchiver: Archiver {
19+
public var supportedExtensions: Set<String>
20+
21+
/// Errors specific to the implementation of ``UniversalArchiver``.
22+
enum Error: Swift.Error {
23+
case unknownFormat(String, AbsolutePath)
24+
case noFileNameExtension(AbsolutePath)
25+
26+
var description: String {
27+
switch self {
28+
case .unknownFormat(let ext, let path):
29+
return "unknown format with extension \(ext) at path `\(path)`"
30+
case .noFileNameExtension(let path):
31+
return "file at path `\(path)` has no extension to detect archival format from"
32+
}
33+
}
34+
}
35+
36+
/// A dictionary that maps file extension strings to archiver instances that supports these extensions.
37+
private let formatMapping: [String: any Archiver]
38+
39+
public init(_ fileSystem: any FileSystem, _ cancellator: Cancellator? = nil) {
40+
var formatMapping = [String: any Archiver]()
41+
var supportedExtensions = Set<String>()
42+
43+
for archiver in [
44+
ZipArchiver(fileSystem: fileSystem, cancellator: cancellator),
45+
TarArchiver(fileSystem: fileSystem, cancellator: cancellator),
46+
] as [any Archiver] {
47+
supportedExtensions.formUnion(archiver.supportedExtensions)
48+
for ext in archiver.supportedExtensions {
49+
formatMapping[ext] = archiver
50+
}
51+
}
52+
53+
self.formatMapping = formatMapping
54+
self.supportedExtensions = supportedExtensions
55+
}
56+
57+
private func archiver(for archivePath: AbsolutePath) throws -> any Archiver {
58+
guard let extensions = archivePath.allExtensions else {
59+
throw Error.noFileNameExtension(archivePath)
60+
}
61+
62+
guard let archiver = self.formatMapping[extensions] else {
63+
throw Error.unknownFormat(extensions, archivePath)
64+
}
65+
66+
return archiver
67+
}
68+
69+
public func extract(
70+
from archivePath: AbsolutePath,
71+
to destinationPath: AbsolutePath,
72+
completion: @escaping (Result<Void, Swift.Error>) -> Void
73+
) {
74+
do {
75+
let archiver = try archiver(for: archivePath)
76+
archiver.extract(from: archivePath, to: destinationPath, completion: completion)
77+
} catch {
78+
completion(.failure(error))
79+
}
80+
}
81+
82+
public func compress(
83+
directory: AbsolutePath,
84+
to destinationPath: AbsolutePath,
85+
completion: @escaping (Result<Void, Swift.Error>) -> Void
86+
) {
87+
do {
88+
let archiver = try archiver(for: destinationPath)
89+
archiver.compress(directory: directory, to: destinationPath, completion: completion)
90+
} catch {
91+
completion(.failure(error))
92+
}
93+
}
94+
95+
public func validate(
96+
path: AbsolutePath,
97+
completion: @escaping (Result<Bool, Swift.Error>) -> Void
98+
) {
99+
do {
100+
let archiver = try archiver(for: path)
101+
archiver.validate(path: path, completion: completion)
102+
} catch {
103+
completion(.failure(error))
104+
}
105+
}
106+
}
File renamed without changes.

Sources/Basics/CMakeLists.txt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@
77
# See http://swift.org/CONTRIBUTORS.txt for Swift project authors
88

99
add_library(Basics
10-
Archiver.swift
11-
Archiver+Tar.swift
12-
Archiver+Zip.swift
10+
Archiver/Archiver.swift
11+
Archiver/TarArchiver.swift
12+
Archiver/ZipArchiver.swift
13+
Archiver/UniversalArchiver.swift
1314
AuthorizationProvider.swift
1415
ByteString+Extensions.swift
1516
Cancellator.swift

Sources/Basics/FileSystem/Path+Extensions.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,22 @@ extension AbsolutePath {
3838
public func escapedPathString() -> String {
3939
return self.pathString.replacingOccurrences(of: "\\", with: "\\\\")
4040
}
41+
42+
/// Unlike ``AbsolutePath//extension``, this property returns all characters after the first `.` character in a
43+
/// `````. If no dot character is present in the filename or first dot is the last character, `nil` is returned.
44+
var allExtensions: String? {
45+
guard let firstDot = basename.firstIndex(of: ".") else {
46+
return nil
47+
}
48+
49+
var extensions = String(basename[firstDot ..< basename.endIndex])
50+
51+
guard extensions.count > 1 else {
52+
return nil
53+
}
54+
55+
extensions.removeFirst()
56+
57+
return extensions
58+
}
4159
}

0 commit comments

Comments
 (0)