Skip to content

Instantly share code, notes, and snippets.

@swhitty
Last active October 7, 2022 21:08
Show Gist options
  • Save swhitty/9aa7f62bef03510874a32ec210ec4221 to your computer and use it in GitHub Desktop.
Save swhitty/9aa7f62bef03510874a32ec210ec4221 to your computer and use it in GitHub Desktop.
/// .print("🐡") Similar to the Combine variant, but for AsyncSequence.
///
/// ```
/// 🐡: makeAsyncIterator(): AsyncMapSequence<String>
/// 🐡: receive value: (1)
/// 🐡: receive value: (2)
/// 🐡: finished: AsyncMapSequence<String>
/// ```
///
public extension AsyncSequence {
/// Prints log messages for all iteration events.
/// - Parameters:
/// - prefix: A string — which defaults to empty, with which to prefix all log messages.
/// - stream: A stream for text output that receives messages. Defaults to console.
/// - Returns: A sequence that prints log messages for all iteration events.
func print(_ prefix: String = "", to stream: TextOutputStream? = nil) -> AsyncPrintSequence<Self> {
AsyncPrintSequence(upstream: self, prefix: prefix, stream: stream)
}
}
/// A sequence that prints log messages for all iteration events, optionally prefixed with a given string.
public struct AsyncPrintSequence<Upstream>: AsyncSequence where Upstream: AsyncSequence {
public typealias Element = Upstream.Element
private let upstream: Upstream
private let logger: Logger
/// Creates a sequence that prints log messages for all iteration events.
/// - Parameters:
/// - upstream: The sequence from which this seqence receives elements.
/// - prefix: A string with which to prefix all log messages.
/// - stream: A stream for text output that receives messages. Defaults to console.
public init(upstream: Upstream, prefix: String, stream: TextOutputStream? = nil) {
self.upstream = upstream
self.logger = Logger(prefix: prefix, stream: stream)
}
public func makeAsyncIterator() -> Iterator<Upstream.AsyncIterator> {
Iterator(upstream.makeAsyncIterator(), logger: logger)
}
public struct Iterator<Inner>: AsyncIteratorProtocol where Inner: AsyncIteratorProtocol, Element == Inner.Element {
private var it: Inner
private let logger: Logger
fileprivate init(_ it: Inner, logger: Logger) {
self.it = it
self.logger = logger
logger.logMakeIterator()
}
public mutating func next() async throws -> Inner.Element? {
do {
let element = try await it.next()
if let element = element {
logger.logValue(element)
} else {
logger.logFinished()
}
return element
} catch {
logger.logError(error)
throw error
}
}
}
}
private extension AsyncPrintSequence {
struct Logger {
let prefix: String
let stream: TextOutputStream?
func log(_ message: String) {
let message = prefix.isEmpty ? message : "\(prefix): \(message)"
if var stream = stream.map(AnyStream.init) {
Swift.print(message, terminator: "\n", to: &stream)
} else {
Swift.print(message)
}
}
private func makeIdentifier() -> String {
let elementName = String(describing: Upstream.Element.self)
let upstreamName = String(describing: Upstream.self).prefix { $0 != "<" }
return "\(upstreamName)<\(elementName)>"
}
func logMakeIterator() {
log("makeAsyncIterator(): \(makeIdentifier())")
}
func logValue(_ value: Element) {
log("receive value: (\(String(describing: value)))")
}
func logError(_ error: Error) {
log("finished error: (\(String(describing: error)))")
}
func logFinished() {
log("finished: \(makeIdentifier())")
}
private struct AnyStream: TextOutputStream {
var inner: TextOutputStream
mutating func write(_ string: String) {
inner.write(string)
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment