Skip to content

Commit 5a8dcbf

Browse files
authored
Add Recorable Output (#11)
This commit adds a the `Output.Recorder` type, which allows recording output to be written later to a `TextOutputStream`. `Recorder` also includes API to make it easy to separate standard error and standard output streams.
1 parent 84fccd3 commit 5a8dcbf

3 files changed

Lines changed: 74 additions & 2 deletions

File tree

Sources/Shwift/Builtins.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,10 +131,10 @@ extension Builtin {
131131
Lines(byteBuffers: byteBuffers)
132132
}
133133

134-
fileprivate typealias ByteBuffers = AsyncCompactMapSequence<
134+
typealias ByteBuffers = AsyncCompactMapSequence<
135135
AsyncPrefixWhileSequence<AsyncInboundHandler<ByteBuffer>>, ByteBuffer
136136
>
137-
fileprivate let byteBuffers: ByteBuffers
137+
let byteBuffers: ByteBuffers
138138
}
139139

140140
/**

Sources/Shwift/IO.swift

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,43 @@ public struct Output {
6363
*/
6464
public static let fatalDevice = Output(kind: .fatalDevice)
6565

66+
public static func record(to recording: Recorder.Recording) -> Output {
67+
Output(kind: .recording(recording))
68+
}
69+
70+
public actor Recorder {
71+
72+
public func write<T: TextOutputStream>(to stream: inout T) async {
73+
for (_, buffer) in strings {
74+
buffer.write(to: &stream)
75+
}
76+
}
77+
78+
public struct Recording {
79+
public func write<T: TextOutputStream>(to stream: inout T) async {
80+
for (source, buffer) in await recorder.strings {
81+
if source == self.source {
82+
buffer.write(to: &stream)
83+
}
84+
}
85+
}
86+
87+
fileprivate let recorder: Recorder
88+
fileprivate let source: Source
89+
}
90+
public var output: Recording { Recording(recorder: self, source: .output) }
91+
public var error: Recording { Recording(recorder: self, source: .error) }
92+
93+
public func record(_ string: String, from source: Source) {
94+
strings.append((source, string))
95+
}
96+
97+
public enum Source {
98+
case output, error
99+
}
100+
fileprivate var strings: [(Source, String)] = []
101+
}
102+
66103
/**
67104
An output backed by an unmanaged file descriptor. It is the caller's responsibility to ensure that this file descriptor remains valid for as long as this input is in use.
68105
*/
@@ -83,6 +120,19 @@ public struct Output {
83120
return try await context.withNullOutputDevice(operation)
84121
case .fatalDevice:
85122
return try await context.withFatalOutputDevice(operation)
123+
case .recording(let recording):
124+
return try await Builtin.pipe(
125+
operation,
126+
to: { input in
127+
try await context.withNullOutputDevice { output in
128+
try await Builtin.withChannel(input: input, output: output, in: context) { channel in
129+
for try await buffer in channel.input.byteBuffers {
130+
await recording.recorder.record(String(buffer: buffer), from: recording.source)
131+
}
132+
}
133+
}
134+
}
135+
).source
86136
case .unmanaged(let fileDescriptor):
87137
return try await operation(fileDescriptor)
88138
}
@@ -94,6 +144,7 @@ public struct Output {
94144
case nullDevice
95145
case fatalDevice
96146
case unmanaged(FileDescriptor)
147+
case recording(Recorder.Recording)
97148
}
98149
fileprivate let kind: Kind
99150
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import XCTest
2+
@testable import Shwift
3+
4+
final class RecorderTests: XCTestCase {
5+
6+
func testRecorder() async throws {
7+
let recorder = Shwift.Output.Recorder()
8+
await recorder.record("Output\n", from: .output)
9+
await recorder.record("Error\n", from: .error)
10+
var output = ""
11+
await recorder.output.write(to: &output)
12+
var error = ""
13+
await recorder.error.write(to: &error)
14+
var joined = ""
15+
await recorder.write(to: &joined)
16+
XCTAssertEqual("Output\n", output)
17+
XCTAssertEqual("Error\n", error)
18+
XCTAssertEqual("Output\nError\n", joined)
19+
}
20+
21+
}

0 commit comments

Comments
 (0)