9
9
*/
10
10
11
11
import Foundation
12
+ import XCTest
12
13
13
14
/*
14
15
This file contains API for working with folder hierarchies, and is extensible to allow for testing
15
16
for hierarchies as well.
16
17
*/
17
18
18
19
/// An abstract representation of a file (or folder).
19
- protocol File {
20
+ public protocol File {
20
21
/// The name of the file.
21
22
var name : String { get }
22
23
23
24
/// Writes the file to a given URL.
24
25
func write( to url: URL ) throws
25
26
}
26
27
27
- extension File {
28
+ public extension File {
28
29
/// Writes the file inside of a folder and returns the URL that it was written to.
29
30
@discardableResult
30
31
func write( inside url: URL ) throws -> URL {
@@ -35,12 +36,12 @@ extension File {
35
36
}
36
37
37
38
/// An item which provides data.
38
- protocol DataRepresentable {
39
+ public protocol DataRepresentable {
39
40
func data( ) throws -> Data
40
41
}
41
42
42
43
/// `DataRepresentable` can automatically write itself to disk via `Data.write(to:)`
43
- extension DataRepresentable {
44
+ public extension DataRepresentable {
44
45
func write( to url: URL ) throws {
45
46
try data ( ) . write ( to: url)
46
47
}
@@ -49,17 +50,22 @@ extension DataRepresentable {
49
50
// MARK: -
50
51
51
52
/// An abstract representation of a folder, containing some files or folders.
52
- struct Folder : File {
53
- let name : String
53
+ public struct Folder : File {
54
+ public init ( name: String , content: [ File ] ) {
55
+ self . name = name
56
+ self . content = content
57
+ }
58
+
59
+ public let name : String
54
60
55
61
/// The files and sub folders that this folder contains.
56
- let content : [ File ]
62
+ public let content : [ File ]
57
63
58
- func appendingFile( _ newFile: File ) -> Folder {
64
+ public func appendingFile( _ newFile: File ) -> Folder {
59
65
return Folder ( name: name, content: content + [ newFile] )
60
66
}
61
67
62
- func write( to url: URL ) throws {
68
+ public func write( to url: URL ) throws {
63
69
try FileManager . default. createDirectory ( at: url, withIntermediateDirectories: false , attributes: nil )
64
70
for file in content {
65
71
try file. write ( inside: url)
@@ -69,7 +75,7 @@ struct Folder: File {
69
75
70
76
extension Folder {
71
77
/// Returns a flat list of a folder's recursive listing for testing purposes.
72
- var recursiveContent : [ File ] {
78
+ public var recursiveContent : [ File ] {
73
79
var result = content
74
80
for file in content {
75
81
if let content = ( file as? Folder ) ? . recursiveContent {
@@ -81,13 +87,13 @@ extension Folder {
81
87
}
82
88
83
89
/// A representation of an Info.plist file.
84
- struct InfoPlist : File , DataRepresentable {
85
- let name = " Info.plist "
90
+ public struct InfoPlist : File , DataRepresentable {
91
+ public let name = " Info.plist "
86
92
87
93
/// The information that the Into.plist file contains.
88
- let content : Content
94
+ public let content : Content
89
95
90
- init ( displayName: String , identifier: String , versionString: String = " 1.0 " , developmentRegion: String = " en " ) {
96
+ public init ( displayName: String , identifier: String , versionString: String = " 1.0 " , developmentRegion: String = " en " ) {
91
97
self . content = Content (
92
98
displayName: displayName,
93
99
identifier: identifier,
@@ -96,11 +102,11 @@ struct InfoPlist: File, DataRepresentable {
96
102
)
97
103
}
98
104
99
- struct Content : Codable , Equatable {
100
- let displayName : String
101
- let identifier : String
102
- let versionString : String
103
- let developmentRegion : String
105
+ public struct Content : Codable , Equatable {
106
+ public let displayName : String
107
+ public let identifier : String
108
+ public let versionString : String
109
+ public let developmentRegion : String
104
110
105
111
fileprivate init ( displayName: String , identifier: String , versionString: String , developmentRegion: String ) {
106
112
self . displayName = displayName
@@ -117,7 +123,7 @@ struct InfoPlist: File, DataRepresentable {
117
123
}
118
124
}
119
125
120
- func data( ) throws -> Data {
126
+ public func data( ) throws -> Data {
121
127
// TODO: Replace this with PropertListEncoder (see below) when it's available in swift-corelibs-foundation
122
128
// https://github.com/apple/swift-corelibs-foundation/commit/d2d72f88d93f7645b94c21af88a7c9f69c979e4f
123
129
let infoPlist = [
@@ -136,74 +142,85 @@ struct InfoPlist: File, DataRepresentable {
136
142
}
137
143
138
144
/// A representation of a text file with some UTF-8 content.
139
- struct TextFile : File , DataRepresentable {
140
- let name : String
145
+ public struct TextFile : File , DataRepresentable {
146
+ public init ( name: String , utf8Content: String ) {
147
+ self . name = name
148
+ self . utf8Content = utf8Content
149
+ }
150
+
151
+ public let name : String
141
152
142
153
/// The UTF8 content of the file.
143
- let utf8Content : String
154
+ public let utf8Content : String
144
155
145
- func data( ) throws -> Data {
156
+ public func data( ) throws -> Data {
146
157
return utf8Content. data ( using: . utf8) !
147
158
}
148
159
}
149
160
150
161
/// A representation of a text file with some UTF-8 content.
151
- struct JSONFile < Content: Codable > : File , DataRepresentable {
152
- let name : String
162
+ public struct JSONFile < Content: Codable > : File , DataRepresentable {
163
+ public init ( name: String , content: Content ) {
164
+ self . name = name
165
+ self . content = content
166
+ }
167
+
168
+ public let name : String
153
169
154
170
/// The UTF8 content of the file.
155
- let content : Content
171
+ public let content : Content
156
172
157
- func data( ) throws -> Data {
173
+ public func data( ) throws -> Data {
158
174
return try JSONEncoder ( ) . encode ( content)
159
175
}
160
176
}
161
177
162
178
/// A copy of another file on disk somewhere.
163
- struct CopyOfFile : File , DataRepresentable {
164
- enum Error : DescribedError {
179
+ public struct CopyOfFile : File , DataRepresentable {
180
+ enum Error : LocalizedError {
165
181
case notAFile( URL )
166
182
var errorDescription : String {
167
183
switch self {
168
- case . notAFile( let url) : return " Original url is not a file: \( url. path. singleQuoted ) "
184
+ case . notAFile( let url) : return " Original url is not a file: ' \( url. path) ' "
169
185
}
170
186
}
171
187
}
172
188
173
189
/// The original file.
174
- let original : URL
175
- let name : String
190
+ public let original : URL
191
+ public let name : String
176
192
177
- init ( original: URL , newName: String ? = nil ) {
193
+ public init ( original: URL , newName: String ? = nil ) {
178
194
self . original = original
179
195
self . name = newName ?? original. lastPathComponent
180
196
}
181
197
182
- func data( ) throws -> Data {
198
+ public func data( ) throws -> Data {
183
199
// Note that `CopyOfFile` always reads a file from disk and so it's okay
184
200
// to use `FileManager.default` directly here instead of `FileManagerProtocol`.
185
- guard !FileManager. default. directoryExists ( atPath: original. path) else { throw Error . notAFile ( original) }
201
+ var isDirectory : ObjCBool = false
202
+ guard FileManager . default. fileExists ( atPath: original. path, isDirectory: & isDirectory) , !isDirectory. boolValue else { throw Error . notAFile ( original) }
186
203
return try Data ( contentsOf: original)
187
204
}
188
205
189
- func write( to url: URL ) throws {
206
+ public func write( to url: URL ) throws {
190
207
try FileManager . default. copyItem ( at: original, to: url)
191
208
}
192
209
}
193
210
194
- struct CopyOfFolder : File {
211
+ public struct CopyOfFolder : File {
195
212
/// The original file.
196
213
let original : URL
197
- let name : String
214
+ public let name : String
198
215
let shouldCopyFile : ( URL ) -> Bool
199
216
200
- init ( original: URL , newName: String ? = nil , filter shouldCopyFile: @escaping ( URL ) -> Bool = { _ in true } ) {
217
+ public init ( original: URL , newName: String ? = nil , filter shouldCopyFile: @escaping ( URL ) -> Bool = { _ in true } ) {
201
218
self . original = original
202
219
self . name = newName ?? original. lastPathComponent
203
220
self . shouldCopyFile = shouldCopyFile
204
221
}
205
222
206
- func write( to url: URL ) throws {
223
+ public func write( to url: URL ) throws {
207
224
try FileManager . default. createDirectory ( at: url, withIntermediateDirectories: false , attributes: nil )
208
225
for filePath in try FileManager . default. contentsOfDirectory ( atPath: original. path) {
209
226
// `contentsOfDirectory(atPath)` includes hidden files, skipHiddenFiles option doesn't help on Linux.
@@ -217,46 +234,30 @@ struct CopyOfFolder: File {
217
234
}
218
235
219
236
/// A file backed by `Data`.
220
- struct DataFile : File , DataRepresentable {
221
- var name : String
237
+ public struct DataFile : File , DataRepresentable {
238
+ public var name : String
222
239
var _data : Data
223
240
224
- init ( name: String , data: Data ) {
241
+ public init ( name: String , data: Data ) {
225
242
self . name = name
226
243
self . _data = data
227
244
}
228
245
229
- func data( ) throws -> Data {
246
+ public func data( ) throws -> Data {
230
247
return _data
231
248
}
232
249
}
233
250
234
- /// A temporary folder which can write files to a temporary location on disk and
235
- /// will delete itself when its instance is released from memory.
236
- class TempFolder : File {
237
- let name : String
238
- let url : URL
239
-
240
- /// The files and sub folders that this folder contains.
241
- let content : [ File ]
242
-
243
- func write( to url: URL ) throws {
244
- try FileManager . default. createDirectory ( at: url, withIntermediateDirectories: false , attributes: nil )
245
- for file in content {
246
- try file. write ( inside: url)
247
- }
248
- }
249
-
250
- init ( content: [ File ] ) throws {
251
- self . content = content
252
-
253
- url = URL ( fileURLWithPath: NSTemporaryDirectory ( ) ) . appendingPathComponent ( UUID ( ) . uuidString)
254
- name = url. absoluteString
255
-
256
- try write ( to: url)
257
- }
258
-
259
- deinit {
260
- try ? FileManager . default. removeItem ( at: url)
251
+ extension XCTestCase {
252
+ /// Creates a ``Folder`` and writes its content to a temporary location on disk.
253
+ ///
254
+ /// - Parameters:
255
+ /// - content: The files and subfolders to write to a temporary location
256
+ /// - Returns: The temporary location where the temporary folder was written.
257
+ public func createTempFolder( content: [ File ] ) throws -> URL {
258
+ let temporaryDirectory = try createTemporaryDirectory ( ) . appendingPathComponent ( " TempDirectory- \( ProcessInfo . processInfo. globallyUniqueString) " )
259
+ let folder = Folder ( name: temporaryDirectory. lastPathComponent, content: content)
260
+ try folder. write ( to: temporaryDirectory)
261
+ return temporaryDirectory
261
262
}
262
263
}
0 commit comments