Coquille

0.3.0

A simple, modern Swift class for running shell commands
alexrozanski/Coquille

What's New

v0.3.0: Cancellation and better defaults

2023-05-09T10:54:28Z
  • Supports cancellation of in-flight execution by returning an opaque cancellation handle you can call cancel() on
  • Defaults printing to stdout and stderr to false to make it easier to run background tasks silently
  • Fixes implementation of tests to make them more robust

🐚 Coquille

Build Status

A simple Swift wrapper around Process supporting Swift Concurrency and streamed output from stdout and stderr.

Requirements

macOS 10.15+

Installation

Add Coquille to your project using Xcode (File > Add Packages...) or by adding it to your project's Package.swift file:

dependencies: [
  .package(url: "https://github.com/alexrozanski/Coquille.git", from: "0.3.0")
]

Usage

Coquille exposes its own Process class which you can interact with to execute commands. Process.run() is an async function so you can just await the exit code:

import Coquille

let process = Process(commandString: "pwd"))
_ = try await process.run() // Prints `pwd` to `stdout`

// Use `command:` for more easily working with variable command-line arguments
let deps = ["numpy", "torch"]
let process = Process(command: .init("python3", arguments: ["-m", "pip", "install"] + deps)))
_ = try await process.run()

I/O

By default Process does not pipe any output from the spawned process to stdout and stderr. This can be configured with printStdout and printStderr:

import Coquille

let process = Process(commandString: "brew install wget", printStdout: true))
_ = try await process.run() // Pipes standard output to `stdout` but will not pipe error output to `stderr`

You can also pass an OutputHandler for both stdout and stderr which will stream contents from both:

import Coquille

let process = Process(
  commandString: "swift build",
  stdout: { stdout in
    ...
  },
  stderr: { stderr in
    ...
  })
_ = try await process.run() // Streams standard and error output to the handlers provided to `stdout:` and `stderr:`

Exit Codes

// `isSuccess` can be used to test the exit code for success
let hasRuby = (try await Process(commandString: "which ruby").run()).isSuccess

// Use `errorCode` to get a nonzero exit code
if let errorCode = (try await Process(commandString: "swift build").run()).errorCode {
  switch errorCode {
    case 127:
      // Command not found
    default:
      ...
  }
}

Cancellation

The main Process.run() function signature is:

public func run() async throws -> Status

which allows you use Swift Concurrency to execute the subprocess and await the exit status. However if you want to support cancellation you can use the other run() function:

public func run(with completionHandler: @escaping ((Status) -> Void)) -> ProcessCancellationHandle

This immediately returns an opaque ProcessCancellationHandle type which you can call cancel() on, should you wish to cancel execution, and the process status is delivered through a completionHandler closure.

Acknowledgements

Thanks to Ben Chatelain for their blog post on intercepting stdout, used to implement some of the tests in the test suite.

Description

  • Swift Tools 5.5.0
View More Packages from this Author

Dependencies

  • None
Last updated: Sun Oct 27 2024 08:30:38 GMT-0900 (Hawaii-Aleutian Daylight Time)