Last active
April 21, 2022 17:51
-
-
Save bhargavg/26049391c5c6692e3ae4b1b80f66d24c to your computer and use it in GitHub Desktop.
An interactive process
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import Result | |
func launchProcess( | |
path: String, | |
args: [String], | |
options: LaunchProcessArgs | |
) -> Result<(stdOut: Data, stdErr: Data), ProcessError> { | |
let process = Process() | |
process.launchPath = path | |
process.arguments = args | |
let group = DispatchGroup() | |
let queue = DispatchQueue(label: "com.bhargavg.process.queue") | |
let stdInChannel: DispatchIO? | |
if options.contains(.attachStdIn) { | |
let channel = DispatchIO( | |
type: .stream, | |
fileDescriptor: STDIN_FILENO, | |
queue: queue, | |
cleanupHandler: { (fd) in } | |
) | |
channel.setLimit(lowWater: 1) | |
stdInChannel = channel | |
} else { | |
stdInChannel = nil | |
} | |
let execInPipe = Pipe() | |
process.standardInput = execInPipe.fileHandleForReading | |
let execInChannel = DispatchIO( | |
type: .stream, | |
fileDescriptor: execInPipe.fileHandleForWriting.fileDescriptor, | |
queue: queue, | |
cleanupHandler: { (fd) in } | |
) | |
execInChannel.setLimit(lowWater: 1) | |
stdInChannel?.read( | |
offset: 0, | |
length: .max, | |
queue: queue | |
) { (isDone, mayBeDispatchData, errorCode) in | |
let result: Result<DispatchData, ProcessError> = Result(mayBeDispatchData, failWith: .error(errorCode)) | |
switch result { | |
case let .success(data): | |
execInChannel.write( | |
offset: 0, | |
data: data, | |
queue: queue, | |
ioHandler: { _ in } | |
) | |
case .failure: | |
/// Ignoring stdin read errors | |
break | |
} | |
if isDone { | |
stdInChannel?.close() | |
} | |
} | |
let stdOutChannel: DispatchIO? | |
if options.contains(.attachStdOut) { | |
let channel = DispatchIO( | |
type: .stream, | |
fileDescriptor: STDOUT_FILENO, | |
queue: queue, | |
cleanupHandler: { (fd) in } | |
) | |
channel.setLimit(lowWater: 1) | |
stdOutChannel = channel | |
} else { | |
stdOutChannel = nil | |
} | |
var execOutData: DispatchData? = nil | |
let execOutPipe = Pipe() | |
process.standardOutput = execOutPipe.fileHandleForWriting | |
let execOutChannel = DispatchIO( | |
type: .stream, | |
fileDescriptor: execOutPipe.fileHandleForReading.fileDescriptor, | |
queue: queue, | |
cleanupHandler: { (fd) in } | |
) | |
execOutChannel.setLimit(lowWater: 1) | |
execOutChannel.setInterval(interval: .milliseconds(500), flags: .strictInterval) | |
execOutChannel.read( | |
offset: 0, | |
length: .max, | |
queue: queue | |
) { (isDone, mayBeDispatchData, errorCode) in | |
let result: Result<DispatchData, ProcessError> = Result(mayBeDispatchData, failWith: .error(errorCode)) | |
switch result { | |
case let .success(data): | |
if var execOutData = execOutData { | |
execOutData.append(data) | |
} else { | |
execOutData = data | |
} | |
stdOutChannel?.write( | |
offset: 0, | |
data: data, | |
queue: queue, | |
ioHandler: { _ in | |
/// Ignoring console write errors | |
} | |
) | |
case .failure: | |
/// Ignoring read errors, | |
/// hoping these will trigger process to fail, thereby | |
/// caught in termination handler | |
break; | |
} | |
if isDone { | |
execOutChannel.close() | |
} | |
} | |
let stdErrChannel: DispatchIO? | |
if options.contains(.attachStdErr) { | |
let channel = DispatchIO( | |
type: .stream, | |
fileDescriptor: STDOUT_FILENO, | |
queue: queue, | |
cleanupHandler: { (fd) in } | |
) | |
channel.setLimit(lowWater: 1) | |
stdErrChannel = channel | |
} else { | |
stdErrChannel = nil | |
} | |
var execErrData: DispatchData? = nil | |
let execErrPipe = Pipe() | |
process.standardError = execErrPipe.fileHandleForWriting | |
let execErrChannel = DispatchIO( | |
type: .stream, | |
fileDescriptor: execErrPipe.fileHandleForReading.fileDescriptor, | |
queue: queue, | |
cleanupHandler: { (fd) in } | |
) | |
execErrChannel.setLimit(lowWater: 1) | |
execErrChannel.read( | |
offset: 0, | |
length: .max, | |
queue: queue | |
) { (isDone, mayBeDispatchData, errorCode) in | |
let result: Result<DispatchData, ProcessError> = Result(mayBeDispatchData, failWith: .error(errorCode)) | |
switch result { | |
case let .success(data): | |
if var execErrData = execErrData { | |
execErrData.append(data) | |
} else { | |
execErrData = data | |
} | |
stdErrChannel?.write( | |
offset: 0, | |
data: data, | |
queue: queue, | |
ioHandler: { _ in | |
/// Ignoring console write errors | |
} | |
) | |
case .failure: | |
/// Ignoring read errors, | |
/// hoping these will trigger process to fail, thereby | |
/// caught in termination handler | |
break; | |
} | |
if isDone { | |
execErrChannel.close() | |
} | |
} | |
var result: Result<(stdOut: Data, stdErr: Data), ProcessError> = .failure(.error(-1)) | |
process.terminationHandler = { (process) in | |
if process.terminationStatus == EXIT_SUCCESS { | |
result = .success((stdOut: execOutData?.asData ?? Data(), stdErr: execErrData?.asData ?? Data())) | |
} else { | |
result = .failure(.error(process.terminationStatus)) | |
} | |
group.leave() | |
} | |
group.enter() | |
process.launch() | |
group.wait() | |
stdOutChannel?.close() | |
return result | |
} | |
extension DispatchData { | |
var asData: Data { | |
let bytes = UnsafeMutablePointer<UInt8>.allocate(capacity: count) | |
copyBytes(to: bytes, count: count) | |
let data = Data(bytes: bytes, count: count) | |
bytes.deinitialize(count: count) | |
bytes.deallocate(capacity: count) | |
return data | |
} | |
} | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
struct LaunchProcessArgs: OptionSet { | |
let rawValue: Int | |
static let attachStdIn = LaunchProcessArgs(rawValue: 1 << 0) | |
static let attachStdOut = LaunchProcessArgs(rawValue: 1 << 1) | |
static let attachStdErr = LaunchProcessArgs(rawValue: 1 << 2) | |
static let interactive: LaunchProcessArgs = [.attachStdIn, .attachStdOut, .attachStdErr] | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Result | |
let result = launchProcess( | |
path: "/usr/local/bin/npm", | |
args: ["init"], | |
options: .interactive | |
) | |
print(result) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// swift-tools-version:3.1 | |
import PackageDescription | |
let package = Package( | |
name: "testprocess", | |
dependencies: [ | |
.Package(url: "https://github.com/antitypical/Result", majorVersion: 3, minor: 2) | |
] | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment