diff --git a/XCTool/REPLBuffer.swift b/XCTool/REPLBuffer.swift index a09a231..a8d1fa8 100644 --- a/XCTool/REPLBuffer.swift +++ b/XCTool/REPLBuffer.swift @@ -11,9 +11,9 @@ struct REPLBuffer { private var buffer = Data() private let delimData = String("\n").data(using: .utf8)! - mutating func append(_ data: Data) -> String? { + mutating func append(_ data: Data) -> [String]? { buffer.append(data) - return getLine() + return getLines() } mutating func outstandingText() -> [String]? { @@ -21,13 +21,10 @@ struct REPLBuffer { buffer.removeAll() buffer.count = 0 } - guard buffer.count > 0 else { - return nil - } - var result = [String]() - while let line = getLine() { - result.append(line) + // Get all complete lines from buffer + guard var result = getLines() else { + return nil } // Get remaining buffer text(if any) @@ -39,6 +36,18 @@ struct REPLBuffer { return result } + private mutating func getLines() -> [String]? { + guard buffer.count > 0 else { + return nil + } + + var result = [String]() + while let line = getLine() { + result.append(line) + } + return result.isEmpty ? nil : result + } + private mutating func getLine() -> String? { guard let range = buffer.range(of: delimData) else { return nil } diff --git a/XCTool/REPLCommand.swift b/XCTool/REPLCommand.swift index fbed01d..0b4fa5f 100644 --- a/XCTool/REPLCommand.swift +++ b/XCTool/REPLCommand.swift @@ -12,9 +12,9 @@ struct REPLCommand { let arguments: [String] func run() -> (result: Data?, exitStatus: Int32) { - let (task, pipe) = launchProcess(at: launchPath, with: arguments) + let (task, stdOutPipe, _) = launchProcess(at: launchPath, with: arguments) let outdata = autoreleasepool { () -> Data? in - pipe.fileHandleForReading.readDataToEndOfFile() + stdOutPipe.fileHandleForReading.readDataToEndOfFile() } task.waitUntilExit() let status = task.terminationStatus @@ -24,10 +24,12 @@ struct REPLCommand { @discardableResult func run(readabilityHandler: @escaping (([String]?) -> Void)) -> Int32 { - let (task, pipe) = launchProcess(at: launchPath, with: arguments) + let (task, stdOutPipe, stdErrPipe) = launchProcess(at: launchPath, with: arguments) - let outHandle = pipe.fileHandleForReading - var buffer = REPLBuffer() + let outHandle = stdOutPipe.fileHandleForReading + let errHandle = stdErrPipe.fileHandleForReading + var outBuffer = REPLBuffer() + var errBuffer = REPLBuffer() let group = DispatchGroup() group.enter() @@ -35,11 +37,24 @@ struct REPLCommand { let availableOutData = fileHandle.availableData if availableOutData.isEmpty { //EOF reached - readabilityHandler(buffer.outstandingText()) + readabilityHandler(outBuffer.outstandingText()) outHandle.readabilityHandler = nil group.leave() - } else if let line = buffer.append(availableOutData) { - readabilityHandler([line]) + } else if let lines = outBuffer.append(availableOutData) { + readabilityHandler(lines) + } + } + + group.enter() + errHandle.readabilityHandler = { fileHandle in + let availableOutData = fileHandle.availableData + if availableOutData.isEmpty { + //EOF reached + readabilityHandler(errBuffer.outstandingText()) + errHandle.readabilityHandler = nil + group.leave() + } else if let lines = errBuffer.append(availableOutData) { + readabilityHandler(lines) } } @@ -52,16 +67,20 @@ struct REPLCommand { return status } - private func launchProcess(at launchPath: String, with arguments: [String]) -> (task: Process, pipe: Pipe) { - return autoreleasepool { () -> (Process, Pipe) in + private func launchProcess(at launchPath: String, with arguments: [String]) -> (task: Process, stdOutPipe: Pipe, stdErrPipe: Pipe) { + return autoreleasepool { () -> (Process, Pipe, Pipe) in let task = Process() task.executableURL = URL(fileURLWithPath: launchPath) task.arguments = arguments - let pipe = Pipe() - task.standardOutput = pipe + let stdOutPipe = Pipe() + task.standardOutput = stdOutPipe + + let stdErrPipe = Pipe() + task.standardError = stdErrPipe + try? task.run() - return (task, pipe) + return (task, stdOutPipe, stdErrPipe) } } }