From 69920c133f1c4a4e15b7f513265c6df69752cbb0 Mon Sep 17 00:00:00 2001 From: Charles Hu Date: Thu, 28 Aug 2025 17:17:02 -0700 Subject: [PATCH 1/4] Fix documentation throughout the codebase and enforce that all public symbols have documentation. --- .spi.yml | 6 +- .swift-format | 2 +- README.md | 43 ++++---- Sources/Subprocess/API.swift | 101 ++++++++---------- Sources/Subprocess/AsyncBufferSequence.swift | 35 +++++- Sources/Subprocess/Buffer.swift | 4 + Sources/Subprocess/Configuration.swift | 49 ++++++--- Sources/Subprocess/Error.swift | 8 +- Sources/Subprocess/IO/Input.swift | 54 ++++++---- Sources/Subprocess/IO/Output.swift | 64 ++++++----- .../Platforms/Subprocess+Darwin.swift | 11 +- .../Platforms/Subprocess+Linux.swift | 6 +- .../Platforms/Subprocess+Unix.swift | 14 ++- .../Platforms/Subprocess+Windows.swift | 29 +++-- Sources/Subprocess/Result.swift | 10 +- .../Input+Foundation.swift | 14 ++- .../Output+Foundation.swift | 7 +- .../Span+SubprocessFoundation.swift | 2 +- .../SubprocessTests/PlatformConformance.swift | 11 +- Tests/SubprocessTests/TestSupport.swift | 1 + 20 files changed, 289 insertions(+), 182 deletions(-) diff --git a/.spi.yml b/.spi.yml index 8f764503..c6d9883b 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/.swift-format b/.swift-format index 84d3719d..9f4c33e3 100644 --- a/.swift-format +++ b/.swift-format @@ -23,7 +23,7 @@ "prioritizeKeepingFunctionOutputTogether": false, "respectsExistingLineBreaks": true, "rules": { - "AllPublicDeclarationsHaveDocumentation": false, + "AllPublicDeclarationsHaveDocumentation": true, "AlwaysUseLiteralForEmptyCollectionInit": false, "AlwaysUseLowerCamelCase": false, "AmbiguousTrailingClosureOverload": false, diff --git a/README.md b/README.md index a1c78dc7..a7cc3fc8 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 62553091..17ba806e 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 returns a +/// `CollectedResult` containing 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 returns a +/// `CollectedResult` containing 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 f365f718..3cf71185 100644 --- a/Sources/Subprocess/AsyncBufferSequence.swift +++ b/Sources/Subprocess/AsyncBufferSequence.swift @@ -19,8 +19,11 @@ internal import Dispatch #endif +/// A synchronous sequence of `Buffer`s used to stream output from subprocess. public struct AsyncBufferSequence: AsyncSequence, @unchecked Sendable { + /// The failure type for this `AsyncSequence` public typealias Failure = any Swift.Error + /// The element type for this `AsyncSequence` 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 this 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, nor `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 `AsyncSequence` 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 this `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] + /// An asynchronous sequence of `String` which each String representing a line + /// `LineSequence` parses and splits `AsyncBufferSequence` into lines. It is the + /// preferred method to convert `Buffer` to `String` public struct LineSequence: AsyncSequence, Sendable { + /// The element type for this `AsyncSequence` public typealias Element = String private let base: AsyncBufferSequence private let bufferingPolicy: BufferingPolicy + /// The iterator for `LineSequence` 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 } + /// Retrieve the next line public mutating func next() async throws -> String? { func loadBuffer() async throws -> [Encoding.CodeUnit]? { @@ -300,6 +327,7 @@ extension AsyncBufferSequence { } } + /// Creates a iterator for this `LineSequence` public func makeAsyncIterator() -> AsyncIterator { return AsyncIterator( underlyingIterator: self.base.makeAsyncIterator(), @@ -319,6 +347,7 @@ extension AsyncBufferSequence { } extension AsyncBufferSequence.LineSequence { + /// A strategy that handles 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 1a2e8a3a..e43a3bf5 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 indicating whether two `AsyncBufferSequence.Buffer`s 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 4a930408..4578ccab 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,20 @@ 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 given parameters + /// - 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 +127,7 @@ public struct Configuration: Sendable { } extension Configuration: CustomStringConvertible, CustomDebugStringConvertible { + /// A textual representation of this `Configuration`. public var description: String { return """ Configuration( @@ -131,6 +140,7 @@ extension Configuration: CustomStringConvertible, CustomDebugStringConvertible { """ } + /// A textual representation of this `Configuration`. public var debugDescription: String { return """ Configuration( @@ -198,8 +208,8 @@ extension Configuration { // MARK: - Executable -/// `Executable` defines how the executable should -/// be looked up for execution. +/// Executable defines how the executable should be +/// looked up 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 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 given array 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 this `Arguments`. public var description: String { var result: [String] = self.storage.map(\.description) @@ -321,6 +335,7 @@ extension Arguments: CustomStringConvertible, CustomDebugStringConvertible { return result.description } + /// A textual representation of this `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 this `Environment`. public var description: String { switch self.config { case .custom(let customDictionary): @@ -386,6 +402,7 @@ extension Environment: CustomStringConvertible, CustomDebugStringConvertible { } } + /// A textual representation of this `Environment`. public var debugDescription: String { return self.description } @@ -423,12 +440,14 @@ extension Environment: CustomStringConvertible, CustomDebugStringConvertible { // MARK: - TerminationStatus -/// An exit status of a subprocess. +/// An exit status of a subprocess @frozen public enum TerminationStatus: Sendable, Hashable { #if canImport(WinSDK) + /// The type of status code public typealias Code = DWORD #else + /// The type of 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 `TerminationStatus`. public var description: String { switch self { case .exited(let code): @@ -457,6 +477,7 @@ extension TerminationStatus: CustomStringConvertible, CustomDebugStringConvertib } } + /// A textual representation of this `TerminationStatus`. public var debugDescription: String { return self.description } @@ -630,12 +651,11 @@ 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. +/// 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. internal struct IODescriptor: ~Copyable { #if canImport(WinSDK) typealias Descriptor = HANDLE @@ -945,7 +965,10 @@ 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 `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. 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 0a3b8e8d..137d52d4 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 `SubprocessError`. public var description: String { switch self.code.storage { case .spawnFailed: @@ -133,6 +135,7 @@ extension SubprocessError: CustomStringConvertible, CustomDebugStringConvertible } } + /// A textual representation of this `SubprocessError`. 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 of raw value for this UnderlyingError public typealias RawValue = DWORD #else + /// The type of raw value for this UnderlyingError public typealias RawValue = Int32 #endif + /// The platform specific value for this `UnderlyingError` 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 72ae534d..adf9bb94 100644 --- a/Sources/Subprocess/IO/Input.swift +++ b/Sources/Subprocess/IO/Input.swift @@ -33,19 +33,18 @@ 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 the `write(with:)` method that a type must +/// implement to serve as the input source for a subprocess. 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,6 +59,8 @@ public struct NoInput: InputProtocol { ) } + /// Asynchronously write the input to the subprocess using the + /// write file descriptor public func write(with writer: StandardInputWriter) async throws { // noop } @@ -67,11 +68,10 @@ public struct NoInput: InputProtocol { 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,6 +91,8 @@ public struct FileDescriptorInput: InputProtocol { ) } + /// Asynchronously write the input to the subprocess using the + /// write file descriptor public func write(with writer: StandardInputWriter) async throws { // noop } @@ -114,6 +116,8 @@ public struct StringInput< >: InputProtocol { private let string: InputString + /// Asynchronously write the input to the subprocess using 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 +130,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,9 +146,11 @@ 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 used for closure-based run() 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 } @@ -222,29 +230,29 @@ public final actor StandardInputWriter: Sendable { self.diskIO = diskIO } - /// Write an array of UInt8 to the standard input of the subprocess. + /// Write an array of `UInt8` 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 `RawSpan` 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 `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 9c7e3bbe..d4022f6b 100644 --- a/Sources/Subprocess/IO/Output.swift +++ b/Sources/Subprocess/IO/Output.swift @@ -23,11 +23,11 @@ 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. +/// `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. public protocol OutputProtocol: Sendable, ~Copyable { associatedtype OutputType: Sendable @@ -48,13 +48,13 @@ 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 output type for this output option public typealias OutputType = Void internal func createPipe() throws -> CreatedPipe { @@ -74,12 +74,12 @@ 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 `FileDescriptor`. Developers have the option to +/// instruct the `Subprocess` to automatically close the provided +/// `FileDescriptor` after the subprocess is spawned. public struct FileDescriptorOutput: OutputProtocol { + /// The output type for this output option public typealias OutputType = Void private let closeAfterSpawningProcess: Bool @@ -111,13 +111,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 output type for this output option public typealias OutputType = String? + /// The max number of bytes to collect public let maxSize: Int #if SubprocessSpan + /// Create a String from `RawSpawn` public func output(from span: RawSpan) throws -> String? { // FIXME: Span to String var array: [UInt8] = [] @@ -127,6 +128,8 @@ public struct StringOutput: OutputProtocol { return String(decodingBytes: array, as: Encoding.self) } #endif + + /// Create a String from `Sequence` public func output(from buffer: some Sequence) throws -> String? { // FIXME: Span to String let array = Array(buffer) @@ -138,11 +141,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 +185,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 +202,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 +263,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,12 +348,12 @@ 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 `RawSpan` to expected output type public func output(from span: RawSpan) throws { // noop } #endif - + /// Convert the output from `Sequence` to expected output type public func output(from buffer: some Sequence) throws { // noop } diff --git a/Sources/Subprocess/Platforms/Subprocess+Darwin.swift b/Sources/Subprocess/Platforms/Subprocess+Darwin.swift index c1aa76c4..04da1b37 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 + /// Initialize a PlatformOptions with default options 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 this `PlatformOptions`. public var description: String { return self.description(withIndent: 0) } + /// A textual representation of this `PlatformOptions`. 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 `ProcessIdentifier` with the given value 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 this `ProcessIdentifier`. public var description: String { "\(self.value)" } - + /// A textual representation of this `ProcessIdentifier`. public var debugDescription: String { "\(self.value)" } } diff --git a/Sources/Subprocess/Platforms/Subprocess+Linux.swift b/Sources/Subprocess/Platforms/Subprocess+Linux.swift index aee34f87..912f5b24 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 17d5b32b..929efe64 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) + /// 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 this `ProcessIdentifier`. public var description: String { "\(self.value)" } - + /// A textual representation of this `ProcessIdentifier`. 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] = [] - + /// Initialize a `PlatformOptions` with default options. public init() {} } @@ -646,10 +648,12 @@ extension PlatformOptions: CustomStringConvertible, CustomDebugStringConvertible """ } + /// A textual representation of this `PlatformOptions`. public var description: String { return self.description(withIndent: 0) } + /// A textual representation of this `PlatformOptions`. 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 b3526989..75964cf3 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,13 @@ 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 +553,7 @@ public struct PlatformOptions: Sendable { ) throws -> Void )? = nil + /// Initialize PlatformOptions with default options public init() {} } @@ -570,10 +571,12 @@ extension PlatformOptions: CustomStringConvertible, CustomDebugStringConvertible """ } + /// A textual representation of this PlatformOptions. public var description: String { return self.description(withIndent: 0) } + /// A textual representation of this PlatformOptions. public var debugDescription: String { return self.description(withIndent: 0) } @@ -936,11 +939,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 +965,12 @@ public struct ProcessIdentifier: Sendable, Hashable { } extension ProcessIdentifier: CustomStringConvertible, CustomDebugStringConvertible { + /// A textual representation of this `ProcessIdentifier`. public var description: String { return "(processID: \(self.value))" } + /// A textual representation of this `ProcessIdentifier`. public var debugDescription: String { return description } @@ -1416,8 +1423,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 +1450,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 195c8c71..99eea34c 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` closures with the corresponding `TerminationStatus` 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 this `CollectedResult`. 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 textual representation of this `CollectedResult`. 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 this `ExecutionResult`. public var description: String { return """ ExecutionResult( @@ -109,6 +114,7 @@ extension ExecutionResult: CustomStringConvertible where Result: CustomStringCon } extension ExecutionResult: CustomDebugStringConvertible where Result: CustomDebugStringConvertible { + /// A textual representation of this `ExecutionResult`. public var debugDescription: String { return """ ExecutionResult( diff --git a/Sources/Subprocess/SubprocessFoundation/Input+Foundation.swift b/Sources/Subprocess/SubprocessFoundation/Input+Foundation.swift index e7fabb21..a2c5488e 100644 --- a/Sources/Subprocess/SubprocessFoundation/Input+Foundation.swift +++ b/Sources/Subprocess/SubprocessFoundation/Input+Foundation.swift @@ -27,11 +27,13 @@ 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 +/// a given `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 +43,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 +76,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 f5ea4a22..0976259b 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 max number of bytes to collect public let maxSize: Int #if SubprocessSpan + /// Create a Data from `RawSpawn` public func output(from span: RawSpan) throws -> Data { return Data(span) } #else + /// Create a Data from `Sequence` 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 da6785e6..69629f44 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 a7268f62..61f87bf1 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 302a4a5e..d5f4cf6f 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, From 07b22f69eaf9fc9d1d4ad3abc804cf20d60714d4 Mon Sep 17 00:00:00 2001 From: Charles Hu Date: Tue, 2 Sep 2025 13:57:38 -0700 Subject: [PATCH 2/4] Apply documetation suggestions from code review Co-authored-by: Joseph Heck --- Sources/Subprocess/API.swift | 8 +-- Sources/Subprocess/AsyncBufferSequence.swift | 36 ++++++------- Sources/Subprocess/Buffer.swift | 2 +- Sources/Subprocess/Configuration.swift | 50 ++++++++++--------- Sources/Subprocess/Error.swift | 12 ++--- Sources/Subprocess/IO/Input.swift | 44 ++++++++-------- Sources/Subprocess/IO/Output.swift | 40 ++++++++------- .../Platforms/Subprocess+Darwin.swift | 14 +++--- .../Platforms/Subprocess+Unix.swift | 14 +++--- .../Platforms/Subprocess+Windows.swift | 21 ++++---- Sources/Subprocess/Result.swift | 12 ++--- .../Input+Foundation.swift | 11 ++-- .../Output+Foundation.swift | 8 +-- 13 files changed, 142 insertions(+), 130 deletions(-) diff --git a/Sources/Subprocess/API.swift b/Sources/Subprocess/API.swift index 17ba806e..9c898abd 100644 --- a/Sources/Subprocess/API.swift +++ b/Sources/Subprocess/API.swift @@ -17,8 +17,8 @@ // 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. @@ -59,8 +59,8 @@ public func run< } #if SubprocessSpan -/// 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. diff --git a/Sources/Subprocess/AsyncBufferSequence.swift b/Sources/Subprocess/AsyncBufferSequence.swift index 3cf71185..3984d764 100644 --- a/Sources/Subprocess/AsyncBufferSequence.swift +++ b/Sources/Subprocess/AsyncBufferSequence.swift @@ -19,11 +19,11 @@ internal import Dispatch #endif -/// A synchronous sequence of `Buffer`s used to stream output from subprocess. +/// A synchronous sequence of buffers used to stream output from subprocess. public struct AsyncBufferSequence: AsyncSequence, @unchecked Sendable { - /// The failure type for this `AsyncSequence` + /// The failure type for the asynchronous sequence. public typealias Failure = any Swift.Error - /// The element type for this `AsyncSequence` + /// The element type for the asynchronous sequence. public typealias Element = Buffer #if SUBPROCESS_ASYNCIO_DISPATCH @@ -37,7 +37,7 @@ public struct AsyncBufferSequence: AsyncSequence, @unchecked Sendable { /// Iterator for `AsyncBufferSequence`. @_nonSendable public struct Iterator: AsyncIteratorProtocol { - /// The element type for this iterator + /// The element type for the iterator. public typealias Element = Buffer private let diskIO: DiskIO @@ -48,8 +48,8 @@ public struct AsyncBufferSequence: AsyncSequence, @unchecked Sendable { self.buffer = [] } - /// Retrieve the next `Buffer` in the sequence, nor `nil` if - /// the sequence has ended, + /// 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 { @@ -89,12 +89,12 @@ public struct AsyncBufferSequence: AsyncSequence, @unchecked Sendable { self.diskIO = diskIO } - /// Creates a iterator for this `AsyncSequence` + /// Creates a iterator for this asynchronous sequence. public func makeAsyncIterator() -> Iterator { return Iterator(diskIO: self.diskIO) } - /// Creates a line sequence to iterate through this `AsyncBufferSequence` line by line + /// Creates a line sequence to iterate through this `AsyncBufferSequence` line by line. public func lines() -> LineSequence { return LineSequence( underlying: self, @@ -103,7 +103,7 @@ public struct AsyncBufferSequence: AsyncSequence, @unchecked Sendable { ) } - /// Creates a line sequence to iterate through this `AsyncBufferSequence` line by line + /// 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 @@ -118,19 +118,19 @@ public struct AsyncBufferSequence: AsyncSequence, @unchecked Sendable { // MARK: - LineSequence extension AsyncBufferSequence { - /// An asynchronous sequence of `String` which each String representing a line - /// `LineSequence` parses and splits `AsyncBufferSequence` into lines. It is the - /// preferred method to convert `Buffer` to `String` + /// 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 this `AsyncSequence` + /// The element type for the asynchronous sequence. public typealias Element = String private let base: AsyncBufferSequence private let bufferingPolicy: BufferingPolicy - /// The iterator for `LineSequence` + /// The iterator for line sequence. public struct AsyncIterator: AsyncIteratorProtocol { - /// The element type for this Iterator + /// The element type for this Iterator. public typealias Element = String private var source: AsyncBufferSequence.AsyncIterator @@ -152,7 +152,7 @@ extension AsyncBufferSequence { self.bufferingPolicy = bufferingPolicy } - /// Retrieve the next line + /// Retrieves the next line, or returns nil if the sequence ends. public mutating func next() async throws -> String? { func loadBuffer() async throws -> [Encoding.CodeUnit]? { @@ -327,7 +327,7 @@ extension AsyncBufferSequence { } } - /// Creates a iterator for this `LineSequence` + /// Creates a iterator for this line sequence. public func makeAsyncIterator() -> AsyncIterator { return AsyncIterator( underlyingIterator: self.base.makeAsyncIterator(), @@ -347,7 +347,7 @@ extension AsyncBufferSequence { } extension AsyncBufferSequence.LineSequence { - /// A strategy that handles exhaustion of a buffer’s capacity. + /// 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 e43a3bf5..1bf739f1 100644 --- a/Sources/Subprocess/Buffer.swift +++ b/Sources/Subprocess/Buffer.swift @@ -95,7 +95,7 @@ extension AsyncBufferSequence.Buffer { // MARK: - Hashable, Equatable extension AsyncBufferSequence.Buffer: Equatable, Hashable { #if SUBPROCESS_ASYNCIO_DISPATCH - /// Returns a Boolean value indicating whether two `AsyncBufferSequence.Buffer`s are equal. + /// 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 } diff --git a/Sources/Subprocess/Configuration.swift b/Sources/Subprocess/Configuration.swift index 4578ccab..614f6d07 100644 --- a/Sources/Subprocess/Configuration.swift +++ b/Sources/Subprocess/Configuration.swift @@ -41,6 +41,7 @@ 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. public var workingDirectory: FilePath? @@ -48,7 +49,7 @@ public struct Configuration: Sendable { /// running the subprocess. public var platformOptions: PlatformOptions - /// Creates a Configuration with the given parameters + /// Creates a Configuration with the parameters you provide. /// - Parameters: /// - executable: the executable to run /// - arguments: the arguments to pass to the executable. @@ -127,7 +128,7 @@ public struct Configuration: Sendable { } extension Configuration: CustomStringConvertible, CustomDebugStringConvertible { - /// A textual representation of this `Configuration`. + /// A textual representation of this configuration. public var description: String { return """ Configuration( @@ -140,7 +141,7 @@ extension Configuration: CustomStringConvertible, CustomDebugStringConvertible { """ } - /// A textual representation of this `Configuration`. + /// A debug-oriented textual representation of this configuration. public var debugDescription: String { return """ Configuration( @@ -208,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) @@ -241,7 +241,7 @@ public struct Executable: Sendable, Hashable { } extension Executable: CustomStringConvertible, CustomDebugStringConvertible { - /// A textual representation of this `Executable`. + /// A textual representation of this executable. public var description: String { switch storage { case .executable(let executableName): @@ -251,7 +251,7 @@ extension Executable: CustomStringConvertible, CustomDebugStringConvertible { } } - /// A textual representation of this `Executable`. + /// A debug-oriented textual representation of this executable. public var debugDescription: String { switch storage { case .executable(let string): @@ -316,7 +316,7 @@ public struct Arguments: Sendable, ExpressibleByArrayLiteral, Hashable { self.executablePathOverride = nil } } - /// Create an Arguments object using the given array + /// Create an arguments object using the array you provide. public init(_ array: [[UInt8]]) { self.storage = array.map { .rawBytes($0) } self.executablePathOverride = nil @@ -325,7 +325,7 @@ public struct Arguments: Sendable, ExpressibleByArrayLiteral, Hashable { } extension Arguments: CustomStringConvertible, CustomDebugStringConvertible { - /// A textual representation of this `Arguments`. + /// A textual representation of the arguments. public var description: String { var result: [String] = self.storage.map(\.description) @@ -335,7 +335,7 @@ extension Arguments: CustomStringConvertible, CustomDebugStringConvertible { return result.description } - /// A textual representation of this `Arguments`. + /// A debug-oriented textual representation of the arguments. public var debugDescription: String { return self.description } } @@ -379,7 +379,7 @@ public struct Environment: Sendable, Hashable { } extension Environment: CustomStringConvertible, CustomDebugStringConvertible { - /// A textual representation of this `Environment`. + /// A textual representation of the environment. public var description: String { switch self.config { case .custom(let customDictionary): @@ -402,7 +402,7 @@ extension Environment: CustomStringConvertible, CustomDebugStringConvertible { } } - /// A textual representation of this `Environment`. + /// A debug-oriented textual representation of the environment. public var debugDescription: String { return self.description } @@ -440,14 +440,14 @@ extension Environment: CustomStringConvertible, CustomDebugStringConvertible { // MARK: - TerminationStatus -/// An exit status of a subprocess +/// An exit status of a subprocess. @frozen public enum TerminationStatus: Sendable, Hashable { #if canImport(WinSDK) - /// The type of status code + /// The type of the status code. public typealias Code = DWORD #else - /// The type of status code + /// The type of the status code. public typealias Code = CInt #endif @@ -467,7 +467,7 @@ public enum TerminationStatus: Sendable, Hashable { } extension TerminationStatus: CustomStringConvertible, CustomDebugStringConvertible { - /// A textual representation of this `TerminationStatus`. + /// A textual representation of this termination status. public var description: String { switch self { case .exited(let code): @@ -477,7 +477,7 @@ extension TerminationStatus: CustomStringConvertible, CustomDebugStringConvertib } } - /// A textual representation of this `TerminationStatus`. + /// A debug-oriented textual representation of this termination status. public var debugDescription: String { return self.description } @@ -651,9 +651,11 @@ internal func _safelyClose(_ target: _CloseTarget) throws { } } -/// IODescriptor wraps platform-specific FileDescriptor, which is used to establish a +/// 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 IODescriptor, the IODescriptor does not support +/// 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 { @@ -965,10 +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 137d52d4..897e3fec 100644 --- a/Sources/Subprocess/Error.swift +++ b/Sources/Subprocess/Error.swift @@ -53,7 +53,7 @@ extension SubprocessError { case invalidWindowsPath(String) } - /// The numeric value of this `Code` + /// The numeric value of this code. public var value: Int { switch self.storage { case .spawnFailed: @@ -99,7 +99,7 @@ extension SubprocessError { // MARK: - Description extension SubprocessError: CustomStringConvertible, CustomDebugStringConvertible { - /// A textual representation of this `SubprocessError`. + /// A textual representation of this subprocess error. public var description: String { switch self.code.storage { case .spawnFailed: @@ -135,7 +135,7 @@ extension SubprocessError: CustomStringConvertible, CustomDebugStringConvertible } } - /// A textual representation of this `SubprocessError`. + /// A debug-oriented textual representation of this subprocess error. public var debugDescription: String { self.description } } @@ -145,14 +145,14 @@ extension SubprocessError { /// - On Windows, `UnderlyingError` wraps Windows Error code public struct UnderlyingError: Swift.Error, RawRepresentable, Hashable, Sendable { #if os(Windows) - /// The type of raw value for this UnderlyingError + /// The type for the raw value of the underlying error. public typealias RawValue = DWORD #else - /// The type of raw value for this UnderlyingError + /// The type for the raw value of the underlying error. public typealias RawValue = Int32 #endif - /// The platform specific value for this `UnderlyingError` + /// The platform specific value for this underlying error. public let rawValue: RawValue /// Initialize a `UnderlyingError` with given error value public init(rawValue: RawValue) { diff --git a/Sources/Subprocess/IO/Input.swift b/Sources/Subprocess/IO/Input.swift index adf9bb94..17ed666e 100644 --- a/Sources/Subprocess/IO/Input.swift +++ b/Sources/Subprocess/IO/Input.swift @@ -33,8 +33,10 @@ 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 @@ -42,8 +44,9 @@ public protocol InputProtocol: Sendable, ~Copyable { } /// 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, +/// 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 { @@ -59,8 +62,8 @@ public struct NoInput: InputProtocol { ) } - /// Asynchronously write the input to the subprocess using the - /// write file descriptor + /// Asynchronously write the input to the subprocess that uses the + /// write file descriptor. public func write(with writer: StandardInputWriter) async throws { // noop } @@ -68,9 +71,9 @@ public struct NoInput: InputProtocol { 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 +/// 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 @@ -91,8 +94,8 @@ public struct FileDescriptorInput: InputProtocol { ) } - /// Asynchronously write the input to the subprocess using the - /// write file descriptor + /// Asynchronously write the input to the subprocess that use the + /// write file descriptor. public func write(with writer: StandardInputWriter) async throws { // noop } @@ -116,8 +119,8 @@ public struct StringInput< >: InputProtocol { private let string: InputString - /// Asynchronously write the input to the subprocess using the - /// write file descriptor + /// 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 @@ -146,11 +149,11 @@ public struct ArrayInput: InputProtocol { } } -/// A concrete input type used for closure-based run() to write custom input +/// 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 + /// write file descriptor. public func write(with writer: StandardInputWriter) async throws { // noop } @@ -230,7 +233,7 @@ 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: the number of bytes written. public func write( @@ -240,15 +243,16 @@ public final actor StandardInputWriter: Sendable { } #if SubprocessSpan - /// Write a `RawSpan` to the standard input of the subprocess. - /// - Parameter `span`: The span to write - /// - Returns: the number of bytes written + /// 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 diff --git a/Sources/Subprocess/IO/Output.swift b/Sources/Subprocess/IO/Output.swift index d4022f6b..0ba5cd12 100644 --- a/Sources/Subprocess/IO/Output.swift +++ b/Sources/Subprocess/IO/Output.swift @@ -23,9 +23,10 @@ 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 +/// 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 { @@ -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 +/// 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 output type for this output option + /// 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 output type for this output option + /// The type for this output. public typealias OutputType = Void private let closeAfterSpawningProcess: Bool @@ -112,13 +116,13 @@ public struct FileDescriptorOutput: OutputProtocol { /// A concrete `Output` type for subprocesses that collects output /// from the subprocess as `String` with the given encoding. public struct StringOutput: OutputProtocol { - /// The output type for this output option + /// The type for this output. public typealias OutputType = String? - /// The max number of bytes to collect + /// The max number of bytes to collect. public let maxSize: Int #if SubprocessSpan - /// Create a String from `RawSpawn` + /// Create a string from a raw span. public func output(from span: RawSpan) throws -> String? { // FIXME: Span to String var array: [UInt8] = [] @@ -129,7 +133,7 @@ public struct StringOutput: OutputProtocol { } #endif - /// Create a String from `Sequence` + /// 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) @@ -348,12 +352,12 @@ extension OutputProtocol where OutputType == Void { internal func captureOutput(from fileDescriptor: consuming IOChannel?) async throws {} #if SubprocessSpan - /// Convert the output from `RawSpan` to expected output type + /// Convert the output from raw span to expected output type public func output(from span: RawSpan) throws { // noop } #endif - /// Convert the output from `Sequence` to expected output type + /// Convert the output from a sequence of 8-bit unsigned integers to expected output type. public func output(from buffer: some Sequence) throws { // noop } diff --git a/Sources/Subprocess/Platforms/Subprocess+Darwin.swift b/Sources/Subprocess/Platforms/Subprocess+Darwin.swift index 04da1b37..e487f7c6 100644 --- a/Sources/Subprocess/Platforms/Subprocess+Darwin.swift +++ b/Sources/Subprocess/Platforms/Subprocess+Darwin.swift @@ -83,7 +83,7 @@ public struct PlatformOptions: Sendable { ) throws -> Void )? = nil - /// Initialize a PlatformOptions with default options + /// Create platform options with the default values. public init() {} } @@ -145,12 +145,12 @@ extension PlatformOptions: CustomStringConvertible, CustomDebugStringConvertible """ } - /// A textual representation of this `PlatformOptions`. + /// A textual representation of the platform options. public var description: String { return self.description(withIndent: 0) } - /// A textual representation of this `PlatformOptions`. + /// A debug oriented textual representation of the platform options. public var debugDescription: String { return self.description(withIndent: 0) } @@ -484,12 +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 `ProcessIdentifier` with the given value + /// Initialize a process identifier with the value you provide. public init(value: pid_t) { self.value = value } @@ -498,9 +498,9 @@ public struct ProcessIdentifier: Sendable, Hashable { } extension ProcessIdentifier: CustomStringConvertible, CustomDebugStringConvertible { - /// A textual representation of this `ProcessIdentifier`. + /// A textual representation of the process identifier. public var description: String { "\(self.value)" } - /// A textual representation of this `ProcessIdentifier`. + /// A debug-oriented textual representation of the process identifier. public var debugDescription: String { "\(self.value)" } } diff --git a/Sources/Subprocess/Platforms/Subprocess+Unix.swift b/Sources/Subprocess/Platforms/Subprocess+Unix.swift index 929efe64..5553c104 100644 --- a/Sources/Subprocess/Platforms/Subprocess+Unix.swift +++ b/Sources/Subprocess/Platforms/Subprocess+Unix.swift @@ -571,13 +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) - /// Process file Descriptor (pidfd) for the running execution + /// The process file descriptor (pidfd) for the running execution. public let processDescriptor: CInt #else internal let processDescriptor: CInt // not used on other platforms @@ -596,9 +596,9 @@ public struct ProcessIdentifier: Sendable, Hashable { } extension ProcessIdentifier: CustomStringConvertible, CustomDebugStringConvertible { - /// A textual representation of this `ProcessIdentifier`. + /// A textual representation of the process identifier. public var description: String { "\(self.value)" } - /// A textual representation of this `ProcessIdentifier`. + /// A debug-oriented textual representation of the process identifier. public var debugDescription: String { "\(self.value)" } } @@ -630,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] = [] - /// Initialize a `PlatformOptions` with default options. + /// Create platform options with the default values. public init() {} } @@ -648,12 +648,12 @@ extension PlatformOptions: CustomStringConvertible, CustomDebugStringConvertible """ } - /// A textual representation of this `PlatformOptions`. + /// A textual representation of the platform options. public var description: String { return self.description(withIndent: 0) } - /// A textual representation of this `PlatformOptions`. + /// 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 75964cf3..14603aff 100644 --- a/Sources/Subprocess/Platforms/Subprocess+Windows.swift +++ b/Sources/Subprocess/Platforms/Subprocess+Windows.swift @@ -443,8 +443,9 @@ 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. public var password: String @@ -553,7 +554,7 @@ public struct PlatformOptions: Sendable { ) throws -> Void )? = nil - /// Initialize PlatformOptions with default options + /// Create platform options with the default values. public init() {} } @@ -571,12 +572,12 @@ extension PlatformOptions: CustomStringConvertible, CustomDebugStringConvertible """ } - /// A textual representation of this PlatformOptions. + /// A textual representation of the platform options. public var description: String { return self.description(withIndent: 0) } - /// A textual representation of this PlatformOptions. + /// A debug-oriented textual representation of the platform options. public var debugDescription: String { return self.description(withIndent: 0) } @@ -939,13 +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 + /// Process handle for current execution. public nonisolated(unsafe) let processDescriptor: HANDLE - /// Main thread handle for current execution + /// Main thread handle for current execution. public nonisolated(unsafe) let threadHandle: HANDLE internal init(value: DWORD, processDescriptor: HANDLE, threadHandle: HANDLE) { @@ -965,12 +966,12 @@ public struct ProcessIdentifier: Sendable, Hashable { } extension ProcessIdentifier: CustomStringConvertible, CustomDebugStringConvertible { - /// A textual representation of this `ProcessIdentifier`. + /// A textual representation of the process identifier. public var description: String { return "(processID: \(self.value))" } - /// A textual representation of this `ProcessIdentifier`. + /// A debug-oriented textual representation of the process identifier. public var debugDescription: String { return description } diff --git a/Sources/Subprocess/Result.swift b/Sources/Subprocess/Result.swift index 99eea34c..a206bd86 100644 --- a/Sources/Subprocess/Result.swift +++ b/Sources/Subprocess/Result.swift @@ -18,7 +18,7 @@ // MARK: - Result /// A simple wrapper around the generic result returned by the -/// `run` closures with the corresponding `TerminationStatus` of +/// `run` closure with the corresponding termination status of /// the child process. public struct ExecutionResult { /// The termination status of the child process @@ -42,7 +42,7 @@ 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 + /// 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 @@ -68,7 +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 this `CollectedResult`. + /// A textual representation of the collected result. public var description: String { return """ CollectedResult( @@ -83,7 +83,7 @@ where Output.OutputType: CustomStringConvertible, Error.OutputType: CustomString extension CollectedResult: CustomDebugStringConvertible where Output.OutputType: CustomDebugStringConvertible, Error.OutputType: CustomDebugStringConvertible { - /// A textual representation of this `CollectedResult`. + /// A debug-oriented textual representation of the collected result. public var debugDescription: String { return """ CollectedResult( @@ -102,7 +102,7 @@ extension ExecutionResult: Equatable where Result: Equatable {} extension ExecutionResult: Hashable where Result: Hashable {} extension ExecutionResult: CustomStringConvertible where Result: CustomStringConvertible { - /// A textual representation of this `ExecutionResult`. + /// A textual representation of the execution result. public var description: String { return """ ExecutionResult( @@ -114,7 +114,7 @@ extension ExecutionResult: CustomStringConvertible where Result: CustomStringCon } extension ExecutionResult: CustomDebugStringConvertible where Result: CustomDebugStringConvertible { - /// A textual representation of this `ExecutionResult`. + /// 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 a2c5488e..e587c897 100644 --- a/Sources/Subprocess/SubprocessFoundation/Input+Foundation.swift +++ b/Sources/Subprocess/SubprocessFoundation/Input+Foundation.swift @@ -27,13 +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 + /// write file descriptor. public func write(with writer: StandardInputWriter) async throws { _ = try await writer.write(self.data) } @@ -44,14 +43,14 @@ public struct DataInput: InputProtocol { } /// A concrete input type for subprocesses that accepts input from -/// a specified sequence of `Data`. +/// 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 + /// write file descriptor. public func write(with writer: StandardInputWriter) async throws { var buffer = Data() for chunk in self.sequence { @@ -77,7 +76,7 @@ public struct DataAsyncSequenceInput< } /// Asynchronously write the input to the subprocess using the - /// write file descriptor + /// 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 0976259b..ed989389 100644 --- a/Sources/Subprocess/SubprocessFoundation/Output+Foundation.swift +++ b/Sources/Subprocess/SubprocessFoundation/Output+Foundation.swift @@ -20,20 +20,20 @@ import FoundationEssentials #endif /// A concrete `Output` type for subprocesses that collects output -/// from the subprocess as `Data`.` +/// from the subprocess as data. public struct DataOutput: OutputProtocol { /// The output type for this output option public typealias OutputType = Data - /// The max number of bytes to collect + /// The maximum number of bytes to collect. public let maxSize: Int #if SubprocessSpan - /// Create a Data from `RawSpawn` + /// Create data from a raw span. public func output(from span: RawSpan) throws -> Data { return Data(span) } #else - /// Create a Data from `Sequence` + /// Create a data from sequence of 8-bit unsigned integers. public func output(from buffer: some Sequence) throws -> Data { return Data(buffer) } From 7b577a775be552997fc068a3036e2e1d747f48c0 Mon Sep 17 00:00:00 2001 From: Charles Hu Date: Tue, 2 Sep 2025 13:59:32 -0700 Subject: [PATCH 3/4] Disable AllPublicDeclarationsHaveDocumentation because it's too excessive --- .swift-format | 2 +- Sources/Subprocess/Configuration.swift | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.swift-format b/.swift-format index 9f4c33e3..84d3719d 100644 --- a/.swift-format +++ b/.swift-format @@ -23,7 +23,7 @@ "prioritizeKeepingFunctionOutputTogether": false, "respectsExistingLineBreaks": true, "rules": { - "AllPublicDeclarationsHaveDocumentation": true, + "AllPublicDeclarationsHaveDocumentation": false, "AlwaysUseLiteralForEmptyCollectionInit": false, "AlwaysUseLowerCamelCase": false, "AmbiguousTrailingClosureOverload": false, diff --git a/Sources/Subprocess/Configuration.swift b/Sources/Subprocess/Configuration.swift index 614f6d07..a6fcff7e 100644 --- a/Sources/Subprocess/Configuration.swift +++ b/Sources/Subprocess/Configuration.swift @@ -653,7 +653,7 @@ internal func _safelyClose(_ target: _CloseTarget) throws { /// 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. +/// 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 @@ -970,7 +970,7 @@ extension Optional where Wrapped == String { /// 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`. +/// 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( From cdb9e2152c1ad02ebe71197b54883da11f189aa6 Mon Sep 17 00:00:00 2001 From: Charles Hu Date: Tue, 2 Sep 2025 20:43:50 -0700 Subject: [PATCH 4/4] Use fatalError() for no-op methods --- Sources/Subprocess/IO/Input.swift | 6 +++--- Sources/Subprocess/IO/Output.swift | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Sources/Subprocess/IO/Input.swift b/Sources/Subprocess/IO/Input.swift index 17ed666e..3fdccc3e 100644 --- a/Sources/Subprocess/IO/Input.swift +++ b/Sources/Subprocess/IO/Input.swift @@ -65,7 +65,7 @@ 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() {} @@ -97,7 +97,7 @@ 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( @@ -155,7 +155,7 @@ 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() {} diff --git a/Sources/Subprocess/IO/Output.swift b/Sources/Subprocess/IO/Output.swift index 0ba5cd12..a7cb3720 100644 --- a/Sources/Subprocess/IO/Output.swift +++ b/Sources/Subprocess/IO/Output.swift @@ -25,7 +25,7 @@ internal import Dispatch /// 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. @@ -52,7 +52,7 @@ extension OutputProtocol { /// 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`. @@ -79,7 +79,7 @@ public struct DiscardedOutput: OutputProtocol { /// 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 { @@ -354,12 +354,12 @@ extension OutputProtocol where OutputType == Void { #if SubprocessSpan /// 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)") } }