Skip to content

An asynchronous version of call to shellOut #34

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

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
22 changes: 14 additions & 8 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
os: linux
language: generic
sudo: required
dist: trusty
install:
- eval "$(curl -sL https://gist.githubusercontent.com/kylef/5c0475ff02b7c7671d2a/raw/9f442512a46d7a2af7b850d65a7e9bd31edfb09b/swiftenv-install.sh)"
script:
- swift test
language: swift

branches:
only:
- master

xcode_project: ShellOut.xcodeproj
xcode_scheme: ShellOut-package
osx_image: xcode9.3
xcode_sdk: iphonesimulator11.3

script:
- xcodebuild -scheme ShellOut-package -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone X,OS=11.3' test

17 changes: 17 additions & 0 deletions Sources/Data+Extensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import Foundation

extension Data {
func shellOutput() -> String {
guard let output = String(data: self, encoding: .utf8) else {
return ""
}

guard !output.hasSuffix("\n") else {
let endIndex = output.index(before: output.endIndex)
return String(output[..<endIndex])
}

return output

}
}
153 changes: 153 additions & 0 deletions Sources/Process+Extensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import Foundation

extension Process {

static func makeBashProcess(withArguments arguments: [String]? = nil) -> Process {
let process = Process()
process.launchPath = "/bin/bash"
process.arguments = arguments
return process
}

@discardableResult func launchBash(outputHandle: FileHandle? = nil, errorHandle: FileHandle? = nil) throws -> String {
// Because FileHandle's readabilityHandler might be called from a
// different queue from the calling queue, avoid a data race by
// protecting reads and writes to outputData and errorData on
// a single dispatch queue.
let outputQueue = DispatchQueue(label: "bash-output-queue")

var outputData = Data()
var errorData = Data()

let outputPipe = Pipe()
standardOutput = outputPipe

let errorPipe = Pipe()
standardError = errorPipe

#if !os(Linux)
outputPipe.fileHandleForReading.readabilityHandler = { handler in
outputQueue.async {
let data = handler.availableData
outputData.append(data)
outputHandle?.write(data)
}
}

errorPipe.fileHandleForReading.readabilityHandler = { handler in
outputQueue.async {
let data = handler.availableData
errorData.append(data)
errorHandle?.write(data)
}
}
#endif

launch()

#if os(Linux)
outputQueue.sync {
outputData = outputPipe.fileHandleForReading.readDataToEndOfFile()
errorData = errorPipe.fileHandleForReading.readDataToEndOfFile()
}
#endif

waitUntilExit()

outputHandle?.closeFile()
errorHandle?.closeFile()

#if !os(Linux)
outputPipe.fileHandleForReading.readabilityHandler = nil
errorPipe.fileHandleForReading.readabilityHandler = nil
#endif

// Block until all writes have occurred to outputData and errorData,
// and then read the data back out.
return try outputQueue.sync {
if terminationStatus != 0 {
throw ShellOutError(
terminationStatus: terminationStatus,
errorData: errorData,
outputData: outputData
)
}

return outputData.shellOutput()
}
}

func launchBash(withCompletion completion: @escaping Completion) {

var outputData = Data()
var errorData = Data()

let outputPipe = Pipe()
standardOutput = outputPipe

let errorPipe = Pipe()
standardError = errorPipe

// Because FileHandle's readabilityHandler might be called from a
// different queue from the calling queue, avoid a data race by
// protecting reads and writes to outputData and errorData on
// a single dispatch queue.
let outputQueue = DispatchQueue(label: "bash-output-queue")

#if !os(Linux)
outputPipe.fileHandleForReading.readabilityHandler = { handler in
outputQueue.async {
let data = handler.availableData
outputData.append(data)
}
}

errorPipe.fileHandleForReading.readabilityHandler = { handler in
outputQueue.async {
let data = handler.availableData
errorData.append(data)
}
}
#endif

launch()

#if os(Linux)
outputQueue.sync {
outputData = outputPipe.fileHandleForReading.readDataToEndOfFile()
errorData = errorPipe.fileHandleForReading.readDataToEndOfFile()
}
#endif

waitUntilExit()

#if !os(Linux)
outputPipe.fileHandleForReading.readabilityHandler = nil
errorPipe.fileHandleForReading.readabilityHandler = nil
#endif

do {
// Block until all writes have occurred to outputData and errorData,
// and then read the data back out.
return try outputQueue.sync {
if terminationStatus != 0 {
throw ShellOutError(
terminationStatus: terminationStatus,
errorData: errorData,
outputData: outputData
)
}

let value = outputData.shellOutput()

DispatchQueue.main.async {
completion({return value})
}
}
} catch {
DispatchQueue.main.async {
completion({throw error})
}
}
}
}
Loading