Skip to content

Instantly share code, notes, and snippets.

@maximkrouk
Last active May 30, 2020 14:32
Show Gist options
  • Save maximkrouk/24a114058187c0cc94a54929f4ba6b07 to your computer and use it in GitHub Desktop.
Save maximkrouk/24a114058187c0cc94a54929f4ba6b07 to your computer and use it in GitHub Desktop.
Class for stdout redirection
import Foundation
import Combine
class REPL {
private static let shared = REPL()
public static var publisher: AnyPublisher<String, Never> {
shared.publisher.eraseToAnyPublisher()
}
public typealias Publisher = PassthroughSubject<String, Never>
public let publisher = Publisher()
private let stdOut = Pipe()
private var buffer = Buffer()
private init() { setup() }
public func setup() {
printWarning()
setvbuf(stdout, nil, _IONBF, 0)
dup2(stdOut.fileHandleForWriting.fileDescriptor, STDOUT_FILENO)
stdOut.fileHandleForReading.readabilityHandler = { [weak self] handle in
guard
let self = self,
let output = self.buffer.append(handle.availableData)
else { return }
self.publisher.send(output)
}
}
private func printWarning(
file: String = #file,
line: UInt = #line,
function: String = #function
) {
print("⚠️ WARNING: STDOUT WILL BE OVERRIDEN BY REPL INSTANCE")
print("file:", file)
print("line:", line)
print("function:", function)
print("✅ It's fine if you expect it to happen.")
print("Output will be redirected to \(stdOut)")
print("You can read it from this fileHandle:", stdOut.fileHandleForReading)
}
}
extension REPL {
private struct Buffer {
private var storage = Data()
mutating func append(_ data: Data) -> String? {
storage.append(data)
if let string = String(data: storage, encoding: .utf8),
string.last?.isNewline == true
{
buffer.removeAll()
return string
}
return nil
}
}
}
@maximkrouk
Copy link
Author

maximkrouk commented Mar 15, 2020

Usage

REPL.publisher.sink { output in 
    // Process value
    someLogger.log(output)
}.store(in: someCancellablesSet)

How does it work

REPL

REPL creates a new pipe for handling console output

private let stdOut = Pipe()

Then sets this pipe as a new stdout

setvbuf(stdout, nil, _IONBF, 0) // make stdout unbuffered
dup2(stdOut.fileHandleForWriting.fileDescriptor, STDOUT_FILENO) // override stdout file descriptor
dup2(stdOut.fileHandleForWriting.fileDescriptor, STDERR_FILENO)  // override stderr file descriptor

Handle output with buffer

self.buffer.append(handle.availableData)

Publish result if present

self.publisher.send(output)

REPL.Buffer

  • Uses private storage: Data to be a buffer
  • Appends data to private storage
  • If the last symbol in data passed to append method was a newline then it returns storage contents and clears it.

Sources:


See also:

Back to index

@maximkrouk
Copy link
Author

maximkrouk commented Mar 30, 2020

Removed stderr redirection due to fatal errors, bad access and other system errors, which is inconvenient for the case, when app crashes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment