piper-app/Piper/Sources/Logic/UseCases/RunProcessUseCase.swift
Javier Cicchelli f090784973 [App] Run terminal processes (#3)
This PR contains the work done to run terminal processes within the context of the app, conforming to Swift concurrency.

Reviewed-on: #3
Co-authored-by: Javier Cicchelli <javier@rock-n-code.com>
Co-committed-by: Javier Cicchelli <javier@rock-n-code.com>
2024-10-05 08:34:34 +00:00

88 lines
2.1 KiB
Swift

//
// RunProcessUseCase.swift
// Piper ~ App
//
// Created by Javier Cicchelli on 05/10/2024.
// Copyright © 2024 Röck+Cöde. All rights reserved.
//
import Foundation
struct RunProcessUseCase {
// MARK: Type aliases
typealias Output = String
// MARK: Properties
private let path: String
private let arguments: [String]?
// MARK: Initialisers
init(
path: String,
arguments: [String] = []
) {
self.path = path
self.arguments = arguments
}
// MARK: Functions
func callAsFunction() async throws -> Output {
let process = Process()
process.executableURL = URL(fileURLWithPath: path)
process.arguments = arguments
let pipeError = Pipe()
let pipeOutput = Pipe()
process.standardError = pipeError
process.standardOutput = pipeOutput
async let streamOutput = pipeOutput.availableData.append()
async let streamError = pipeError.availableData.append()
do {
try process.run()
let dataOutput = await streamOutput
let dataError = await streamError
guard dataError.isEmpty else {
guard let errorOutput = String(data: dataError, encoding: .utf8) else {
throw RunProcessError.unexpected
}
throw RunProcessError.output(errorOutput)
}
guard let output = String(data: dataOutput, encoding: .utf8) else {
throw RunProcessError.unexpected
}
return await withCheckedContinuation { continuation in
process.terminationHandler = { _ in
continuation.resume(returning: output)
}
}
} catch let error as RunProcessError {
throw error
} catch {
throw RunProcessError.captured(error)
}
}
}
// MARK: - Errors
enum RunProcessError: Error {
case captured(_ error: Error)
case output(_ output: String)
case unexpected
}