diff --git a/.spi.yml b/.spi.yml index 8f76450..c6d9883 100644 --- a/.spi.yml +++ b/.spi.yml @@ -1,4 +1,8 @@ version: 1 +metadata: + authors: Apple Inc. builder: configs: - - documentation_targets: [Subprocess] + - swift_version: 6.1 + documentation_targets: [Subprocess] + scheme: Subprocess diff --git a/README.md b/README.md index a1c78dc..a7cc3fc 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ dependencies: [ .package(url: "https://github.com/swiftlang/swift-subprocess.git", branch: "main") ] ``` -Then, adding the `Subprocess` module to your target dependencies: +Then, add the `Subprocess` module to your target dependencies: ```swift .target( @@ -29,27 +29,27 @@ Then, adding the `Subprocess` module to your target dependencies: `Subprocess` offers two [package traits](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0450-swiftpm-package-traits.md): - `SubprocessFoundation`: includes a dependency on `Foundation` and adds extensions on Foundation types like `Data`. This trait is enabled by default. -- `SubprocessSpan`: makes Subprocess’ API, mainly `OutputProtocol`, `RawSpan` based. This trait is enabled whenever `RawSpan` is available and should only be disabled when `RawSpan` is not available. +- `SubprocessSpan`: makes Subprocess's API, mainly `OutputProtocol`, `RawSpan`-based. This trait is enabled whenever `RawSpan` is available and should only be disabled when `RawSpan` is not available. Please find the API proposal [here](https://github.com/swiftlang/swift-foundation/blob/main/Proposals/0007-swift-subprocess.md). ### Swift Versions -The minimal supported Swift version is **Swift 6.1**. +The minimum supported Swift version is **Swift 6.1**. To experiment with the `SubprocessSpan` trait, Swift 6.2 is required. Currently, you can download the Swift 6.2 toolchain (`main` development snapshot) [here](https://www.swift.org/install/macos/#development-snapshots). ## Feature Overview -### Run and Asynchonously Collect Output +### Run and Asynchronously Collect Output The easiest way to spawn a process with `Subprocess` is to simply run it and await its `CollectedResult`: ```swift import Subprocess -let result = try await run(.name("ls")) +let result = try await run(.name("ls"), output: .string(limit: 4096)) print(result.processIdentifier) // prints 1234 print(result.terminationStatus) // prints exited(0) @@ -69,7 +69,7 @@ async let monitorResult = run( .path("/usr/bin/tail"), arguments: ["-f", "/path/to/nginx.log"] ) { execution, standardOutput in - for try await line in standardOutput.lines(encoding: UTF8.self) { + for try await line in standardOutput.lines() { // Parse the log text if line.contains("500") { // Oh no, 500 error @@ -92,6 +92,7 @@ let result = try await run( // add `NewKey=NewValue` environment: .inherit.updating(["NewKey": "NewValue"]), workingDirectory: "/Users/", + output: .string(limit: 4096) ) ``` @@ -127,9 +128,9 @@ let result = try await run(.path("/bin/exe"), platformOptions: platformOptions) ### Flexible Input and Output Configurations By default, `Subprocess`: -- Doesn’t send any input to the child process’s standard input -- Asks the user how to capture the output -- Ignores the child process’s standard error +- Doesn't send any input to the child process's standard input +- Requires you to specify how to capture the output +- Ignores the child process's standard error You can tailor how `Subprocess` handles the standard input, standard output, and standard error by setting the `input`, `output`, and `error` parameters: @@ -137,10 +138,10 @@ You can tailor how `Subprocess` handles the standard input, standard output, and let content = "Hello Subprocess" // Send "Hello Subprocess" to the standard input of `cat` -let result = try await run(.name("cat"), input: .string(content, using: UTF8.self)) +let result = try await run(.name("cat"), input: .string(content), output: .string(limit: 4096)) // Collect both standard error and standard output as Data -let result = try await run(.name("cat"), output: .data, error: .data) +let result = try await run(.name("cat"), output: .data(limit: 4096), error: .data(limit: 4096)) ``` `Subprocess` supports these input options: @@ -155,13 +156,13 @@ Use it by setting `.none` for `input`. This option reads input from a specified `FileDescriptor`. If `closeAfterSpawningProcess` is set to `true`, the subprocess will close the file descriptor after spawning. If `false`, you are responsible for closing it, even if the subprocess fails to spawn. -Use it by setting `.fileDescriptor(closeAfterSpawningProcess:)` for `input`. +Use it by setting `.fileDescriptor(_:closeAfterSpawningProcess:)` for `input`. #### `StringInput` This option reads input from a type conforming to `StringProtocol` using the specified encoding. -Use it by setting `.string(using:)` for `input`. +Use it by setting `.string(_:)` or `.string(_:using:)` for `input`. #### `ArrayInput` @@ -179,13 +180,13 @@ Use it by setting `.data` for `input`. This option reads input from a sequence of `Data`. -Use it by setting `.sequence` for `input`. +Use it by setting `.sequence(_:)` for `input`. #### `DataAsyncSequenceInput` (available with `SubprocessFoundation` trait) This option reads input from an async sequence of `Data`. -Use it by setting `.asyncSequence` for `input`. +Use it by setting `.sequence(_:)` for `input`. --- @@ -201,19 +202,25 @@ Use it by setting `.discarded` for `output` or `error`. This option writes output to a specified `FileDescriptor`. You can choose to have the `Subprocess` close the file descriptor after spawning. -Use it by setting `.fileDescriptor(closeAfterSpawningProcess:)` for `output` or `error`. +Use it by setting `.fileDescriptor(_:closeAfterSpawningProcess:)` for `output` or `error`. #### `StringOutput` This option collects output as a `String` with the given encoding. -Use it by setting `.string(limit:encoding:)` for `output` or `error`. +Use it by setting `.string(limit:)` or `.string(limit:encoding:)` for `output` or `error`. #### `BytesOutput` This option collects output as `[UInt8]`. -Use it by setting`.bytes(limit:)` for `output` or `error`. +Use it by setting `.bytes(limit:)` for `output` or `error`. + +#### `DataOutput` (available with `SubprocessFoundation` trait) + +This option collects output as `Data`. + +Use it by setting `.data(limit:)` for `output` or `error`. ### Cross-platform support diff --git a/Sources/Subprocess/API.swift b/Sources/Subprocess/API.swift index 6255309..9c898ab 100644 --- a/Sources/Subprocess/API.swift +++ b/Sources/Subprocess/API.swift @@ -17,19 +17,18 @@ // MARK: - Collected Result -/// Run an executable with given parameters asynchronously and returns -/// a `CollectedResult` containing the output of the child process. +/// Run an executable with given parameters asynchronously and return a +/// collected result that contains the output of the child process. /// - Parameters: /// - executable: The executable to run. /// - arguments: The arguments to pass to the executable. /// - environment: The environment in which to run the executable. /// - workingDirectory: The working directory in which to run the executable. -/// - platformOptions: The platform specific options to use -/// when running the executable. +/// - platformOptions: The platform-specific options to use when running the executable. /// - input: The input to send to the executable. /// - output: The method to use for redirecting the standard output. /// - error: The method to use for redirecting the standard error. -/// - Returns a CollectedResult containing the result of the run. +/// - Returns: a `CollectedResult` containing the result of the run. public func run< Input: InputProtocol, Output: OutputProtocol, @@ -59,20 +58,19 @@ public func run< ) } -/// Run an executable with given parameters asynchronously and returns -/// a `CollectedResult` containing the output of the child process. +#if SubprocessSpan +/// Run an executable with given parameters asynchronously and return a +/// collected result that contains the output of the child process. /// - Parameters: /// - executable: The executable to run. /// - arguments: The arguments to pass to the executable. /// - environment: The environment in which to run the executable. /// - workingDirectory: The working directory in which to run the executable. -/// - platformOptions: The platform specific options to use -/// when running the executable. +/// - platformOptions: The platform-specific options to use when running the executable. /// - input: span to write to subprocess' standard input. /// - output: The method to use for redirecting the standard output. /// - error: The method to use for redirecting the standard error. -/// - Returns a CollectedResult containing the result of the run. -#if SubprocessSpan +/// - Returns: a CollectedResult containing the result of the run. public func run< InputElement: BitwiseCopyable, Output: OutputProtocol, @@ -138,21 +136,19 @@ public func run< // MARK: - Custom Execution Body /// Run an executable with given parameters and a custom closure -/// to manage the running subprocess' lifetime and stream its standard output. +/// to manage the running subprocess’ lifetime. /// - Parameters: /// - executable: The executable to run. /// - arguments: The arguments to pass to the executable. /// - environment: The environment in which to run the executable. /// - workingDirectory: The working directory in which to run the executable. -/// - platformOptions: The platform specific options to use -/// when running the executable. +/// - platformOptions: The platform-specific options to use when running the executable. /// - input: The input to send to the executable. -/// - output: How to manager executable standard output. -/// - error: How to manager executable standard error. +/// - output: How to manage executable standard output. +/// - error: How to manage executable standard error. /// - isolation: the isolation context to run the body closure. /// - body: The custom execution body to manually control the running process -/// - Returns an executableResult type containing the return value -/// of the closure. +/// - Returns: an `ExecutableResult` type containing the return value of the closure. public func run( _ executable: Executable, arguments: Arguments = [], @@ -198,21 +194,19 @@ public func run( _ executable: Executable, arguments: Arguments = [], @@ -260,21 +254,19 @@ public func run( } } -/// Run an executable with given parameters and a custom closure -/// to manage the running subprocess' lifetime and stream its standard error. +/// Run an executable with given parameters and a custom closure to manage the +/// running subprocess' lifetime and stream its standard error. /// - Parameters: /// - executable: The executable to run. /// - arguments: The arguments to pass to the executable. /// - environment: The environment in which to run the executable. /// - workingDirectory: The working directory in which to run the executable. -/// - platformOptions: The platform specific options to use -/// when running the executable. +/// - platformOptions: The platform-specific options to use when running the executable. /// - input: The input to send to the executable. -/// - output: How to manager executable standard output. -/// - isolation: the isolation context to run the body closure. +/// - output: How to manage executable standard output. +/// - isolation: The isolation context to run the body closure. /// - body: The custom execution body to manually control the running process -/// - Returns an executableResult type containing the return value -/// of the closure. +/// - Returns: an `ExecutableResult` type containing the return value of the closure. public func run( _ executable: Executable, arguments: Arguments = [], @@ -322,21 +314,18 @@ public func run( } } -/// Run an executable with given parameters and a custom closure -/// to manage the running subprocess' lifetime, write to its -/// standard input, and stream its standard output. +/// Run an executable with given parameters and a custom closure to manage the +/// running subprocess' lifetime, write to its standard input, and stream its standard output. /// - Parameters: /// - executable: The executable to run. /// - arguments: The arguments to pass to the executable. /// - environment: The environment in which to run the executable. /// - workingDirectory: The working directory in which to run the executable. -/// - platformOptions: The platform specific options to use -/// when running the executable. -/// - error: How to manager executable standard error. +/// - platformOptions: The platform-specific options to use when running the executable. +/// - error: How to manage executable standard error. /// - isolation: the isolation context to run the body closure. /// - body: The custom execution body to manually control the running process -/// - Returns an executableResult type containing the return value -/// of the closure. +/// - Returns: An `ExecutableResult` type containing the return value of the closure. public func run( _ executable: Executable, arguments: Arguments = [], @@ -367,21 +356,18 @@ public func run( } } -/// Run an executable with given parameters and a custom closure -/// to manage the running subprocess' lifetime, write to its -/// standard input, and stream its standard error. +/// Run an executable with given parameters and a custom closure to manage the +/// running subprocess' lifetime, write to its standard input, and stream its standard error. /// - Parameters: /// - executable: The executable to run. /// - arguments: The arguments to pass to the executable. /// - environment: The environment in which to run the executable. /// - workingDirectory: The working directory in which to run the executable. -/// - platformOptions: The platform specific options to use -/// when running the executable. -/// - output: How to manager executable standard output. +/// - platformOptions: The platform-specific options to use when running the executable. +/// - output: How to manage executable standard output. /// - isolation: the isolation context to run the body closure. /// - body: The custom execution body to manually control the running process -/// - Returns an executableResult type containing the return value -/// of the closure. +/// - Returns: An `ExecutableResult` type containing the return value of the closure. public func run( _ executable: Executable, arguments: Arguments = [], @@ -413,19 +399,17 @@ public func run( } /// Run an executable with given parameters and a custom closure -/// to manage the running subprocess' lifetime, write to its +/// to manage the running subprocess’ lifetime, write to its /// standard input, and stream its standard output and standard error. /// - Parameters: /// - executable: The executable to run. /// - arguments: The arguments to pass to the executable. /// - environment: The environment in which to run the executable. /// - workingDirectory: The working directory in which to run the executable. -/// - platformOptions: The platform specific options to use -/// when running the executable. +/// - platformOptions: The platform-specific options to use when running the executable. /// - isolation: the isolation context to run the body closure. /// - body: The custom execution body to manually control the running process -/// - Returns an executableResult type containing the return value -/// of the closure. +/// - Returns: an `ExecutableResult` type containing the return value of the closure. public func run( _ executable: Executable, arguments: Arguments = [], @@ -474,7 +458,7 @@ public func run( /// - input: The input to send to the executable. /// - output: The method to use for redirecting the standard output. /// - error: The method to use for redirecting the standard error. -/// - Returns a CollectedResult containing the result of the run. +/// - Returns: a `CollectedResult` containing the result of the run. public func run< Input: InputProtocol, Output: OutputProtocol, @@ -562,9 +546,8 @@ public func run< /// - isolation: the isolation context to run the body closure. /// - body: The custom configuration body to manually control /// the running process, write to its standard input, stream -/// its standard output and standard error. -/// - Returns an executableResult type containing the return value -/// of the closure. +/// the standard output and standard error. +/// - Returns: an `ExecutableResult` type containing the return value of the closure. public func run( _ configuration: Configuration, isolation: isolated (any Actor)? = #isolation, diff --git a/Sources/Subprocess/AsyncBufferSequence.swift b/Sources/Subprocess/AsyncBufferSequence.swift index f365f71..3984d76 100644 --- a/Sources/Subprocess/AsyncBufferSequence.swift +++ b/Sources/Subprocess/AsyncBufferSequence.swift @@ -19,8 +19,11 @@ internal import Dispatch #endif +/// A synchronous sequence of buffers used to stream output from subprocess. public struct AsyncBufferSequence: AsyncSequence, @unchecked Sendable { + /// The failure type for the asynchronous sequence. public typealias Failure = any Swift.Error + /// The element type for the asynchronous sequence. public typealias Element = Buffer #if SUBPROCESS_ASYNCIO_DISPATCH @@ -31,8 +34,10 @@ public struct AsyncBufferSequence: AsyncSequence, @unchecked Sendable { internal typealias DiskIO = FileDescriptor #endif + /// Iterator for `AsyncBufferSequence`. @_nonSendable public struct Iterator: AsyncIteratorProtocol { + /// The element type for the iterator. public typealias Element = Buffer private let diskIO: DiskIO @@ -43,6 +48,8 @@ public struct AsyncBufferSequence: AsyncSequence, @unchecked Sendable { self.buffer = [] } + /// Retrieve the next buffer in the sequence, or `nil` if + /// the sequence has ended. public mutating func next() async throws -> Buffer? { // If we have more left in buffer, use that guard self.buffer.isEmpty else { @@ -82,13 +89,27 @@ public struct AsyncBufferSequence: AsyncSequence, @unchecked Sendable { self.diskIO = diskIO } + /// Creates a iterator for this asynchronous sequence. public func makeAsyncIterator() -> Iterator { return Iterator(diskIO: self.diskIO) } - // [New API: 0.0.1] + /// Creates a line sequence to iterate through this `AsyncBufferSequence` line by line. + public func lines() -> LineSequence { + return LineSequence( + underlying: self, + encoding: UTF8.self, + bufferingPolicy: .maxLineLength(128 * 1024) + ) + } + + /// Creates a line sequence to iterate through a `AsyncBufferSequence` line by line. + /// - Parameters: + /// - encoding: The taget encoding to encoding Strings to + /// - bufferingPolicy: How should back-pressure be handled + /// - Returns: A `LineSequence` to iterate though this `AsyncBufferSequence` line by line public func lines( - encoding: Encoding.Type = UTF8.self, + encoding: Encoding.Type, bufferingPolicy: LineSequence.BufferingPolicy = .maxLineLength(128 * 1024) ) -> LineSequence { return LineSequence(underlying: self, encoding: encoding, bufferingPolicy: bufferingPolicy) @@ -97,14 +118,19 @@ public struct AsyncBufferSequence: AsyncSequence, @unchecked Sendable { // MARK: - LineSequence extension AsyncBufferSequence { - // [New API: 0.0.1] + /// Line sequence parses and splits an asynchronous sequence of buffers into lines. + /// + /// It is the preferred method to convert `Buffer` to `String` public struct LineSequence: AsyncSequence, Sendable { + /// The element type for the asynchronous sequence. public typealias Element = String private let base: AsyncBufferSequence private let bufferingPolicy: BufferingPolicy + /// The iterator for line sequence. public struct AsyncIterator: AsyncIteratorProtocol { + /// The element type for this Iterator. public typealias Element = String private var source: AsyncBufferSequence.AsyncIterator @@ -126,6 +152,7 @@ extension AsyncBufferSequence { self.bufferingPolicy = bufferingPolicy } + /// Retrieves the next line, or returns nil if the sequence ends. public mutating func next() async throws -> String? { func loadBuffer() async throws -> [Encoding.CodeUnit]? { @@ -300,6 +327,7 @@ extension AsyncBufferSequence { } } + /// Creates a iterator for this line sequence. public func makeAsyncIterator() -> AsyncIterator { return AsyncIterator( underlyingIterator: self.base.makeAsyncIterator(), @@ -319,6 +347,7 @@ extension AsyncBufferSequence { } extension AsyncBufferSequence.LineSequence { + /// A strategy that handles the exhaustion of a buffer’s capacity. public enum BufferingPolicy: Sendable { /// Continue to add to the buffer, without imposing a limit /// on the number of buffered elements (line length). diff --git a/Sources/Subprocess/Buffer.swift b/Sources/Subprocess/Buffer.swift index 1a2e8a3..1bf739f 100644 --- a/Sources/Subprocess/Buffer.swift +++ b/Sources/Subprocess/Buffer.swift @@ -9,6 +9,8 @@ // //===----------------------------------------------------------------------===// +// swift-format-ignore-file + #if canImport(Darwin) || canImport(Glibc) || canImport(Android) || canImport(Musl) @preconcurrency internal import Dispatch #endif @@ -93,10 +95,12 @@ extension AsyncBufferSequence.Buffer { // MARK: - Hashable, Equatable extension AsyncBufferSequence.Buffer: Equatable, Hashable { #if SUBPROCESS_ASYNCIO_DISPATCH + /// Returns a Boolean value that indicates whether two buffers are equal. public static func == (lhs: AsyncBufferSequence.Buffer, rhs: AsyncBufferSequence.Buffer) -> Bool { return lhs.data == rhs.data } + /// Hashes the essential components of this value by feeding them into the given hasher. public func hash(into hasher: inout Hasher) { return self.data.hash(into: &hasher) } diff --git a/Sources/Subprocess/Configuration.swift b/Sources/Subprocess/Configuration.swift index 4a93040..a6fcff7 100644 --- a/Sources/Subprocess/Configuration.swift +++ b/Sources/Subprocess/Configuration.swift @@ -31,7 +31,7 @@ internal import Dispatch import Synchronization -/// A collection of configurations parameters to use when +/// A collection of configuration parameters to use when /// spawning a subprocess. public struct Configuration: Sendable { /// The executable to run. @@ -41,12 +41,21 @@ public struct Configuration: Sendable { /// The environment to use when running the executable. public var environment: Environment /// The working directory to use when running the executable. - /// If this property is `nil`, the subprocess will inherit the working directory from the parent process. + /// + /// If this property is `nil`, the subprocess will inherit + /// the working directory from the parent process. public var workingDirectory: FilePath? /// The platform specific options to use when /// running the subprocess. public var platformOptions: PlatformOptions + /// Creates a Configuration with the parameters you provide. + /// - Parameters: + /// - executable: the executable to run + /// - arguments: the arguments to pass to the executable. + /// - environment: the environment to use when running the executable. + /// - workingDirectory: the working directory to use when running the executable. + /// - platformOptions: The platform specific options to use when running subprocess. public init( executable: Executable, arguments: Arguments = [], @@ -119,6 +128,7 @@ public struct Configuration: Sendable { } extension Configuration: CustomStringConvertible, CustomDebugStringConvertible { + /// A textual representation of this configuration. public var description: String { return """ Configuration( @@ -131,6 +141,7 @@ extension Configuration: CustomStringConvertible, CustomDebugStringConvertible { """ } + /// A debug-oriented textual representation of this configuration. public var debugDescription: String { return """ Configuration( @@ -198,8 +209,7 @@ extension Configuration { // MARK: - Executable -/// `Executable` defines how the executable should -/// be looked up for execution. +/// Executable defines how subprocess looks up the executable for execution. public struct Executable: Sendable, Hashable { internal enum Storage: Sendable, Hashable { case executable(String) @@ -231,6 +241,7 @@ public struct Executable: Sendable, Hashable { } extension Executable: CustomStringConvertible, CustomDebugStringConvertible { + /// A textual representation of this executable. public var description: String { switch storage { case .executable(let executableName): @@ -240,6 +251,7 @@ extension Executable: CustomStringConvertible, CustomDebugStringConvertible { } } + /// A debug-oriented textual representation of this executable. public var debugDescription: String { switch storage { case .executable(let string): @@ -254,6 +266,7 @@ extension Executable: CustomStringConvertible, CustomDebugStringConvertible { /// A collection of arguments to pass to the subprocess. public struct Arguments: Sendable, ExpressibleByArrayLiteral, Hashable { + /// The type of the elements of an array literal. public typealias ArrayLiteralElement = String internal let storage: [StringOrRawBytes] @@ -303,7 +316,7 @@ public struct Arguments: Sendable, ExpressibleByArrayLiteral, Hashable { self.executablePathOverride = nil } } - + /// Create an arguments object using the array you provide. public init(_ array: [[UInt8]]) { self.storage = array.map { .rawBytes($0) } self.executablePathOverride = nil @@ -312,6 +325,7 @@ public struct Arguments: Sendable, ExpressibleByArrayLiteral, Hashable { } extension Arguments: CustomStringConvertible, CustomDebugStringConvertible { + /// A textual representation of the arguments. public var description: String { var result: [String] = self.storage.map(\.description) @@ -321,6 +335,7 @@ extension Arguments: CustomStringConvertible, CustomDebugStringConvertible { return result.description } + /// A debug-oriented textual representation of the arguments. public var debugDescription: String { return self.description } } @@ -364,6 +379,7 @@ public struct Environment: Sendable, Hashable { } extension Environment: CustomStringConvertible, CustomDebugStringConvertible { + /// A textual representation of the environment. public var description: String { switch self.config { case .custom(let customDictionary): @@ -386,6 +402,7 @@ extension Environment: CustomStringConvertible, CustomDebugStringConvertible { } } + /// A debug-oriented textual representation of the environment. public var debugDescription: String { return self.description } @@ -427,8 +444,10 @@ extension Environment: CustomStringConvertible, CustomDebugStringConvertible { @frozen public enum TerminationStatus: Sendable, Hashable { #if canImport(WinSDK) + /// The type of the status code. public typealias Code = DWORD #else + /// The type of the status code. public typealias Code = CInt #endif @@ -448,6 +467,7 @@ public enum TerminationStatus: Sendable, Hashable { } extension TerminationStatus: CustomStringConvertible, CustomDebugStringConvertible { + /// A textual representation of this termination status. public var description: String { switch self { case .exited(let code): @@ -457,6 +477,7 @@ extension TerminationStatus: CustomStringConvertible, CustomDebugStringConvertib } } + /// A debug-oriented textual representation of this termination status. public var debugDescription: String { return self.description } @@ -630,12 +651,13 @@ internal func _safelyClose(_ target: _CloseTarget) throws { } } -/// `IODescriptor` wraps platform-specific `FileDescriptor`, -/// which is used to establish a connection to the standard input/output (IO) -/// system during the process of spawning a child process. Unlike `IODescriptor`, -/// the `IODescriptor` does not support data read/write operations; -/// its primary function is to facilitate the spawning of child processes -/// by providing a platform-specific file descriptor. +/// An IO descriptor wraps platform-specific file descriptor, which establishes a +/// connection to the standard input/output (IO) system during the process of +/// spawning a child process. +/// +/// Unlike a file descriptor, the `IODescriptor` does not support +/// data read/write operations; its primary function is to facilitate the spawning of +/// child processes by providing a platform-specific file descriptor. internal struct IODescriptor: ~Copyable { #if canImport(WinSDK) typealias Descriptor = HANDLE @@ -945,7 +967,12 @@ extension Optional where Wrapped == String { } } -/// Runs `body`, and then runs `onCleanup` if body throws an error, or if the parent task is cancelled. In the latter case, `onCleanup` may be run concurrently with `body`. `body` is guaranteed to run exactly once. `onCleanup` is guaranteed to run only once, or not at all. +/// Runs the body close, then runs the on-cleanup closure if the body closure throws an error +/// or if the parent task is cancelled. +/// +/// In the latter case, `onCleanup` may be run concurrently with `body`. +/// The `body` closure is guaranteed to run exactly once. +/// The `onCleanup` closure is guaranteed to run only once, or not at all. internal func withAsyncTaskCleanupHandler( _ body: () async throws -> Result, onCleanup handler: @Sendable @escaping () async -> Void, diff --git a/Sources/Subprocess/Error.swift b/Sources/Subprocess/Error.swift index 0a3b8e8..897e3fe 100644 --- a/Sources/Subprocess/Error.swift +++ b/Sources/Subprocess/Error.swift @@ -53,6 +53,7 @@ extension SubprocessError { case invalidWindowsPath(String) } + /// The numeric value of this code. public var value: Int { switch self.storage { case .spawnFailed: @@ -98,6 +99,7 @@ extension SubprocessError { // MARK: - Description extension SubprocessError: CustomStringConvertible, CustomDebugStringConvertible { + /// A textual representation of this subprocess error. public var description: String { switch self.code.storage { case .spawnFailed: @@ -133,6 +135,7 @@ extension SubprocessError: CustomStringConvertible, CustomDebugStringConvertible } } + /// A debug-oriented textual representation of this subprocess error. public var debugDescription: String { self.description } } @@ -142,13 +145,16 @@ extension SubprocessError { /// - On Windows, `UnderlyingError` wraps Windows Error code public struct UnderlyingError: Swift.Error, RawRepresentable, Hashable, Sendable { #if os(Windows) + /// The type for the raw value of the underlying error. public typealias RawValue = DWORD #else + /// The type for the raw value of the underlying error. public typealias RawValue = Int32 #endif + /// The platform specific value for this underlying error. public let rawValue: RawValue - + /// Initialize a `UnderlyingError` with given error value public init(rawValue: RawValue) { self.rawValue = rawValue } diff --git a/Sources/Subprocess/IO/Input.swift b/Sources/Subprocess/IO/Input.swift index 72ae534..3fdccc3 100644 --- a/Sources/Subprocess/IO/Input.swift +++ b/Sources/Subprocess/IO/Input.swift @@ -33,19 +33,21 @@ import FoundationEssentials // MARK: - Input -/// `InputProtocol` defines the `write(with:)` method that a type -/// must implement to serve as the input source for a subprocess. +/// InputProtocol defines a type that serves as the input source for a subprocess. +/// +/// The protocol defines the `write(with:)` method that a type must +/// implement to serve as the input source. public protocol InputProtocol: Sendable, ~Copyable { /// Asynchronously write the input to the subprocess using the /// write file descriptor func write(with writer: StandardInputWriter) async throws } -/// A concrete `Input` type for subprocesses that indicates -/// the absence of input to the subprocess. On Unix-like systems, -/// `NoInput` redirects the standard input of the subprocess -/// to `/dev/null`, while on Windows, it does not bind any -/// file handle to the subprocess standard input handle. +/// A concrete input type for subprocesses that indicates the absence +/// of input to the subprocess. +/// +/// On Unix-like systems, `NoInput` redirects the standard input of the subprocess to /dev/null, +/// while on Windows, it redirects to `NUL`. public struct NoInput: InputProtocol { internal func createPipe() throws -> CreatedPipe { #if os(Windows) @@ -60,18 +62,19 @@ public struct NoInput: InputProtocol { ) } + /// Asynchronously write the input to the subprocess that uses the + /// write file descriptor. public func write(with writer: StandardInputWriter) async throws { - // noop + fatalError("Unexpected call to \(#function)") } internal init() {} } -/// A concrete `Input` type for subprocesses that -/// reads input from a specified `FileDescriptor`. -/// Developers have the option to instruct the `Subprocess` to -/// automatically close the provided `FileDescriptor` -/// after the subprocess is spawned. +/// A concrete input type for subprocesses that reads input from a specified FileDescriptor. +/// +/// Developers have the option to instruct the Subprocess to automatically close the provided +/// FileDescriptor after the subprocess is spawned. public struct FileDescriptorInput: InputProtocol { private let fileDescriptor: FileDescriptor private let closeAfterSpawningProcess: Bool @@ -91,8 +94,10 @@ public struct FileDescriptorInput: InputProtocol { ) } + /// Asynchronously write the input to the subprocess that use the + /// write file descriptor. public func write(with writer: StandardInputWriter) async throws { - // noop + fatalError("Unexpected call to \(#function)") } internal init( @@ -114,6 +119,8 @@ public struct StringInput< >: InputProtocol { private let string: InputString + /// Asynchronously write the input to the subprocess that use the + /// write file descriptor. public func write(with writer: StandardInputWriter) async throws { guard let array = self.string.byteArray(using: Encoding.self) else { return @@ -126,11 +133,13 @@ public struct StringInput< } } -/// A concrete `Input` type for subprocesses that reads input -/// from a given `UInt8` Array. +/// A concrete input type for subprocesses that reads input from +/// a given `UInt8` Array. public struct ArrayInput: InputProtocol { private let array: [UInt8] + /// Asynchronously write the input to the subprocess using the + /// write file descriptor public func write(with writer: StandardInputWriter) async throws { _ = try await writer.write(self.array) } @@ -140,11 +149,13 @@ public struct ArrayInput: InputProtocol { } } -/// A concrete `Input` type for subprocess that indicates that -/// the Subprocess should read its input from `StandardInputWriter`. +/// A concrete input type that the run closure uses to write custom input +/// into the subprocess. internal struct CustomWriteInput: InputProtocol { + /// Asynchronously write the input to the subprocess using the + /// write file descriptor. public func write(with writer: StandardInputWriter) async throws { - // noop + fatalError("Unexpected call to \(#function)") } internal init() {} @@ -222,29 +233,30 @@ public final actor StandardInputWriter: Sendable { self.diskIO = diskIO } - /// Write an array of UInt8 to the standard input of the subprocess. + /// Write an array of 8-bit unsigned integers to the standard input of the subprocess. /// - Parameter array: The sequence of bytes to write. - /// - Returns number of bytes written. + /// - Returns: the number of bytes written. public func write( _ array: [UInt8] ) async throws -> Int { return try await AsyncIO.shared.write(array, to: self.diskIO) } - /// Write a `RawSpan` to the standard input of the subprocess. - /// - Parameter span: The span to write - /// - Returns number of bytes written #if SubprocessSpan + /// Write a raw span to the standard input of the subprocess. + /// + /// - Parameter `span`: The span to write. + /// - Returns: the number of bytes written. public func write(_ span: borrowing RawSpan) async throws -> Int { return try await AsyncIO.shared.write(span, to: self.diskIO) } #endif - /// Write a StringProtocol to the standard input of the subprocess. + /// Write a type that conforms to StringProtocol to the standard input of the subprocess. /// - Parameters: /// - string: The string to write. /// - encoding: The encoding to use when converting string to bytes - /// - Returns number of bytes written. + /// - Returns: number of bytes written. public func write( _ string: some StringProtocol, using encoding: Encoding.Type = UTF8.self diff --git a/Sources/Subprocess/IO/Output.swift b/Sources/Subprocess/IO/Output.swift index 9c7e3bb..a7cb372 100644 --- a/Sources/Subprocess/IO/Output.swift +++ b/Sources/Subprocess/IO/Output.swift @@ -23,11 +23,12 @@ internal import Dispatch // MARK: - Output -/// `OutputProtocol` specifies the set of methods that a type -/// must implement to serve as the output target for a subprocess. -/// Instead of developing custom implementations of `OutputProtocol`, -/// it is recommended to utilize the default implementations provided -/// by the `Subprocess` library to specify the output handling requirements. +/// Output protocol specifies the set of methods that a type must implement to +/// serve as the output target for a subprocess. +/// +/// Instead of developing custom implementations of `OutputProtocol`, use the +/// default implementations provided by the `Subprocess` library to specify the +/// output handling requirements. public protocol OutputProtocol: Sendable, ~Copyable { associatedtype OutputType: Sendable @@ -48,13 +49,15 @@ extension OutputProtocol { public var maxSize: Int { 128 * 1024 } } -/// A concrete `Output` type for subprocesses that indicates that -/// the `Subprocess` should not collect or redirect output -/// from the child process. On Unix-like systems, `DiscardedOutput` -/// redirects the standard output of the subprocess to `/dev/null`, -/// while on Windows, it does not bind any file handle to the -/// subprocess standard output handle. +/// A concrete output type for subprocesses that indicates that the +/// subprocess should not collect or redirect output from the child +/// process. +/// +/// On Unix-like systems, `DiscardedOutput` redirects the +/// standard output of the subprocess to `/dev/null`, while on Windows, +/// redirects the output to `NUL`. public struct DiscardedOutput: OutputProtocol { + /// The type for the output. public typealias OutputType = Void internal func createPipe() throws -> CreatedPipe { @@ -74,12 +77,13 @@ public struct DiscardedOutput: OutputProtocol { internal init() {} } -/// A concrete `Output` type for subprocesses that -/// writes output to a specified `FileDescriptor`. -/// Developers have the option to instruct the `Subprocess` to -/// automatically close the provided `FileDescriptor` -/// after the subprocess is spawned. +/// A concrete output type for subprocesses that writes output +/// to a specified file descriptor. +/// +/// Developers have the option to instruct the `Subprocess` to automatically +/// close the related `FileDescriptor` after the subprocess is spawned. public struct FileDescriptorOutput: OutputProtocol { + /// The type for this output. public typealias OutputType = Void private let closeAfterSpawningProcess: Bool @@ -111,13 +115,14 @@ public struct FileDescriptorOutput: OutputProtocol { /// A concrete `Output` type for subprocesses that collects output /// from the subprocess as `String` with the given encoding. -/// This option must be used with he `run()` method that -/// returns a `CollectedResult`. public struct StringOutput: OutputProtocol { + /// The type for this output. public typealias OutputType = String? + /// The max number of bytes to collect. public let maxSize: Int #if SubprocessSpan + /// Create a string from a raw span. public func output(from span: RawSpan) throws -> String? { // FIXME: Span to String var array: [UInt8] = [] @@ -127,6 +132,8 @@ public struct StringOutput: OutputProtocol { return String(decodingBytes: array, as: Encoding.self) } #endif + + /// Create a String from a sequence of 8-bit unsigned integers. public func output(from buffer: some Sequence) throws -> String? { // FIXME: Span to String let array = Array(buffer) @@ -138,11 +145,12 @@ public struct StringOutput: OutputProtocol { } } -/// A concrete `Output` type for subprocesses that collects output -/// from the subprocess as `[UInt8]`. This option must be used with -/// the `run()` method that returns a `CollectedResult` +/// A concrete `Output` type for subprocesses that collects output from +/// the subprocess as `[UInt8]`. public struct BytesOutput: OutputProtocol { + /// The output type for this output option public typealias OutputType = [UInt8] + /// The max number of bytes to collect public let maxSize: Int internal func captureOutput( @@ -181,10 +189,14 @@ public struct BytesOutput: OutputProtocol { } #if SubprocessSpan + /// Create an Array from `RawSpawn`. + /// Not implemented public func output(from span: RawSpan) throws -> [UInt8] { fatalError("Not implemented") } #endif + /// Create an Array from `Sequence`. + /// Not implemented public func output(from buffer: some Sequence) throws -> [UInt8] { fatalError("Not implemented") } @@ -194,11 +206,12 @@ public struct BytesOutput: OutputProtocol { } } -/// A concrete `Output` type for subprocesses that redirects -/// the child output to the `.standardOutput` (a sequence) or `.standardError` -/// property of `Execution`. This output type is -/// only applicable to the `run()` family that takes a custom closure. +/// A concrete `Output` type for subprocesses that redirects the child output to +/// the `.standardOutput` (a sequence) or `.standardError` property of +/// `Execution`. This output type is only applicable to the `run()` family that +/// takes a custom closure. internal struct SequenceOutput: OutputProtocol { + /// The output type for this output option public typealias OutputType = Void internal init() {} @@ -254,6 +267,7 @@ extension OutputProtocol where Self == BytesOutput { // MARK: - Span Default Implementations #if SubprocessSpan extension OutputProtocol { + /// Create an Array from `Sequence`. public func output(from buffer: some Sequence) throws -> OutputType { guard let rawBytes: UnsafeRawBufferPointer = buffer as? UnsafeRawBufferPointer else { fatalError("Unexpected input type passed: \(type(of: buffer))") @@ -338,14 +352,14 @@ extension OutputProtocol where OutputType == Void { internal func captureOutput(from fileDescriptor: consuming IOChannel?) async throws {} #if SubprocessSpan - /// Convert the output from Data to expected output type + /// Convert the output from raw span to expected output type public func output(from span: RawSpan) throws { - // noop + fatalError("Unexpected call to \(#function)") } #endif - + /// Convert the output from a sequence of 8-bit unsigned integers to expected output type. public func output(from buffer: some Sequence) throws { - // noop + fatalError("Unexpected call to \(#function)") } } diff --git a/Sources/Subprocess/Platforms/Subprocess+Darwin.swift b/Sources/Subprocess/Platforms/Subprocess+Darwin.swift index c1aa76c..e487f7c 100644 --- a/Sources/Subprocess/Platforms/Subprocess+Darwin.swift +++ b/Sources/Subprocess/Platforms/Subprocess+Darwin.swift @@ -38,6 +38,7 @@ import FoundationEssentials /// The collection of platform-specific settings /// to configure the subprocess when running public struct PlatformOptions: Sendable { + /// Constants that indicate the nature and importance of work to the system. public var qualityOfService: QualityOfService = .default /// Set user ID for the subprocess public var userID: uid_t? = nil @@ -82,11 +83,13 @@ public struct PlatformOptions: Sendable { ) throws -> Void )? = nil + /// Create platform options with the default values. public init() {} } extension PlatformOptions { #if SubprocessFoundation + /// Constants that indicate the nature and importance of work to the system. public typealias QualityOfService = Foundation.QualityOfService #else /// Constants that indicate the nature and importance of work to the system. @@ -142,10 +145,12 @@ extension PlatformOptions: CustomStringConvertible, CustomDebugStringConvertible """ } + /// A textual representation of the platform options. public var description: String { return self.description(withIndent: 0) } + /// A debug oriented textual representation of the platform options. public var debugDescription: String { return self.description(withIndent: 0) } @@ -479,11 +484,12 @@ extension Configuration { // MARK: - ProcessIdentifier -/// A platform independent identifier for a Subprocess. +/// A platform-independent identifier for a Subprocess. public struct ProcessIdentifier: Sendable, Hashable { /// The platform specific process identifier value public let value: pid_t + /// Initialize a process identifier with the value you provide. public init(value: pid_t) { self.value = value } @@ -492,8 +498,9 @@ public struct ProcessIdentifier: Sendable, Hashable { } extension ProcessIdentifier: CustomStringConvertible, CustomDebugStringConvertible { + /// A textual representation of the process identifier. public var description: String { "\(self.value)" } - + /// A debug-oriented textual representation of the process identifier. public var debugDescription: String { "\(self.value)" } } diff --git a/Sources/Subprocess/Platforms/Subprocess+Linux.swift b/Sources/Subprocess/Platforms/Subprocess+Linux.swift index aee34f8..912f5b2 100644 --- a/Sources/Subprocess/Platforms/Subprocess+Linux.swift +++ b/Sources/Subprocess/Platforms/Subprocess+Linux.swift @@ -179,9 +179,9 @@ private struct MonitorThreadContext: Sendable { } } -// Okay to be unlocked global mutable because this value is only set once like dispatch_once +// Okay to be unlocked global mutable state because this value is only set once, like dispatch_once private nonisolated(unsafe) var _signalPipe: (readEnd: CInt, writeEnd: CInt) = (readEnd: -1, writeEnd: -1) -// Okay to be unlocked global mutable because this value is only set once like dispatch_once +// Okay to be unlocked global mutable state because this value is only set once, like dispatch_once private nonisolated(unsafe) var _waitProcessDescriptorSupported = false private let _processMonitorState: Mutex = .init(.notStarted) @@ -455,7 +455,7 @@ private func _blockAndWaitForProcessDescriptor(_ pidfd: CInt, context: MonitorTh continuation?.resume(with: terminationStatus) } -// On older kernel, fallback to using signal handlers +// On older kernels, fall back to using signal handlers private typealias ResultContinuation = ( result: Result, continuation: CheckedContinuation diff --git a/Sources/Subprocess/Platforms/Subprocess+Unix.swift b/Sources/Subprocess/Platforms/Subprocess+Unix.swift index 17d5b32..5553c10 100644 --- a/Sources/Subprocess/Platforms/Subprocess+Unix.swift +++ b/Sources/Subprocess/Platforms/Subprocess+Unix.swift @@ -33,8 +33,8 @@ import Musl // MARK: - Signals -/// Signals are standardized messages sent to a running program -/// to trigger specific behavior, such as quitting or error handling. +/// Signals are standardized messages sent to a running program to +/// trigger specific behavior, such as quitting or error handling. public struct Signal: Hashable, Sendable { /// The underlying platform specific value for the signal public let rawValue: Int32 @@ -571,12 +571,13 @@ extension Configuration { // MARK: - ProcessIdentifier -/// A platform independent identifier for a Subprocess. +/// A platform-independent identifier for a subprocess. public struct ProcessIdentifier: Sendable, Hashable { /// The platform specific process identifier value public let value: pid_t #if os(Linux) || os(Android) || os(FreeBSD) + /// The process file descriptor (pidfd) for the running execution. public let processDescriptor: CInt #else internal let processDescriptor: CInt // not used on other platforms @@ -595,8 +596,9 @@ public struct ProcessIdentifier: Sendable, Hashable { } extension ProcessIdentifier: CustomStringConvertible, CustomDebugStringConvertible { + /// A textual representation of the process identifier. public var description: String { "\(self.value)" } - + /// A debug-oriented textual representation of the process identifier. public var debugDescription: String { "\(self.value)" } } @@ -628,7 +630,7 @@ public struct PlatformOptions: Sendable { /// the child process terminates. /// Always ends in sending a `.kill` signal at the end. public var teardownSequence: [TeardownStep] = [] - + /// Create platform options with the default values. public init() {} } @@ -646,10 +648,12 @@ extension PlatformOptions: CustomStringConvertible, CustomDebugStringConvertible """ } + /// A textual representation of the platform options. public var description: String { return self.description(withIndent: 0) } + /// A debug-oriented textual representation of the platform options. public var debugDescription: String { return self.description(withIndent: 0) } diff --git a/Sources/Subprocess/Platforms/Subprocess+Windows.swift b/Sources/Subprocess/Platforms/Subprocess+Windows.swift index b352698..14603af 100644 --- a/Sources/Subprocess/Platforms/Subprocess+Windows.swift +++ b/Sources/Subprocess/Platforms/Subprocess+Windows.swift @@ -21,7 +21,7 @@ internal import Dispatch import _SubprocessCShims -// Windows specific implementation +// Windows-specific implementation extension Configuration { internal func spawn( withInput inputPipe: consuming CreatedPipe, @@ -443,13 +443,14 @@ public struct PlatformOptions: Sendable { /// A `UserCredentials` to use spawning the subprocess /// as a different user public struct UserCredentials: Sendable, Hashable { - // The name of the user. This is the name - // of the user account to run as. + /// The name of the user. + /// + /// This is the name of the user account to run as. public var username: String - // The clear-text password for the account. + /// The clear-text password for the account. public var password: String - // The name of the domain or server whose account database - // contains the account. + /// The name of the domain or server whose account database + /// contains the account. public var domain: String? } @@ -553,6 +554,7 @@ public struct PlatformOptions: Sendable { ) throws -> Void )? = nil + /// Create platform options with the default values. public init() {} } @@ -570,10 +572,12 @@ extension PlatformOptions: CustomStringConvertible, CustomDebugStringConvertible """ } + /// A textual representation of the platform options. public var description: String { return self.description(withIndent: 0) } + /// A debug-oriented textual representation of the platform options. public var debugDescription: String { return self.description(withIndent: 0) } @@ -936,11 +940,13 @@ extension Environment { // MARK: - ProcessIdentifier -/// A platform independent identifier for a subprocess. +/// A platform-independent identifier for a subprocess. public struct ProcessIdentifier: Sendable, Hashable { /// Windows specific process identifier value public let value: DWORD + /// Process handle for current execution. public nonisolated(unsafe) let processDescriptor: HANDLE + /// Main thread handle for current execution. public nonisolated(unsafe) let threadHandle: HANDLE internal init(value: DWORD, processDescriptor: HANDLE, threadHandle: HANDLE) { @@ -960,10 +966,12 @@ public struct ProcessIdentifier: Sendable, Hashable { } extension ProcessIdentifier: CustomStringConvertible, CustomDebugStringConvertible { + /// A textual representation of the process identifier. public var description: String { return "(processID: \(self.value))" } + /// A debug-oriented textual representation of the process identifier. public var debugDescription: String { return description } @@ -1416,8 +1424,8 @@ fileprivate func SUCCEEDED(_ hr: HRESULT) -> Bool { hr >= 0 } -// This is a non-standard extension to the Windows SDK that allows us to convert -// an HRESULT to a Win32 error code. +// This is a non-standard extension to the Windows SDK that allows us to convert an +// HRESULT to a Win32 error code. @inline(__always) fileprivate func WIN32_FROM_HRESULT(_ hr: HRESULT) -> DWORD { if SUCCEEDED(hr) { return DWORD(ERROR_SUCCESS) } @@ -1443,8 +1451,8 @@ extension UInt8 { /// - parameter initialSize: Initial size of the buffer (including the null terminator) to allocate to hold the returned string. /// - parameter maxSize: Maximum size of the buffer (including the null terminator) to allocate to hold the returned string. /// - parameter body: Closure to call the Win32 API function to populate the provided buffer. -/// Should return the number of UTF-16 code units (not including the null terminator) copied, 0 to indicate an error. -/// If the buffer is not of sufficient size, should return a value greater than or equal to the size of the buffer. +/// Should return the number of UTF-16 code units (not including the null terminator) copied, 0 to indicate an error. +/// If the buffer is not of sufficient size, should return a value greater than or equal to the size of the buffer. internal func fillNullTerminatedWideStringBuffer( initialSize: DWORD, maxSize: DWORD, diff --git a/Sources/Subprocess/Result.swift b/Sources/Subprocess/Result.swift index 195c8c7..a206bd8 100644 --- a/Sources/Subprocess/Result.swift +++ b/Sources/Subprocess/Result.swift @@ -18,8 +18,8 @@ // MARK: - Result /// A simple wrapper around the generic result returned by the -/// `run` closures with the corresponding `TerminationStatus` -/// of the child process. +/// `run` closure with the corresponding termination status of +/// the child process. public struct ExecutionResult { /// The termination status of the child process public let terminationStatus: TerminationStatus @@ -42,7 +42,9 @@ public struct CollectedResult< public let processIdentifier: ProcessIdentifier /// The termination status of the executed subprocess public let terminationStatus: TerminationStatus + /// The captured standard output of the executed subprocess. public let standardOutput: Output.OutputType + /// The captured standard error of the executed subprocess. public let standardError: Error.OutputType internal init( @@ -66,6 +68,7 @@ extension CollectedResult: Hashable where Output.OutputType: Hashable, Error.Out extension CollectedResult: CustomStringConvertible where Output.OutputType: CustomStringConvertible, Error.OutputType: CustomStringConvertible { + /// A textual representation of the collected result. public var description: String { return """ CollectedResult( @@ -80,6 +83,7 @@ where Output.OutputType: CustomStringConvertible, Error.OutputType: CustomString extension CollectedResult: CustomDebugStringConvertible where Output.OutputType: CustomDebugStringConvertible, Error.OutputType: CustomDebugStringConvertible { + /// A debug-oriented textual representation of the collected result. public var debugDescription: String { return """ CollectedResult( @@ -98,6 +102,7 @@ extension ExecutionResult: Equatable where Result: Equatable {} extension ExecutionResult: Hashable where Result: Hashable {} extension ExecutionResult: CustomStringConvertible where Result: CustomStringConvertible { + /// A textual representation of the execution result. public var description: String { return """ ExecutionResult( @@ -109,6 +114,7 @@ extension ExecutionResult: CustomStringConvertible where Result: CustomStringCon } extension ExecutionResult: CustomDebugStringConvertible where Result: CustomDebugStringConvertible { + /// A debug-oriented textual representation of this execution result. public var debugDescription: String { return """ ExecutionResult( diff --git a/Sources/Subprocess/SubprocessFoundation/Input+Foundation.swift b/Sources/Subprocess/SubprocessFoundation/Input+Foundation.swift index e7fabb2..e587c89 100644 --- a/Sources/Subprocess/SubprocessFoundation/Input+Foundation.swift +++ b/Sources/Subprocess/SubprocessFoundation/Input+Foundation.swift @@ -27,11 +27,12 @@ import FoundationEssentials internal import Dispatch -/// A concrete `Input` type for subprocesses that reads input -/// from a given `Data`. +/// A concrete input type for subprocesses that reads input from data. public struct DataInput: InputProtocol { private let data: Data + /// Asynchronously write the input to the subprocess using the + /// write file descriptor. public func write(with writer: StandardInputWriter) async throws { _ = try await writer.write(self.data) } @@ -41,13 +42,15 @@ public struct DataInput: InputProtocol { } } -/// A concrete `Input` type for subprocesses that accepts input -/// from a specified sequence of `Data`. +/// A concrete input type for subprocesses that accepts input from +/// a specified sequence of data. public struct DataSequenceInput< InputSequence: Sequence & Sendable >: InputProtocol where InputSequence.Element == Data { private let sequence: InputSequence + /// Asynchronously write the input to the subprocess using the + /// write file descriptor. public func write(with writer: StandardInputWriter) async throws { var buffer = Data() for chunk in self.sequence { @@ -72,6 +75,8 @@ public struct DataAsyncSequenceInput< _ = try await writer.write(chunk) } + /// Asynchronously write the input to the subprocess using the + /// write file descriptor. public func write(with writer: StandardInputWriter) async throws { for try await chunk in self.sequence { try await self.writeChunk(chunk, with: writer) diff --git a/Sources/Subprocess/SubprocessFoundation/Output+Foundation.swift b/Sources/Subprocess/SubprocessFoundation/Output+Foundation.swift index f5ea4a2..ed98938 100644 --- a/Sources/Subprocess/SubprocessFoundation/Output+Foundation.swift +++ b/Sources/Subprocess/SubprocessFoundation/Output+Foundation.swift @@ -20,17 +20,20 @@ import FoundationEssentials #endif /// A concrete `Output` type for subprocesses that collects output -/// from the subprocess as `Data`. This option must be used with -/// the `run()` method that returns a `CollectedResult` +/// from the subprocess as data. public struct DataOutput: OutputProtocol { + /// The output type for this output option public typealias OutputType = Data + /// The maximum number of bytes to collect. public let maxSize: Int #if SubprocessSpan + /// Create data from a raw span. public func output(from span: RawSpan) throws -> Data { return Data(span) } #else + /// Create a data from sequence of 8-bit unsigned integers. public func output(from buffer: some Sequence) throws -> Data { return Data(buffer) } diff --git a/Sources/Subprocess/SubprocessFoundation/Span+SubprocessFoundation.swift b/Sources/Subprocess/SubprocessFoundation/Span+SubprocessFoundation.swift index da6785e..69629f4 100644 --- a/Sources/Subprocess/SubprocessFoundation/Span+SubprocessFoundation.swift +++ b/Sources/Subprocess/SubprocessFoundation/Span+SubprocessFoundation.swift @@ -26,7 +26,7 @@ extension Data { self = s.withUnsafeBytes { Data($0) } } - public var bytes: RawSpan { + internal var bytes: RawSpan { // FIXME: For demo purpose only let ptr = self.withUnsafeBytes { ptr in return ptr diff --git a/Tests/SubprocessTests/PlatformConformance.swift b/Tests/SubprocessTests/PlatformConformance.swift index a7268f6..61f87bf 100644 --- a/Tests/SubprocessTests/PlatformConformance.swift +++ b/Tests/SubprocessTests/PlatformConformance.swift @@ -23,12 +23,11 @@ import Musl import WinSDK #endif -/// This file defines protocols for platform-specific structs -/// and adds retroactive conformances to them to ensure they all -/// conform to a uniform shape. We opted to keep these protocols -/// in the test target as opposed to making them public APIs -/// because we don't directly use them in public APIs. - +/// This file defines protocols for platform-specific structs and +/// adds retroactive conformances to them to ensure they all conform +/// to a uniform shape. We opted to keep these protocols in the test +/// target as opposed to making them public APIs because we don't +/// directly use them in public APIs. protocol ProcessIdentifierProtocol: Sendable, Hashable, CustomStringConvertible, CustomDebugStringConvertible { #if os(Windows) var value: DWORD { get } diff --git a/Tests/SubprocessTests/TestSupport.swift b/Tests/SubprocessTests/TestSupport.swift index 302a4a5..d5f4cf6 100644 --- a/Tests/SubprocessTests/TestSupport.swift +++ b/Tests/SubprocessTests/TestSupport.swift @@ -64,6 +64,7 @@ internal func directory(_ lhs: String, isSameAs rhs: String) -> Bool { } extension Trait where Self == ConditionTrait { + /// This test requires bash to run (instead of sh) public static var requiresBash: Self { enabled( if: (try? Executable.name("bash").resolveExecutablePath(in: .inherit)) != nil,