diff --git a/Sources/Subprocess/API.swift b/Sources/Subprocess/API.swift index 042a0aef..ffe8b148 100644 --- a/Sources/Subprocess/API.swift +++ b/Sources/Subprocess/API.swift @@ -30,6 +30,10 @@ public import SystemPackage /// - output: The method to use for redirecting standard output. /// - error: The method to use for redirecting standard error. /// - Returns: An ``ExecutionResult`` that contains the result of the run. +/// - Throws: A ``SubprocessError`` if the subprocess can't be launched, for +/// example when the executable isn't found, or if the collected output +/// exceeds the limit you set. A non-zero exit code is a normal result, not +/// a thrown error; inspect ``ExecutionResult/terminationStatus`` instead. public func run< Input: InputProtocol, Output: OutputProtocol, @@ -72,6 +76,10 @@ public func run< /// - output: The method to use for redirecting standard output. /// - error: The method to use for redirecting standard error. /// - Returns: An ``ExecutionResult`` that contains the result of the run. +/// - Throws: A ``SubprocessError`` if the subprocess can't be launched, for +/// example when the executable isn't found, or if the collected output +/// exceeds the limit you set. A non-zero exit code is a normal result, not +/// a thrown error; inspect ``ExecutionResult/terminationStatus`` instead. public func run< InputElement: BitwiseCopyable, Output: OutputProtocol, @@ -126,6 +134,10 @@ public func run< /// Don't let the execution value escape the closure. /// - Returns: An ``ExecutionResult`` that contains the closure's return value and /// the termination status of the subprocess. +/// - Throws: A ``SubprocessError`` if the subprocess can't be launched, for +/// example when the executable isn't found, or rethrows any error your +/// `body` closure throws. A non-zero exit code is a normal result, not a +/// thrown error; inspect ``ExecutionResult/terminationStatus`` instead. public func run< Result: ~Copyable, Input: InputProtocol, @@ -170,6 +182,10 @@ public func run< /// - output: The method to use for redirecting standard output. /// - error: The method to use for redirecting standard error. /// - Returns: An ``ExecutionResult`` that contains the result of the run. +/// - Throws: A ``SubprocessError`` if the subprocess can't be launched, for +/// example when the executable isn't found, or if the collected output +/// exceeds the limit you set. A non-zero exit code is a normal result, not +/// a thrown error; inspect ``ExecutionResult/terminationStatus`` instead. public func run< InputElement: BitwiseCopyable, Output: OutputProtocol, @@ -200,6 +216,10 @@ public func run< /// - output: The method to use for redirecting standard output. /// - error: The method to use for redirecting standard error. /// - Returns: An ``ExecutionResult`` that contains the result of the run. +/// - Throws: A ``SubprocessError`` if the subprocess can't be launched, for +/// example when the executable isn't found, or if the collected output +/// exceeds the limit you set. A non-zero exit code is a normal result, not +/// a thrown error; inspect ``ExecutionResult/terminationStatus`` instead. public func run< Input: InputProtocol, Output: OutputProtocol, @@ -234,6 +254,10 @@ public func run< /// Don't let the execution value escape the closure. /// - Returns: An ``ExecutionResult`` that contains the closure's return value and /// the termination status of the subprocess. +/// - Throws: A ``SubprocessError`` if the subprocess can't be launched, for +/// example when the executable isn't found, or rethrows any error your +/// `body` closure throws. A non-zero exit code is a normal result, not a +/// thrown error; inspect ``ExecutionResult/terminationStatus`` instead. public func run< Result: ~Copyable, Input: InputProtocol, diff --git a/Sources/Subprocess/Documentation.docc/GettingStarted.md b/Sources/Subprocess/Documentation.docc/GettingStarted.md new file mode 100644 index 00000000..b05b5a7a --- /dev/null +++ b/Sources/Subprocess/Documentation.docc/GettingStarted.md @@ -0,0 +1,206 @@ +# Getting started with Subprocess + +Run a command, collect its output, and see how input, output, +and process lifetime fit together. + +## Overview + +Running a subprocess takes the input you provide, and you control how its output and error are collected or streamed. +Depending on the command, a subprocess might produce output, error output, both, or neither. +Subprocess lets you handle each of these cases independently. + +The function that launches a subprocess, `run`, comes in two forms: +* The *collecting* form waits for the subprocess to finish and provides the result. +* The *streaming* form lets you read output while the subprocess runs. + +You choose between them depending on whether you pass a trailing closure. +This article covers the collecting form; for streaming, see . + +Subprocess encloses the lifetime of the process you run within the `run` call. +When `run` returns, the process is complete and cleaned up. + +### Run a command and read its output + +To run a subprocess, import the `Subprocess` framework and call a flavor of the `run` function, identifying what to run, any arguments to use, and how to handle its output. +You `await` this call, which means that the subprocess execution is finished when the code resumes. +The following snippet shows an example of executing the `ls -la` command as a subprocess: + +```swift +import Subprocess + +let result = try await run( + .name("ls"), + arguments: ["-la"], + output: .string(limit: 4096) +) +print(result.standardOutput) +``` + +The preceding example uses ``run(_:arguments:environment:workingDirectory:platformOptions:input:output:error:)-(_,_,_,_,_,Input,_,_)``: + +Take note of the following behaviors in this example: + +Take note of the following behaviors in this example: + +- The `output` parameter is required. + The example uses `.string(limit:)` to collect standard output as a string (`String`) no longer than 4096 bytes. +- The `limit` parameter is a byte count, not a character count, and Subprocess applies it before decoding. + There's no default limit — you always specify one, because a reasonable ceiling depends entirely on the command you run, anywhere from a few bytes to many megabytes. + Treat the limit as a ceiling, not a truncation: output that fits today can grow past it tomorrow, and when it does, `run` throws ``SubprocessError`` rather than silently handing you a partial result you might mistake for the whole. +- The `input` parameter defaults to `.none` and the `error` parameter defaults to `.discarded`. + +### Name the command and its arguments + +The first parameter to `run` is an ``Executable`` that you can specify in one of two ways: + +- ``Executable/name(_:)`` — `.name("ls")` looks the command up using the `PATH` + environment variable, the way a shell finds it. +- ``Executable/path(_:)`` — `.path("/bin/ls")` runs the command at an exact + location and skips the search. + +Arguments are a separate ``Arguments`` value that you can write as an array literal. +Each element is one argument, passed to the command exactly as written: + +```swift +let result = try await run( + .name("git"), + arguments: ["commit", "-m", "a message with spaces"], + output: .string(limit: 16 * 1024) +) +``` + +Subprocess doesn't run a shell, so there's no quoting to get right and no +shell expansion or injection to guard against. +The string `"a message with spaces"` arrives as a single argument, with spaces. + +### Set input, output, and error independently + +Each of the `input`, `output`, and `error` parameters is independent, and each has its own type. +Input conforms to ``InputProtocol``, and output and error conform to ``OutputProtocol``. +In the following example, `run` collects standard output as a large string and standard error as a smaller one: + +```swift +let result = try await run( + .name("swift"), + arguments: ["build"], + output: .string(limit: 2 * 1024 * 1024), + error: .string(limit: 512 * 1024) +) +``` + +To keep the output and error together instead — the equivalent of using `2>&1` in a shell — pass ``ErrorOutputProtocol/combinedWithOutput`` as the error. +This merges standard error into standard output. +Only the `error:` line changes: + +```swift +let result = try await run( + .name("swift"), + arguments: ["build"], + output: .string(limit: 2 * 1024 * 1024), + error: .combinedWithOutput +) +``` + +The examples in this article wait for the process to complete and collect the output. +This approach fits a short-lived subprocess whose output fits in memory, when you want the result after it exits. + +The alternative, when a subprocess is long-running or its output is large or open-ended, and you want to act on output before it exits, is to use ``run(_:arguments:environment:workingDirectory:platformOptions:input:output:error:body:)``. +This `run` takes a trailing closure in which you iterate over the output as it arrives. +For more detail on how to use streaming output, see . + +### Read what you get back + +A collecting `run` returns an ``ExecutionResult`` that carries everything the finished process produced: + +| Property | Type | What it holds | +| --- | --- | --- | +| `standardOutput` | Matches your `output:` choice | The collected standard output | +| `standardError` | Matches your `error:` choice | The collected standard error | +| `terminationStatus` | ``TerminationStatus`` | How the process ended | +| `processIdentifier` | ``ProcessIdentifier`` | The identifier of the process that ran | + +The types of `standardOutput` and `standardError` follow the collection method you chose for each stream: + +| Collection method | Result type | +| --- | --- | +| `.string(limit:)` | `String` | +| `.bytes(limit:)` | `[UInt8]` | +| ``OutputProtocol/discarded`` | `Void` (nothing collected) | + +Both `.string(limit:)` and `.bytes(limit:)` take a required `limit` — a byte count with no default, applied before decoding. +Use `.bytes(limit:)` when the output isn't text, such as an image or an archive. + +In the common case, a command runs, succeeds, and hands you its output, which you read from the result: + +```swift +let result = try await run( + .name("git"), + arguments: ["rev-parse", "HEAD"], + output: .string(limit: 4096) +) +print(result.standardOutput) // the commit hash +``` + +### Detect failure + +Around a subprocess, the word “error” pulls in three directions. +Keeping them apart is the difference between handling a real problem and misreading a healthy result as a broken one. +Throughout the Subprocess documentation: + +- A **Swift error** is a value thrown out of `run` that you handle with `do`/`catch`. Subprocess reserves this for problems it can't express as a result: a broken setup or environment, or a body closure of your own that throws. +- A **command failure** is a subprocess that ran to completion but exited with a non-zero code. This is reported by the ``ExecutionResult/terminationStatus`` of the ``ExecutionResult``, not a Swift error. +- **Standard error output** is bytes the command wrote to its standard error stream. Many programs write progress or diagnostics there while succeeding, so it's ordinary output you collect through the `error:` parameter — not a signal that anything went wrong. + +`run` doesn't throw a Swift error when a command exits with a non-zero code. +A command that ran and failed is a normal result; inspect its ``TerminationStatus`` to see how it ended: + +```swift +switch result.terminationStatus { +case .exited(0): + print("succeeded") +case .exited(let code): + print("exited with code \(code)") +case .signaled(let signal): + print("stopped by signal \(signal)") +} +``` + +For a simple pass-or-fail check, ``TerminationStatus/isSuccess`` collapses that to a Boolean. +The `signaled` case exists only on platforms that report signals; on Windows, every command ends as `exited`. + +`run` throws a Swift error in only two situations: + +- It throws a ``SubprocessError`` for a problem with the setup or environment — for example, when ``Executable/name(_:)`` finds nothing in `PATH`, or when the collected output exceeds the `limit` you specified. +- It rethrows any error your own body closure throws, in the streaming form described in . + +Because a command failure isn't a Swift error, you handle the two concerns in different places — `do`/`catch` for a process that couldn't run, and ``TerminationStatus/isSuccess`` for one that ran and failed: + +```swift +do { + let result = try await run( + .name("git"), + arguments: ["status"], + output: .string(limit: 64 * 1024) + ) + if result.terminationStatus.isSuccess { + print(result.standardOutput) + } else { + print("git reported failure") + } +} catch { + // A Swift error: git wasn't found, or the output exceeded the limit. + print("couldn't run git: \(error)") +} +``` + +> Note: A non-zero exit never surfaces as a Swift error, and neither does output on standard error. +> To run a `catch` block when a command “fails,” check ``TerminationStatus/isSuccess`` yourself and throw from your own code. + +### Understand the process lifetime + +A subprocess is scoped to its `run` call. +`run` returns only after the process has exited and its output is collected, +so by the time you hold an ``ExecutionResult``, no live process remains. + +Processing streaming input and output, as described in , keeps this guarantee. +Its trailing closure runs while the process is alive, and `run` returns only after the process exits. diff --git a/Sources/Subprocess/Documentation.docc/StreamingAndInput.md b/Sources/Subprocess/Documentation.docc/StreamingAndInput.md new file mode 100644 index 00000000..7de9ca7e --- /dev/null +++ b/Sources/Subprocess/Documentation.docc/StreamingAndInput.md @@ -0,0 +1,259 @@ +# Streaming output and providing input + +Stream the output of a subprocess as it arrives and feed it input, while following +the one rule that keeps pipes from deadlocking. + +## Overview + +The function that launches a subprocess, `run`, comes in two forms. +The collecting form waits for the subprocess to exit and hands you all its output at once. +The streaming form takes a trailing closure so you can process output while the subprocess runs. +You choose between them depending on whether you pass that closure. +This article covers the streaming form; for collecting, see . + +Reach for streaming when, for example: + +- Reading each line of a long-running subprocess as it becomes available. +- Handling output that's too large to hold in memory. +- Providing input to the subprocess while it runs. + +The streaming API gives you live streams, which come with one rule: **don't block pipes.** + +> Warning: A subprocess can't finish while a pipe it depends on is stuck. +> +> Drain every output stream you open — concurrently, when you open more than one — and close standard input once you're done writing to it. + +### Stream output as it arrives + +To stream, pass ``SequenceOutput/sequence`` as the output with ``run(_:arguments:environment:workingDirectory:platformOptions:input:output:error:body:)``. + +Inside the trailing closure, the +``Execution`` value provides the output as an asynchronous sequence. +The following example illustrates reading the output line by line: + +```swift +import Subprocess + +_ = try await run( + .name("swift"), + arguments: ["build"], + input: .none, + output: .sequence, + error: .discarded +) { execution in + for try await line in execution.standardOutput.strings() { + print(line) + } +} +``` + +The preceding example has no default parameters. +Unlike the collecting `run`, this streaming form requires you to state `input`, `output`, and `error` on every call. +The ``SubprocessOutputSequence/strings(separatedBy:bufferingPolicy:)`` method yields one `String` per line, +with the line separator removed. +It recognizes the common separators, so you get text without splitting bytes yourself. +This call opens a single output stream and drains it in the loop, while the input pipe is `.none` and the error pipe is ``OutputProtocol/discarded``, so it follows the rule to drain every output pipe. + +### Understand the closure scope + +The trailing closure on `run` is how the library guarantees that the subprocess lives only +as long as the `run` call. +The closure runs concurrently with the live subprocess: while your code reads output inside the body, the process is still running. +The function `run` returns only after the body returns and the process exits. +Because of that, the `execution` value, its streams, and the input writer are valid only inside the body. + +### Drain every stream, concurrently + +Standard output and standard error are separate pipes. +A process writes to both, and each pipe holds only so much data before a write to it blocks — on Linux, for example, about 64 KB. +If you read one pipe to completion while never reading the other, the process eventually blocks +writing to the pipe you ignored — so it never exits, and `run` never returns. +This isn't a rare edge case: a command whose output fits the buffer today can cross it as that output grows, and then it hangs with no change to your code. +Reading the two pipes one after another has the same problem, because “after” never arrives when the first pipe blocks. + +Read both streams concurrently by giving each its own child task +so neither waits on the other: + +```swift +_ = try await run( + .name("swift"), + arguments: ["build"], + input: .none, + output: .sequence, + error: .sequence +) { execution in + try await withThrowingTaskGroup(of: Void.self) { group in + group.addTask { + for try await line in execution.standardOutput.strings() { + print("out:", line) + } + } + group.addTask { + for try await line in execution.standardError.strings() { + print("err:", line) + } + } + try await group.waitForAll() + } +} +``` + +If you've used Python's `subprocess` and `communicate()` after a program +hung on `wait()` with `PIPE`, this is the same kind of issue and the same shape of fix: +consume every pipe concurrently instead of in sequence. + +### Provide input + +Input has three tiers of choices; reach for the simplest one that does the job: + +- For no input, ``InputProtocol/none`` (the default) points standard input at the null device. +- For a value you already have, pass ``InputProtocol/string(_:)``, ``InputProtocol/array(_:)``, or ``InputProtocol/data(_:)``. +- For input you produce over time, pass ``InputProtocol/inputWriter`` and write from the closure. + +When the input is a value in hand, pass it directly and use the collected form. +No closure is needed, and the `error` parameter defaults to `.discarded`. +Use ``InputProtocol/string(_:)``, ``InputProtocol/array(_:)`` for `[UInt8]`, or ``InputProtocol/data(_:)`` for Foundation `Data`. +The following example provides a static string as input: + +```swift +let result = try await run( + .name("wc"), + arguments: ["-l"], + input: .string("one\ntwo\n"), + output: .string(limit: 4096) +) +print(result.standardOutput) // the line count +``` + +To write input as the process runs, pass +``InputProtocol/inputWriter`` and use the closure form of `run`: ``run(_:arguments:environment:workingDirectory:platformOptions:input:output:error:body:)``. +The ``Execution`` that the closure provides gives you a ``StandardInputWriter``. +Each `write` sends its bytes immediately, and the return value is the number of bytes written. +You can discard the count with `_ =` if you don't need it: + +```swift +let result = try await run( + .name("cat"), + input: .inputWriter, + output: .string(limit: 4096), + error: .discarded +) { execution in + let writer = execution.standardInputWriter + _ = try await writer.write("one\ntwo\nthree\n") + try await writer.finish() +} +print(result.standardOutput) +``` + +When you use this form to write input, call ``StandardInputWriter/finish()`` after your last write. + +Calling `finish` closes the input stream so the subprocess sees end-of-file. +Many programs don't produce their final output — or exit at all — until they do. +A missing `finish()` may show up as a hung process. +`run` calls it for you when the body closure returns, +or call it yourself when you need to close the input stream sooner, +such as when you await the process's full output inside the body. + +Don't hold on to the writer past the body. +Once the closure returns, `run` finishes it, and a later write throws a ``SubprocessError``. + +### Write and read at the same time + +Some programs interleave both input and output, such as the encoding tool `base64`. +They read some input, emit some output, and repeat. + +Writing all the input before you start reading can fill the output pipe and block the subprocess mid-write, causing a deadlock. +To avoid that, write and read with concurrent tasks: + +```swift +_ = try await run( + .name("base64"), + input: .inputWriter, + output: .sequence, + error: .discarded +) { execution in + let writer = execution.standardInputWriter + try await withThrowingTaskGroup(of: Void.self) { group in + group.addTask { + _ = try await writer.write("Hello, world.\n") + try await writer.finish() + } + group.addTask { + for try await line in execution.standardOutput.strings() { + print(line) + } + } + try await group.waitForAll() + } +} +``` + +The same structure can chain two subprocesses, sending the stream of output from one subprocess to the input of another. +This is the equivalent of `ls | sort` in a shell. +To chain together the subprocesses, invoke the second subprocess with `run` using a closure, and inside it run the first, +forwarding each output buffer's bytes to the second's writer while a sibling task drains +the second's output: + +```swift +_ = try await run( + .name("sort"), + input: .inputWriter, + output: .sequence, + error: .discarded +) { sort in + let writer = sort.standardInputWriter + try await withThrowingTaskGroup(of: Void.self) { group in + group.addTask { + _ = try await run( + .name("ls"), + input: .none, + output: .sequence, + error: .discarded + ) { ls in + for try await chunk in ls.standardOutput { + _ = try await writer.write(chunk.bytes) + } + } + try await writer.finish() + } + group.addTask { + for try await line in sort.standardOutput.strings() { + print(line) + } + } + try await group.waitForAll() + } +} +``` + +Iterating `standardOutput` yields ``SubprocessOutputSequence/Buffer`` values rather than decoded text. +Passing each buffer's ``SubprocessOutputSequence/Buffer/bytes`` — a `RawSpan` view — to the writer forwards the data with no intermediate array copy. + +Both subprocesses are alive at once — the outer `run` for `sort` doesn't return +until its body does — and every pipe has a task draining it. + +### Use files and inherited standard I/O + +Not every stream has to flow through your process. +You can point the input or output of a subprocess at a file descriptor directly with +``FileDescriptorInput/fileDescriptor(_:closeAfterSpawningProcess:)`` and +``FileDescriptorOutput/fileDescriptor(_:closeAfterSpawningProcess:)``, +which is efficient for large amounts of data. +When you use a file descriptor, the bytes never pass through your code. + +You can also let the subprocess share your process's own terminal. For input, +that's `.standardInput`; for output and error, it's `.currentStandardOutput` and +`.currentStandardError`: + +```swift +_ = try await run( + .name("less"), + arguments: ["Package.swift"], + input: .standardInput, + output: .currentStandardOutput, + error: .currentStandardError +) +``` + +Note the naming: input inherits with `.standardInput`, while output and error +inherit with the `current`-prefixed names. diff --git a/Sources/Subprocess/Documentation.docc/Subprocess.md b/Sources/Subprocess/Documentation.docc/Subprocess.md index f0858013..76894d67 100644 --- a/Sources/Subprocess/Documentation.docc/Subprocess.md +++ b/Sources/Subprocess/Documentation.docc/Subprocess.md @@ -18,49 +18,37 @@ let result = try await run(.name("ls"), output: .string(limit: 4096)) print(result.processIdentifier) // e.g. 1234 print(result.terminationStatus) // e.g. exited(0) -print(result.standardOutput) // e.g. Optional("LICENSE\nPackage.swift\n...") +print(result.standardOutput) // e.g. "LICENSE\nPackage.swift\n..." ``` You can run an executable directly, supplying its ``Arguments``, ``Environment``, -and working directory inline, or build a reusable ``Configuration`` and run that. -When you need to interact with the process while it runs, use the overloads that -take a trailing closure: the closure receives an ``Execution`` value you use to -write to standard input, stream standard output and standard error, and signal -or tear down the process. - -Input, output, and errors are three independent parameters, so a single call -can mix collecting and streaming. Each conforms to ``InputProtocol``, -``OutputProtocol``, or ``ErrorOutputProtocol``, respectively. - -You can read input from a string, an array, a file descriptor, or, in the -closure-based API, from the body itself through ``StandardInputWriter``. You -can collect output as a `String`, a `[UInt8]` array, or `Data`, send it to a -file descriptor, or stream it. Passing ``CombinedErrorOutput`` for the error -parameter merges standard error into standard output, the equivalent of the -shell's `2>&1`. - -Streamed output arrives as a ``SubprocessOutputSequence``, an asynchronous -sequence of buffers you can iterate directly or read line by line. The -``Execution``, ``SubprocessOutputSequence``, and ``StandardInputWriter`` values -are valid only for the duration of the body closure; don't let them escape it. - -If the task running a subprocess is canceled, Subprocess can run a configurable -teardown sequence (for example, a graceful shutdown followed by a forced termination) -before the call returns. You describe that sequence with ``TeardownStep`` values, -and you can trigger one yourself from the body closure. - -Platform-specific settings live on ``PlatformOptions``: user and group -identifiers and session behavior on Unix, quality of service on Darwin, and -console and window behavior on Windows. The `SubprocessFoundation` trait is -enabled by default and adds the `Data`-based input and output types, which -import Foundation (the system Foundation on Darwin and swift-foundation's -`FoundationEssentials` elsewhere). Disable the trait to build without that -dependency. +and working directory inline, or build a reusable ``Configuration`` to run repeatedly. + +To get more detail on how to use `run` to invoke a command, wait for it to finish, then provide everything it produced, read . +If you're invoking a long-running command, a command that produces more output than fits into memory, or you want to process what that command produces while it's still running, read . + +Subprocess also handles the concerns that surround a running process: + +- term Graceful teardown: When the task running a subprocess is canceled, + Subprocess can run a configurable teardown sequence — for example, a graceful + shutdown followed by a forced termination — before the call returns. You + describe it with ``TeardownStep`` values. +- term Platform options: Platform-specific settings live on ``PlatformOptions``: + user, group, and session behavior on Unix, quality of service on Darwin, and + console and window behavior on Windows. +- term Foundation integration: The `SubprocessFoundation` trait, enabled by + default, adds `Data`-based input and output. It imports Foundation — the + system Foundation on Darwin, and swift-foundation's `FoundationEssentials` + elsewhere. Disable the trait to build without that dependency. ## Topics -### Running a subprocess +### Essentials + +- +- +### Running a subprocess - ``run(_:arguments:environment:workingDirectory:platformOptions:input:output:error:)-(_,_,_,_,_,Input,_,_)`` - ``run(_:arguments:environment:workingDirectory:platformOptions:input:output:error:)-(_,_,_,_,_,Span,_,_)`` - ``run(_:arguments:environment:workingDirectory:platformOptions:input:output:error:body:)`` diff --git a/Sources/Subprocess/IO/Input.swift b/Sources/Subprocess/IO/Input.swift index 22895ed2..dc72a88b 100644 --- a/Sources/Subprocess/IO/Input.swift +++ b/Sources/Subprocess/IO/Input.swift @@ -147,7 +147,7 @@ public struct StringInput< } } -/// An input type that reads from a `[UInt8]` array. +/// An input type that reads from an array of bytes. public struct ArrayInput: InputProtocol { private let array: [UInt8] @@ -161,11 +161,11 @@ public struct ArrayInput: InputProtocol { } /// An input type that lets the body closure write to the subprocess's -/// standard input through ``Execution/standardInputWriter``. +/// standard input while the process runs. /// -/// Use ``InputProtocol/inputWriter`` to create a value of this type when you -/// call a `run` function that takes a body closure. The closure writes the -/// input through ``Execution/standardInputWriter`` and calls +/// Create a value of this type with ``InputProtocol/inputWriter``, then pass it +/// to a `run` function that takes a body closure. Inside the closure, write your +/// input through ``Execution/standardInputWriter`` and call /// ``StandardInputWriter/finish()`` to signal end-of-file. public struct CustomWriteInput: InputProtocol { /// Writes no input to the subprocess. diff --git a/Sources/Subprocess/IO/Output.swift b/Sources/Subprocess/IO/Output.swift index 6c6f1030..52beca2d 100644 --- a/Sources/Subprocess/IO/Output.swift +++ b/Sources/Subprocess/IO/Output.swift @@ -107,7 +107,9 @@ public struct FileDescriptorOutput: OutputProtocol, ErrorOutputProtocol { } } -/// An output type that collects the subprocess's output as decoded text using the encoding you provide. +/// An output type that collects the subprocess's output as a string. +/// +/// Specify the encoding to use when decoding the output; the default is UTF-8. public struct StringOutput: OutputProtocol, ErrorOutputProtocol { public typealias OutputType = String public let maxSize: Int @@ -179,8 +181,8 @@ public struct BytesOutput: OutputProtocol, ErrorOutputProtocol { } } -/// An output type that streams the subprocess's output through the body -/// closure as an asynchronous sequence of buffers. +/// An output type that streams the subprocess's output as an asynchronous +/// sequence of buffers. /// /// Use ``OutputProtocol/sequence`` to create a value of this type when you /// call a `run` function that takes a body closure. The closure reads the @@ -233,7 +235,7 @@ extension OutputProtocol where Self == FileDescriptorOutput { extension OutputProtocol where Self == StringOutput { /// Creates a subprocess output that collects output as a UTF-8 string. /// - /// The subprocess throws an error if the process + /// `run` throws a ``SubprocessError`` if the process /// produces more bytes than `limit`. public static func string(limit: Int) -> Self { return .init(limit: limit, encoding: UTF8.self) @@ -244,7 +246,7 @@ extension OutputProtocol { /// Creates a subprocess output that collects output as /// a string using the encoding you provide, up to `limit` bytes. /// - /// The subprocess throws an error if the process + /// `run` throws a ``SubprocessError`` if the process /// produces more bytes than `limit`. public static func string( limit: Int, @@ -258,7 +260,7 @@ extension OutputProtocol where Self == BytesOutput { /// Creates a subprocess output that collects output as bytes, /// up to `limit` bytes. /// - /// The subprocess throws an error if the process + /// `run` throws a ``SubprocessError`` if the process /// produces more bytes than `limit`. public static func bytes(limit: Int) -> Self { return .init(limit: limit) diff --git a/Sources/Subprocess/SubprocessFoundation/Output+Foundation.swift b/Sources/Subprocess/SubprocessFoundation/Output+Foundation.swift index 1a1ae9a8..648c945b 100644 --- a/Sources/Subprocess/SubprocessFoundation/Output+Foundation.swift +++ b/Sources/Subprocess/SubprocessFoundation/Output+Foundation.swift @@ -36,7 +36,7 @@ public struct DataOutput: OutputProtocol, ErrorOutputProtocol { extension OutputProtocol where Self == DataOutput { /// Creates a subprocess output that collects the process's binary output, up to the specified byte limit. /// - /// The subprocess throws an error if the process + /// `run` throws a ``SubprocessError`` if the process /// produces more bytes than `limit`. public static func data(limit: Int) -> Self { return .init(limit: limit) diff --git a/Sources/Subprocess/SubprocessOutputSequence.swift b/Sources/Subprocess/SubprocessOutputSequence.swift index f0ea992d..b92791d4 100644 --- a/Sources/Subprocess/SubprocessOutputSequence.swift +++ b/Sources/Subprocess/SubprocessOutputSequence.swift @@ -521,7 +521,7 @@ extension SubprocessOutputSequence.StringSequence { case unbounded /// Imposes a maximum line length limit. /// - /// The subprocess throws an error if a line exceeds this limit. + /// Iterating the output throws a ``SubprocessError`` if a line exceeds this limit. case maxLineLength(Int) }