Giter VIP home page Giter VIP logo

subprocess's Introduction

Subprocess

License Build CocoaPods Platform Language Carthage compatible SwiftPM compatible Documentation

Subprocess is a Swift library for macOS providing interfaces for both synchronous and asynchronous process execution. SubprocessMocks can be used in unit tests for quick and highly customizable mocking and verification of Subprocess usage.

Full Documentation

Usage

Subprocess Class

The Subprocess class can be used for command execution.

Command Input

Input for data
let inputData = Data("hello world".utf8)
let data = try await Subprocess.data(for: ["/usr/bin/grep", "hello"], standardInput: inputData)
Input for text
let data = try await Subprocess.data(for: ["/usr/bin/grep", "hello"], standardInput: "hello world")
Input for file URL
let data = try await Subprocess.data(for: ["/usr/bin/grep", "foo"], standardInput: URL(filePath: "/path/to/input/file"))

Command Output

Output as Data
let data = try await Subprocess.data(for: ["/usr/bin/sw_vers"])
Output as String
let string = try await Subprocess.string(for: ["/usr/bin/sw_vers"])
Output as decodable object from JSON
struct LogMessage: Codable {
    var subsystem: String
    var category: String
    var machTimestamp: UInt64
}

let result: [LogMessage] = try await Subprocess.value(for: ["/usr/bin/log", "show", "--style", "json", "--last", "30s"], decoder: JSONDecoder())
Output as decodable object from Property List
struct SystemVersion: Codable {
    enum CodingKeys: String, CodingKey {
        case version = "ProductVersion"
    }
    var version: String
}

let result: SystemVersion = try await Subprocess.value(for: ["/bin/cat", "/System/Library/CoreServices/SystemVersion.plist"], decoder: PropertyListDecoder())
Output mapped to other type
let enabled = try await Subprocess(["/usr/bin/csrutil", "status"]).run().standardOutput.lines.first(where: { $0.contains("enabled") } ) != nil
Output options
let errorText = try await Subprocess.string(for: ["/usr/bin/cat", "/non/existent/file.txt"], options: .returnStandardError)
let outputText = try await Subprocess.string(for: ["/usr/bin/sw_vers"])

async let (standardOutput, standardError, _) = try Subprocess(["/usr/bin/csrutil", "status"]).run()
let combinedOutput = try await [standardOutput.string(), standardError.string()]
Handling output as it is read
let (stream, input) = {
    var input: AsyncStream<UInt8>.Continuation!
    let stream: AsyncStream<UInt8> = AsyncStream { continuation in
        input = continuation
    }

    return (stream, input!)
}()

let subprocess = Subprocess(["/bin/cat"])
let (standardOutput, _, waitForExit) = try subprocess.run(standardInput: stream)

input.yield("hello\n")

Task {
    for await line in standardOutput.lines {
        switch line {
        case "hello":
            input.yield("world\n")
        case "world":
            input.yield("and\nuniverse")
            input.finish()
        case "universe":
            await waitForExit()
            break
        default:
            continue
        }
    }
}
Handling output on termination
let process = Subprocess(["/usr/bin/csrutil", "status"])
let (standardOutput, standardError, waitForExit) = try process.run()
async let (stdout, stderr) = (standardOutput, standardError)
let combinedOutput = await [stdout.data(), stderr.data()]

await waitForExit()

if process.exitCode == 0 {
    // Do something with output data
} else {
    // Handle failure
}
Closure based callbacks
let command: [String] = ...
let process = Subprocess(command)
nonisolated(unsafe) var outputData: Data?
nonisolated(unsafe) var errorData: Data?

// The outputHandler and errorHandler are invoked serially
try process.launch(outputHandler: { data in
    // Handle new data read from stdout
    outputData = data 
}, errorHandler: { data in
    // Handle new data read from stderr
    errorData = data
}, terminationHandler: { process in
    // Handle process termination, all scheduled calls to
    // the outputHandler and errorHandler are guaranteed to
    // have completed.
})
Handing output on termination with a closure
let command: [String] = ...
let process = Subprocess(command)

try process.launch { (process, outputData, errorData) in
    if process.exitCode == 0 {
        // Do something with output data
    } else {
        // Handle failure
    }

Installation

SwiftPM

let package = Package(
    // name, platforms, products, etc.
    dependencies: [
        // other dependencies
        .package(url: "https://github.com/jamf/Subprocess.git", .upToNextMajor(from: "3.0.0")),
    ],
    targets: [
        .target(name: "<target>",
        dependencies: [
            // other dependencies
            .product(name: "Subprocess"),
        ]),
        // other targets
    ]
)

Cocoapods

pod 'Subprocess'

Carthage

github 'jamf/Subprocess'

subprocess's People

Contributors

mlink avatar jacobhearst avatar saczuu avatar cyrusingraham avatar watkyn avatar mm512 avatar robbtom avatar aaronvegh avatar macblazer avatar

Stargazers

Hwee-Boon Yar avatar Anbalagan D avatar 南知 avatar kim4apple avatar Denys Telezhkin avatar 逸风 avatar Karim Zhang avatar Simeon Leifer avatar Alexander Repty avatar huskyroll avatar Aleksey Gurtovoy avatar Anton Prokhorov avatar Yang Hu avatar Eric Summers avatar ThatXliner avatar 丁二 avatar Frank Meeusen avatar  avatar Nathaniel Strauss avatar Brandon Evans avatar  avatar  avatar Mark avatar Dennis Beatty avatar Adrian Kubisztal avatar  avatar Isaac Halvorson avatar

Watchers

 avatar Henry Stamerjohann avatar Derek Arnold avatar Ryan Rud avatar James Cloos avatar Jiri Sedlacek avatar Kevin Walker avatar  avatar hillary avatar Adrian Kubisztal avatar  avatar Chris Robus avatar Sameh Sayed avatar Mike Mello avatar Martin Uridil avatar Tom (Rosco) Wile II avatar  avatar Joel Seeger avatar Brandon Roehl avatar  avatar Marek Saktor avatar

subprocess's Issues

Publish docs using GitHub Pages

Use DocC compilation to publish the reference documentation on GitHub pages. The current docs use the README.md file as a landing page for the docs; DocC will need its own landing page.

Remove the /docs source folder as part of this.

  • Note: this will fix the issue noted by DependaBot under the Security tab about outdated Javascript libraries in the /docs folder.

Fix README.md documentation badge

In the README.md file, the documentation badge should be changed to this URL: https://img.shields.io/badge/documentation-100%25-green instead of pointing into the no-longer-existing docs/ folder for an svg file.

Would be nice to have a single cover page as well with examples or overview of the library. The old Jazzy generated docs used the README file as the main point of entry, but DocC needs a different file.

Update GitHub actions

  • Rename master branch to main.
  • Update GitHub actions to use actions/checkout@v3 and to run on PRs as well as changes to main.
  • Add CODEOWNERS file

Once that is complete, update branch protection for main to require all unit tests to pass.

Subprocess has strange behavior when process exits with non-zero code

Calling error.localizedDescription inside the catch block will cause any calls to os_log and print to do nothing. I'm not sure why this is the case, but maybe something to do with stdout/stderr manipulation or something to do with the UnsafeData usage for the standardOutput data in the error?

Example code:

Task {
	do {
		let output = try await Subprocess.data(for: ["/usr/bin/false"])
	} catch {
		os_log("Hit a snag.")
		let value = error.localizedDescription // why does this call cause os_log and print to not work?
		os_log("As Interpolation: \(value)")
		os_log("As Parameter: %s", value)
		print(value)
		os_log("plain old log?")
	}
}

Actual output:

Hit a snag.

Expected output:

Hit a snag.
As Interpolation: Process exited with status 1: 
As Parameter: Process exited with status 1: 
Process exited with status 1: 
plain old log?

Added exit code to message for exitedWithNonZeroStatus error

The SubprocessError type nicely encapsulates the errors that come from the (deprecated) Shell code. However, when the process being called exits with a non-zero exit code and output no other info to stdout or stderr, the description of the error is empty for the calling code.

Example code:

do {
	_ = try Shell(["/usr/bin/false"]).exec()
} catch {
	os_log("Error description was: '\(error.localizedDescription)'")
	if let exitError = error as? SubprocessError,
	   case .exitedWithNonZeroStatus(let exitCode, let text) = exitError {
		os_log("Exit code was: \(exitCode); text was '\(text)'")
	}
}

Actual output:

Error description was: ''
Exit code was: 1; text was ''

Expected output (something like this):

Error description was: 'Process exited with code 1'
Exit code was: 1; text was ''

Current usages of . exitedWithNonZeroStatus:

throw SubprocessError.exitedWithNonZeroStatus(exitCode, message ?? "")

throw SubprocessError.exitedWithNonZeroStatus(exitCode, text)

Current code that converts the error to text:

case .exitedWithNonZeroStatus(_, let errorMessage):

Prep for next release

  • Update the README.md file with a link to the reference documentation at https://engineering.jamf.com/Subprocess/documentation/subprocess/.
  • Update the CHANGELOG file with all non-trivial changes.
  • Update the Subprocess.podspec file with the new version number.

After the merge:

  • Put a git tag on the next version.
  • Update public CocoaPods listing.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.