Skip to content

Commit 0712fdb

Browse files
committed
Introduce custom initializers to initialize String, Data, and Array from AsyncBufferSequence.Buffer
1 parent e62036c commit 0712fdb

File tree

5 files changed

+99
-55
lines changed

5 files changed

+99
-55
lines changed

Sources/Subprocess/API.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ public func run<Result, Input: InputProtocol, Error: OutputProtocol>(
168168
workingDirectory: FilePath? = nil,
169169
platformOptions: PlatformOptions = PlatformOptions(),
170170
input: Input = .none,
171-
error: Error,
171+
error: Error = .discarded,
172172
isolation: isolated (any Actor)? = #isolation,
173173
body: ((Execution, AsyncBufferSequence) async throws -> Result)
174174
) async throws -> ExecutionResult<Result> where Error.OutputType == Void {
@@ -267,7 +267,7 @@ public func run<Result, Error: OutputProtocol>(
267267
environment: Environment = .inherit,
268268
workingDirectory: FilePath? = nil,
269269
platformOptions: PlatformOptions = PlatformOptions(),
270-
error: Error,
270+
error: Error = .discarded,
271271
isolation: isolated (any Actor)? = #isolation,
272272
body: ((Execution, StandardInputWriter, AsyncBufferSequence) async throws -> Result)
273273
) async throws -> ExecutionResult<Result> where Error.OutputType == Void {

Sources/Subprocess/Buffer.swift

Lines changed: 79 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@ extension AsyncBufferSequence {
1818
/// A immutable collection of bytes
1919
public struct Buffer: Sendable {
2020
#if os(Windows)
21-
private var data: [UInt8]
21+
internal var data: [UInt8]
2222

2323
internal init(data: [UInt8]) {
2424
self.data = data
2525
}
2626
#else
27-
private var data: DispatchData
27+
internal var data: DispatchData
2828

2929
internal init(data: DispatchData) {
3030
self.data = data
@@ -54,7 +54,6 @@ extension AsyncBufferSequence.Buffer {
5454
@available(SubprocessSpan, *)
5555
#endif
5656
extension AsyncBufferSequence.Buffer {
57-
#if !SubprocessSpan
5857
/// Access the raw bytes stored in this buffer
5958
/// - Parameter body: A closure with an `UnsafeRawBufferPointer` parameter that
6059
/// points to the contiguous storage for the type. If no such storage exists,
@@ -64,13 +63,6 @@ extension AsyncBufferSequence.Buffer {
6463
/// - Returns: The return value, if any, of the body closure parameter.
6564
public func withUnsafeBytes<ResultType>(
6665
_ body: (UnsafeRawBufferPointer) throws -> ResultType
67-
) rethrows -> ResultType {
68-
return try self._withUnsafeBytes(body)
69-
}
70-
#endif // !SubprocessSpan
71-
72-
internal func _withUnsafeBytes<ResultType>(
73-
_ body: (UnsafeRawBufferPointer) throws -> ResultType
7466
) rethrows -> ResultType {
7567
#if os(Windows)
7668
return try self.data.withUnsafeBytes(body)
@@ -88,44 +80,46 @@ extension AsyncBufferSequence.Buffer {
8880
#if SubprocessSpan
8981
// Access the storge backing this Buffer
9082
public var bytes: RawSpan {
91-
var backing: SpanBacking?
92-
#if os(Windows)
93-
self.data.withUnsafeBufferPointer {
94-
backing = .pointer($0)
95-
}
96-
#else
97-
self.data.enumerateBytes { buffer, byteIndex, stop in
98-
if _fastPath(backing == nil) {
99-
// In practice, almost all `DispatchData` is contiguous
100-
backing = .pointer(buffer)
101-
} else {
102-
// This DispatchData is not contiguous. We need to copy
103-
// the bytes out
104-
let contents = Array(buffer)
105-
switch backing! {
106-
case .pointer(let ptr):
107-
// Convert the ptr to array
108-
let existing = Array(ptr)
109-
backing = .array(existing + contents)
110-
case .array(let array):
111-
backing = .array(array + contents)
83+
@lifetime(borrow self)
84+
borrowing get {
85+
var backing: SpanBacking?
86+
#if os(Windows)
87+
self.data.withUnsafeBufferPointer {
88+
backing = .pointer($0)
89+
}
90+
#else
91+
self.data.enumerateBytes { buffer, byteIndex, stop in
92+
if _fastPath(backing == nil) {
93+
// In practice, almost all `DispatchData` is contiguous
94+
backing = .pointer(buffer)
95+
} else {
96+
// This DispatchData is not contiguous. We need to copy
97+
// the bytes out
98+
let contents = Array(buffer)
99+
switch backing! {
100+
case .pointer(let ptr):
101+
// Convert the ptr to array
102+
let existing = Array(ptr)
103+
backing = .array(existing + contents)
104+
case .array(let array):
105+
backing = .array(array + contents)
106+
}
112107
}
113108
}
114-
}
115-
#endif
116-
guard let backing = backing else {
117-
let empty = UnsafeRawBufferPointer(start: nil, count: 0)
118-
let span = RawSpan(_unsafeBytes: empty)
119-
return _overrideLifetime(of: span, to: self)
120-
}
121-
switch backing {
122-
case .pointer(let ptr):
123-
let span = RawSpan(_unsafeElements: ptr)
124-
return _overrideLifetime(of: span, to: self)
125-
case .array(let array):
126-
let ptr = array.withUnsafeBytes { $0 }
127-
let span = RawSpan(_unsafeBytes: ptr)
128-
return _overrideLifetime(of: span, to: self)
109+
#endif
110+
guard let backing = backing else {
111+
let empty = UnsafeRawBufferPointer(start: nil, count: 0)
112+
let span = RawSpan(_unsafeBytes: empty)
113+
return _overrideLifetime(of: span, to: self)
114+
}
115+
switch backing {
116+
case .pointer(let ptr):
117+
let span = RawSpan(_unsafeElements: ptr)
118+
return _overrideLifetime(of: span, to: self)
119+
case .array(let array):
120+
let span = array.bytes
121+
return _overrideLifetime(of: span, to: self)
122+
}
129123
}
130124
}
131125
#endif // SubprocessSpan
@@ -159,3 +153,42 @@ extension AsyncBufferSequence.Buffer: Equatable, Hashable {
159153
}
160154
#endif
161155
}
156+
157+
// MARK: - Initializers
158+
#if SubprocessSpan
159+
@available(SubprocessSpan, *)
160+
#endif
161+
extension String {
162+
/// Create a String with the given encoding from `Buffer`.
163+
/// - Parameters:
164+
/// - buffer: the buffer to copy from
165+
/// - encoding: the encoding to encode Self with
166+
public init?<Encoding: _UnicodeEncoding>(buffer: AsyncBufferSequence.Buffer, as encoding: Encoding.Type) {
167+
#if os(Windows)
168+
let source = buffer.data.map { Encoding.CodeUnit($0) }
169+
self = String(decoding: source, as: encoding)
170+
#else
171+
self = buffer.withUnsafeBytes { ptr in
172+
return String(
173+
decoding: ptr.bindMemory(to: Encoding.CodeUnit.self).lazy.map { $0 },
174+
as: encoding
175+
)
176+
}
177+
#endif
178+
}
179+
}
180+
181+
#if SubprocessSpan
182+
@available(SubprocessSpan, *)
183+
#endif
184+
extension Array where Element == UInt8 {
185+
/// Create an Array from `Buffer`
186+
/// - Parameter buffer: the buffer to copy from
187+
public init(buffer: AsyncBufferSequence.Buffer) {
188+
#if os(Windows)
189+
self = buffer.data
190+
#else
191+
self = Array(buffer.data)
192+
#endif
193+
}
194+
}

Sources/Subprocess/SubprocessFoundation/Output+Foundation.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,17 @@ extension OutputProtocol where Self == DataOutput {
6161
}
6262
}
6363

64+
#if SubprocessSpan
65+
@available(SubprocessSpan, *)
66+
#endif
67+
extension Data {
68+
/// Create a `Data` from `Buffer`
69+
/// - Parameter buffer: buffer to copy from
70+
public init(buffer: AsyncBufferSequence.Buffer) {
71+
self = Data(buffer.data)
72+
}
73+
}
74+
6475
// MARK: - Workarounds
6576
#if SubprocessSpan
6677
@available(SubprocessSpan, *)

Tests/SubprocessTests/SubprocessTests+Unix.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,7 @@ extension SubprocessUnixTests {
433433
) { execution, standardOutput in
434434
var buffer = Data()
435435
for try await chunk in standardOutput {
436-
let currentChunk = chunk._withUnsafeBytes { Data($0) }
436+
let currentChunk = chunk.withUnsafeBytes { Data($0) }
437437
buffer += currentChunk
438438
}
439439
return buffer
@@ -472,7 +472,7 @@ extension SubprocessUnixTests {
472472
) { execution, standardOutput in
473473
var buffer = Data()
474474
for try await chunk in standardOutput {
475-
let currentChunk = chunk._withUnsafeBytes { Data($0) }
475+
let currentChunk = chunk.withUnsafeBytes { Data($0) }
476476
buffer += currentChunk
477477
}
478478
return buffer
@@ -620,7 +620,7 @@ extension SubprocessUnixTests {
620620
) { execution, standardOutput in
621621
var buffer = Data()
622622
for try await chunk in standardOutput {
623-
let currentChunk = chunk._withUnsafeBytes { Data($0) }
623+
let currentChunk = chunk.withUnsafeBytes { Data($0) }
624624
buffer += currentChunk
625625
}
626626
return buffer
@@ -828,7 +828,7 @@ extension SubprocessUnixTests {
828828
group.addTask {
829829
var outputs: [String] = []
830830
for try await bit in standardOutput {
831-
let bitString = bit._withUnsafeBytes { ptr in
831+
let bitString = bit.withUnsafeBytes { ptr in
832832
return String(decoding: ptr, as: UTF8.self)
833833
}.trimmingCharacters(in: .whitespacesAndNewlines)
834834
if bitString.contains("\n") {

Tests/SubprocessTests/SubprocessTests+Windows.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,7 @@ extension SubprocessWindowsTests {
367367
) { execution, standardOutput in
368368
var buffer = Data()
369369
for try await chunk in standardOutput {
370-
let currentChunk = chunk._withUnsafeBytes { Data($0) }
370+
let currentChunk = chunk.withUnsafeBytes { Data($0) }
371371
buffer += currentChunk
372372
}
373373
return buffer
@@ -407,7 +407,7 @@ extension SubprocessWindowsTests {
407407
) { execution, standardOutput in
408408
var buffer = Data()
409409
for try await chunk in standardOutput {
410-
let currentChunk = chunk._withUnsafeBytes { Data($0) }
410+
let currentChunk = chunk.withUnsafeBytes { Data($0) }
411411
buffer += currentChunk
412412
}
413413
return buffer
@@ -504,7 +504,7 @@ extension SubprocessWindowsTests {
504504
) { subprocess, standardOutput in
505505
var buffer = Data()
506506
for try await chunk in standardOutput {
507-
let currentChunk = chunk._withUnsafeBytes { Data($0) }
507+
let currentChunk = chunk.withUnsafeBytes { Data($0) }
508508
buffer += currentChunk
509509
}
510510
return buffer

0 commit comments

Comments
 (0)