Closed msanders closed 4 years ago
I'm inclined to think that this might be better implemented as more general SplitStream
that would also allow output to be written to multiple files at once. Something like:
public class SplitStream: WritableStream {
public let writeHandle: FileHandle
public let processObject: Any
public var encoding: String.Encoding = .utf8
private let queue = DispatchQueue(label: "com.jakeheis.SwiftCLI.SplitStream")
private let semaphore = DispatchSemaphore(value: 0)
public init(streams: [WritableStream]) {
let pipe = Pipe()
self.processObject = pipe
self.writeHandle = pipe.fileHandleForWriting
let readStream = ReadStream.for(fileHandle: pipe.fileHandleForReading)
queue.async { [weak self] in
while let data = readStream.readData() {
streams.forEach { $0.writeData(data) }
}
self?.semaphore.signal()
}
}
public convenience init(_ streams: WritableStream...) {
self.init(streams: streams)
}
}
// In use:
let capture = CaptureStream()
let result = Task(
executable: "/bin/ls",
arguments: ["."],
stdout: SplitStream(Term.stdout, capture)
).runSync()
Would this accomplish what you're talking about? I think capture
with the additional arguments you've added could be implemented with this sort of split stream
Thanks! That does make this simpler to implement, although unfortunately still requires duplicating an implementation of CaptureResult
and CaptureError
to take care of error handling. Would it be possible to make those initializers public instead, or is there something that would fit better with the existing API?
I think making the initializers public is probably the best call:
public struct CaptureResult {
public init(stdout: CaptureStream, stderr: CaptureStream) { ... }
}
public struct CaptureError: ProcessError {
public init(exitStatus: Int32, captured: CaptureResult) { ... }
}
// In use
let stdoutCap = CaptureStream()
let stderrCap = CaptureStream()
let result = Task(
executable: "/bin/ls",
arguments: ["."],
stdout: SplitStream(Term.stdout, stdoutCap),
stderr: stderrCap
).runSync()
// Build CaptureResult and CaptureError
Would something like that work for you?
Thanks, that does solve my use-case. Updated PR with proposed changes.
Left a few comments about conforming SplitStream
to ProcessingStream
, otherwise looks good!
Thanks, done.
Looks great, thank you for the contribution!
6.0.2
This allows printing or redirecting output as it's being captured, analogous to the
tee
command. For example:Can be written as:
Currently to do this without a fork requires re-implementing
Task.capture
,CaptureStream
,CaptureResult
, andCaptureError
.I've also added support for passing in
forwardInterrupt
andenv
properties as arguments, can open a separate PR for that if necessary.For
CaptureStream
, I used a block instead of anotherWritableStream
parameter as that was more consistent with the surrounding code and similar to thereadabilityHandler
block onFileHandle
.