mirror of
https://github.com/nicoverbruggen/phpmon.git
synced 2025-11-07 21:20:07 +01:00
🏗 WIP: Much improved Shell protocol
This commit is contained in:
@@ -11,34 +11,55 @@ import Foundation
|
||||
struct ShellOutput: CustomStringConvertible {
|
||||
var output: String
|
||||
var isError: Bool
|
||||
|
||||
var description: String {
|
||||
return output
|
||||
}
|
||||
|
||||
static func out(_ output: String) -> ShellOutput {
|
||||
return ShellOutput(output: output, isError: false)
|
||||
}
|
||||
|
||||
static func err(_ output: String) -> ShellOutput {
|
||||
return ShellOutput(output: output, isError: true)
|
||||
}
|
||||
}
|
||||
|
||||
protocol Shellable {
|
||||
/**
|
||||
Run a command synchronously. Waits until the command is done.
|
||||
Returns the most relevant output (prefers error output if it exists).
|
||||
*/
|
||||
func sync(_ command: String) -> ShellOutput
|
||||
|
||||
/**
|
||||
Run a command asynchronously.
|
||||
Returns the most relevant output (prefers error output if it exists).
|
||||
*/
|
||||
func pipe(_ command: String) async -> ShellOutput
|
||||
|
||||
/**
|
||||
Run a command asynchronously, without returning the output of the command.
|
||||
Returns the most relevant output (prefers error output if it exists).
|
||||
*/
|
||||
func quiet(_ command: String) async
|
||||
|
||||
/**
|
||||
Attach to a given command and listen for progress updates.
|
||||
Any data that ends up in standard out or standard error becomes available.
|
||||
Runs a command asynchronously, and fires closure with `stdout` or `stderr` data as it comes in.
|
||||
|
||||
You can specify how long this task should run.
|
||||
The process will always be terminated after the specified time interval.
|
||||
(Whether it is complete or not.)
|
||||
|
||||
Unlike `sync`, `pipe` and `quiet`, you can capture both `stdout` and `stderr` with this mechanism.
|
||||
The end result is still the most relevant output (where error output is preferred if it exists).
|
||||
*/
|
||||
func attach(
|
||||
_ command: String,
|
||||
didReceiveOutput: @escaping (ShellOutput) -> Void
|
||||
) async -> ShellOutput
|
||||
didReceiveOutput: @escaping (ShellOutput) -> Void,
|
||||
withTimeout timeout: TimeInterval
|
||||
) async throws -> ShellOutput
|
||||
}
|
||||
|
||||
enum ShellError: Error {
|
||||
case timedOut
|
||||
}
|
||||
|
||||
@@ -90,8 +90,8 @@ class SystemShell: Shellable {
|
||||
|
||||
task.standardOutput = outputPipe
|
||||
task.standardError = errorPipe
|
||||
task.waitUntilExit()
|
||||
task.launch()
|
||||
task.waitUntilExit()
|
||||
|
||||
let stdOut = String(
|
||||
data: outputPipe.fileHandleForReading.readDataToEndOfFile(),
|
||||
@@ -118,7 +118,45 @@ class SystemShell: Shellable {
|
||||
_ = await self.pipe(command)
|
||||
}
|
||||
|
||||
func attach(_ command: String, didReceiveOutput: @escaping (ShellOutput) -> Void) async -> ShellOutput {
|
||||
return sync(command)
|
||||
func attach(
|
||||
_ command: String,
|
||||
didReceiveOutput: @escaping (ShellOutput) -> Void,
|
||||
withTimeout timeout: TimeInterval = 5.0
|
||||
) async throws -> ShellOutput {
|
||||
let task = getShellProcess(for: command)
|
||||
|
||||
var allOut: String = ""
|
||||
var allErr: String = ""
|
||||
|
||||
task.listen { stdOut in
|
||||
allOut += stdOut; didReceiveOutput(.out(stdOut))
|
||||
} didReceiveStandardErrorData: { stdErr in
|
||||
allErr += stdErr; didReceiveOutput(.err(stdErr))
|
||||
}
|
||||
|
||||
return try await withCheckedThrowingContinuation({ continuation in
|
||||
var timer: Timer?
|
||||
|
||||
task.terminationHandler = { process in
|
||||
process.haltListening()
|
||||
|
||||
timer?.invalidate()
|
||||
|
||||
if !allErr.isEmpty {
|
||||
return continuation.resume(returning: .err(allErr))
|
||||
}
|
||||
|
||||
return continuation.resume(returning: .out(allOut))
|
||||
}
|
||||
|
||||
timer = Timer.scheduledTimer(withTimeInterval: timeout, repeats: false) { _ in
|
||||
task.terminationHandler = nil
|
||||
task.terminate()
|
||||
return continuation.resume(throwing: ShellError.timedOut)
|
||||
}
|
||||
|
||||
task.launch()
|
||||
task.waitUntilExit()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
import Foundation
|
||||
|
||||
public class TestableShell: Shellable {
|
||||
|
||||
public typealias Input = String
|
||||
|
||||
init(expectations: [Input: OutputsToShell]) {
|
||||
@@ -26,7 +25,11 @@ public class TestableShell: Shellable {
|
||||
self.sync(command)
|
||||
}
|
||||
|
||||
func attach(_ command: String, didReceiveOutput: @escaping (ShellOutput) -> Void) async -> ShellOutput {
|
||||
func attach(
|
||||
_ command: String,
|
||||
didReceiveOutput: @escaping (ShellOutput) -> Void,
|
||||
withTimeout timeout: TimeInterval
|
||||
) async throws -> ShellOutput {
|
||||
self.sync(command)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user