Skip to content

AsyncBufferSequence.Buffer Improvements #48

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jun 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 9 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,27 +64,17 @@ To have more precise control over input and output, you can provide a custom clo
```swift
import Subprocess

let result = try await run(
.path("/bin/dd"),
arguments: ["if=/path/to/document"]
) { execution in
var contents = ""
for try await chunk in execution.standardOutput {
let string = chunk.withUnsafeBytes { String(decoding: $0, as: UTF8.self) }
contents += string
if string == "Done" {
// Stop execution
await execution.teardown(
using: [
.gracefulShutDown(
allowedDurationToNextStep: .seconds(0.5)
)
]
)
return contents
// Monitor Nginx log via `tail -f`
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) {
// Parse the log text
if line.contains("500") {
// Oh no, 500 error
}
}
return contents
}
```

Expand Down Expand Up @@ -240,10 +230,6 @@ This option collects output as `[UInt8]`.

Use it by setting `.bytes` or `.bytes(limit:)` for `input` or `error`.

#### `SequenceOutput`:

This option redirects the child output to the `.standardOutput` or `.standardError` property of `Execution`. It’s only for the `run()` family that takes a custom closure.


### Cross-platform support

Expand Down
103 changes: 60 additions & 43 deletions Sources/Subprocess/API.swift
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ public func run<
// MARK: - Custom Execution Body

/// Run an executable with given parameters and a custom closure
/// to manage the running subprocess' lifetime and its IOs.
/// to manage the running subprocess' lifetime and stream its standard output.
/// - Parameters:
/// - executable: The executable to run.
/// - arguments: The arguments to pass to the executable.
Expand All @@ -152,23 +152,19 @@ public func run<
/// - platformOptions: The platform specific options to use
/// when running the executable.
/// - input: The input to send to the executable.
/// - output: How to manage the executable standard ouput.
/// - error: How to manager executable standard error.
/// - isolation: the isolation context to run the body closure.
/// - body: The custom execution body to manually control the running process
/// - Returns a ExecutableResult type containing the return value
/// - Returns an executableResult type containing the return value
/// of the closure.
#if SubprocessSpan
@available(SubprocessSpan, *)
#endif
public func run<Result, Input: InputProtocol, Error: OutputProtocol>(
_ executable: Executable,
arguments: Arguments = [],
environment: Environment = .inherit,
workingDirectory: FilePath? = nil,
platformOptions: PlatformOptions = PlatformOptions(),
input: Input = .none,
error: Error,
error: Error = .discarded,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was accidentally left out from the ~Copyable PR. I discovered it as I was testing closure based runs

isolation: isolated (any Actor)? = #isolation,
body: ((Execution, AsyncBufferSequence) async throws -> Result)
) async throws -> ExecutionResult<Result> where Error.OutputType == Void {
Expand Down Expand Up @@ -208,9 +204,21 @@ public func run<Result, Input: InputProtocol, Error: OutputProtocol>(
}
}

#if SubprocessSpan
@available(SubprocessSpan, *)
#endif
/// 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.
/// - input: The input to send to the executable.
/// - output: How to manager 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.
public func run<Result, Input: InputProtocol, Output: OutputProtocol>(
_ executable: Executable,
arguments: Arguments = [],
Expand Down Expand Up @@ -258,16 +266,28 @@ public func run<Result, Input: InputProtocol, Output: OutputProtocol>(
}
}

#if SubprocessSpan
@available(SubprocessSpan, *)
#endif
/// 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.
/// - 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.
public func run<Result, Error: OutputProtocol>(
_ executable: Executable,
arguments: Arguments = [],
environment: Environment = .inherit,
workingDirectory: FilePath? = nil,
platformOptions: PlatformOptions = PlatformOptions(),
error: Error,
error: Error = .discarded,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this part of the same change?

isolation: isolated (any Actor)? = #isolation,
body: ((Execution, StandardInputWriter, AsyncBufferSequence) async throws -> Result)
) async throws -> ExecutionResult<Result> where Error.OutputType == Void {
Expand All @@ -291,9 +311,21 @@ public func run<Result, Error: OutputProtocol>(
}
}

#if SubprocessSpan
@available(SubprocessSpan, *)
#endif
/// 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.
/// - 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.
public func run<Result, Output: OutputProtocol>(
_ executable: Executable,
arguments: Arguments = [],
Expand Down Expand Up @@ -324,25 +356,20 @@ public func run<Result, Output: OutputProtocol>(
}
}

/// Run a executable with given parameters and a custom closure
/// to manage the running subprocess' lifetime and write to its
/// standard input via `StandardInputWriter`
/// 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 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.
/// - output:How to handle executable's standard output
/// - error: How to handle executable's standard error
/// - isolation: the isolation context to run the body closure.
/// - body: The custom execution body to manually control the running process
/// - Returns a ExecutableResult type containing the return value
/// - Returns an executableResult type containing the return value
/// of the closure.
#if SubprocessSpan
@available(SubprocessSpan, *)
#endif
public func run<Result>(
_ executable: Executable,
arguments: Arguments = [],
Expand Down Expand Up @@ -384,7 +411,7 @@ public func run<Result>(

// MARK: - Configuration Based

/// Run a `Configuration` asynchrously and returns
/// Run a `Configuration` asynchronously and returns
/// a `CollectedResult` containing the output of the child process.
/// - Parameters:
/// - configuration: The `Subprocess` configuration to run.
Expand Down Expand Up @@ -476,19 +503,15 @@ public func run<
)
}

/// Run a executable with given parameters specified by a `Configuration`
/// Run an executable with given parameters specified by a `Configuration`
/// - Parameters:
/// - configuration: The `Subprocess` configuration to run.
/// - output: The method to use for redirecting the standard output.
/// - error: The method to use for redirecting the standard error.
/// - isolation: the isolation context to run the body closure.
/// - body: The custom configuration body to manually control
/// the running process and write to its standard input.
/// - Returns a ExecutableResult type containing the return value
/// 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.
#if SubprocessSpan
@available(SubprocessSpan, *)
#endif
public func run<Result>(
_ configuration: Configuration,
isolation: isolated (any Actor)? = #isolation,
Expand All @@ -511,7 +534,7 @@ public func run<Result>(

// MARK: - Detached

/// Run a executable with given parameters and return its process
/// Run an executable with given parameters and return its process
/// identifier immediately without monitoring the state of the
/// subprocess nor waiting until it exits.
///
Expand All @@ -528,9 +551,6 @@ public func run<Result>(
/// - output: A file descriptor to bind to the subprocess' standard output.
/// - error: A file descriptor to bind to the subprocess' standard error.
/// - Returns: the process identifier for the subprocess.
#if SubprocessSpan
@available(SubprocessSpan, *)
#endif
public func runDetached(
_ executable: Executable,
arguments: Arguments = [],
Expand All @@ -551,7 +571,7 @@ public func runDetached(
return try runDetached(config, input: input, output: output, error: error)
}

/// Run a executable with given configuration and return its process
/// Run an executable with given configuration and return its process
/// identifier immediately without monitoring the state of the
/// subprocess nor waiting until it exits.
///
Expand All @@ -564,9 +584,6 @@ public func runDetached(
/// - output: A file descriptor to bind to the subprocess' standard output.
/// - error: A file descriptor to bind to the subprocess' standard error.
/// - Returns: the process identifier for the subprocess.
#if SubprocessSpan
@available(SubprocessSpan, *)
#endif
public func runDetached(
_ configuration: Configuration,
input: FileDescriptor? = nil,
Expand Down
Loading
Loading