Skip to content

Commit 93784c5

Browse files
committed
Add async overloads of withTemporaryFile and withTemporaryDirectory
1 parent 52a962f commit 93784c5

File tree

1 file changed

+137
-0
lines changed

1 file changed

+137
-0
lines changed

Diff for: Sources/TSCBasic/TemporaryFile.swift

+137
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,39 @@ public func withTemporaryFile<Result>(
141141
}
142142
}
143143

144+
/// Creates a temporary file and evaluates a closure with the temporary file as an argument.
145+
/// The temporary file will live on disk while the closure is evaluated and will be deleted when
146+
/// the cleanup block is called.
147+
///
148+
/// This function is basically a wrapper over posix's mkstemps() function to create disposable files.
149+
///
150+
/// - Parameters:
151+
/// - dir: If specified the temporary file will be created in this directory otherwise environment variables
152+
/// TMPDIR, TEMP and TMP will be checked for a value (in that order). If none of the env variables are
153+
/// set, dir will be set to `/tmp/`.
154+
/// - prefix: The prefix to the temporary file name.
155+
/// - suffix: The suffix to the temporary file name.
156+
/// - body: A closure to execute that receives the TemporaryFile as an argument.
157+
/// If `body` has a return value, that value is also used as the
158+
/// return value for the `withTemporaryFile` function.
159+
/// The cleanup block should be called when the temporary file is no longer needed.
160+
///
161+
/// - Throws: TempFileError and rethrows all errors from `body`.
162+
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
163+
public func withTemporaryFile<Result>(
164+
dir: AbsolutePath? = nil, prefix: String = "TemporaryFile", suffix: String = "", _ body: (TemporaryFile, @escaping (TemporaryFile) async -> Void) async throws -> Result
165+
) async throws -> Result {
166+
return try await body(TemporaryFile(dir: dir, prefix: prefix, suffix: suffix)) { tempFile in
167+
#if os(Windows)
168+
_ = tempFile.path.pathString.withCString(encodedAs: UTF16.self) {
169+
_wunlink($0)
170+
}
171+
#else
172+
unlink(tempFile.path.pathString)
173+
#endif
174+
}
175+
}
176+
144177
/// Creates a temporary file and evaluates a closure with the temporary file as an argument.
145178
/// The temporary file will live on disk while the closure is evaluated and will be deleted afterwards.
146179
///
@@ -167,6 +200,40 @@ public func withTemporaryFile<Result>(
167200
}
168201
}
169202

203+
/// Creates a temporary file and evaluates a closure with the temporary file as an argument.
204+
/// The temporary file will live on disk while the closure is evaluated and will be deleted afterwards.
205+
///
206+
/// This function is basically a wrapper over posix's mkstemps() function to create disposable files.
207+
///
208+
/// - Parameters:
209+
/// - dir: If specified the temporary file will be created in this directory otherwise environment variables
210+
/// TMPDIR, TEMP and TMP will be checked for a value (in that order). If none of the env variables are
211+
/// set, dir will be set to `/tmp/`.
212+
/// - prefix: The prefix to the temporary file name.
213+
/// - suffix: The suffix to the temporary file name.
214+
/// - deleteOnClose: Whether the file should get deleted after the call of `body`
215+
/// - body: A closure to execute that receives the TemporaryFile as an argument.
216+
/// If `body` has a return value, that value is also used as the
217+
/// return value for the `withTemporaryFile` function.
218+
///
219+
/// - Throws: TempFileError and rethrows all errors from `body`.
220+
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
221+
public func withTemporaryFile<Result>(
222+
dir: AbsolutePath? = nil, prefix: String = "TemporaryFile", suffix: String = "", deleteOnClose: Bool = true, _ body: (TemporaryFile) async throws -> Result
223+
) async throws -> Result {
224+
try await withTemporaryFile(dir: dir, prefix: prefix, suffix: suffix) { tempFile, cleanup in
225+
let result: Result
226+
do {
227+
result = try await body(tempFile)
228+
if (deleteOnClose) { await cleanup(tempFile) }
229+
} catch {
230+
if (deleteOnClose) { await cleanup(tempFile) }
231+
throw error
232+
}
233+
return result
234+
}
235+
}
236+
170237
// FIXME: This isn't right place to declare this, probably POSIX or merge with FileSystemError?
171238
//
172239
/// Contains the error which can be thrown while creating a directory using POSIX's mkdir.
@@ -252,6 +319,44 @@ public func withTemporaryDirectory<Result>(
252319
}
253320
}
254321

322+
/// Creates a temporary directory and evaluates a closure with the directory path as an argument.
323+
/// The temporary directory will live on disk while the closure is evaluated and will be deleted when
324+
/// the cleanup closure is called. This allows the temporary directory to have an arbitrary lifetime.
325+
///
326+
/// This function is basically a wrapper over posix's mkdtemp() function.
327+
///
328+
/// - Parameters:
329+
/// - dir: If specified the temporary directory will be created in this directory otherwise environment
330+
/// variables TMPDIR, TEMP and TMP will be checked for a value (in that order). If none of the env
331+
/// variables are set, dir will be set to `/tmp/`.
332+
/// - prefix: The prefix to the temporary file name.
333+
/// - body: A closure to execute that receives the absolute path of the directory as an argument.
334+
/// If `body` has a return value, that value is also used as the
335+
/// return value for the `withTemporaryDirectory` function.
336+
/// The cleanup block should be called when the temporary directory is no longer needed.
337+
///
338+
/// - Throws: MakeDirectoryError and rethrows all errors from `body`.
339+
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
340+
public func withTemporaryDirectory<Result>(
341+
dir: AbsolutePath? = nil, prefix: String = "TemporaryDirectory" , _ body: (AbsolutePath, @escaping (AbsolutePath) async -> Void) async throws -> Result
342+
) async throws -> Result {
343+
// Construct path to the temporary directory.
344+
let templatePath = try AbsolutePath(validating: prefix + ".XXXXXX", relativeTo: determineTempDirectory(dir))
345+
346+
// Convert templatePath to a C style string terminating with null char to be an valid input
347+
// to mkdtemp method. The XXXXXX in this string will be replaced by a random string
348+
// which will be the actual path to the temporary directory.
349+
var template = [UInt8](templatePath.pathString.utf8).map({ Int8($0) }) + [Int8(0)]
350+
351+
if TSCLibc.mkdtemp(&template) == nil {
352+
throw MakeDirectoryError(errno: errno)
353+
}
354+
355+
return try await body(AbsolutePath(validating: String(cString: template))) { path in
356+
_ = try? FileManager.default.removeItem(atPath: path.pathString)
357+
}
358+
}
359+
255360
/// Creates a temporary directory and evaluates a closure with the directory path as an argument.
256361
/// The temporary directory will live on disk while the closure is evaluated and will be deleted afterwards.
257362
///
@@ -277,3 +382,35 @@ public func withTemporaryDirectory<Result>(
277382
}
278383
}
279384

385+
/// Creates a temporary directory and evaluates a closure with the directory path as an argument.
386+
/// The temporary directory will live on disk while the closure is evaluated and will be deleted afterwards.
387+
///
388+
/// This function is basically a wrapper over posix's mkdtemp() function.
389+
///
390+
/// - Parameters:
391+
/// - dir: If specified the temporary directory will be created in this directory otherwise environment
392+
/// variables TMPDIR, TEMP and TMP will be checked for a value (in that order). If none of the env
393+
/// variables are set, dir will be set to `/tmp/`.
394+
/// - prefix: The prefix to the temporary file name.
395+
/// - removeTreeOnDeinit: If enabled try to delete the whole directory tree otherwise remove only if its empty.
396+
/// - body: A closure to execute that receives the absolute path of the directory as an argument.
397+
/// If `body` has a return value, that value is also used as the
398+
/// return value for the `withTemporaryDirectory` function.
399+
///
400+
/// - Throws: MakeDirectoryError and rethrows all errors from `body`.
401+
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
402+
public func withTemporaryDirectory<Result>(
403+
dir: AbsolutePath? = nil, prefix: String = "TemporaryDirectory", removeTreeOnDeinit: Bool = false , _ body: (AbsolutePath) async throws -> Result
404+
) async throws -> Result {
405+
try await withTemporaryDirectory(dir: dir, prefix: prefix) { path, cleanup in
406+
let result: Result
407+
do {
408+
result = try await body(path)
409+
if removeTreeOnDeinit { await cleanup(path) }
410+
} catch {
411+
if removeTreeOnDeinit { await cleanup(path) }
412+
throw error
413+
}
414+
return result
415+
}
416+
}

0 commit comments

Comments
 (0)