Skip to content

Instantly share code, notes, and snippets.

@dilames
Created January 9, 2020 13:01
Show Gist options
  • Save dilames/a09b2c6fd59c735c844492cfbec2b143 to your computer and use it in GitHub Desktop.
Save dilames/a09b2c6fd59c735c844492cfbec2b143 to your computer and use it in GitHub Desktop.
import Foundation
final class Bash {
// MARK: Private
private var operations: [Process]
///
/// - Parameters:
/// - domain: Root folder for storing output files (Pipe is limited my itself capacity so you may send output data to an file)
/// - arguments: Arguments for execution
public init(domain: URL? = nil,
arguments: [String]) throws {
operations = try arguments
.split(separator: "|")
.map { try Bash.operation(Array($0), domain: domain) }
var buffer: Process?
operations.forEach {
if let buffer = buffer {
$0.standardInput = buffer.standardOutput
$0.standardError = buffer.standardError
}
buffer = $0
}
}
@discardableResult
public func start() throws -> Bash {
for operation in operations {
operation.launch()
operation.waitUntilExit()
}
return self
}
private func data(from source: Any?) throws -> Data? {
var data: Data?
if let pipe = source as? Pipe {
data = pipe.fileHandleForReading.readDataToEndOfFile()
} else if let fileHandle = source as? FileHandle {
let url = try FileManager.url(fromDescriptor: fileHandle.fileDescriptor)
data = try Data(contentsOf: url)
}
return !(data?.isEmpty ?? false) ? data : nil
}
public func output() throws -> Data? {
return try data(from: operations.last?.standardOutput)
}
public func error() throws -> Data? {
return try data(from: operations.last?.standardError)
}
}
// MARK: Constructor
private extension Bash {
static func operation(_ args: [String],
fileManager: FileManager = .default,
domain: URL?) throws -> Process {
let process = Process()
process.environment = ProcessInfo.processInfo.environment
process.launchPath = "/usr/bin/env"
process.arguments = args
if let domain = domain {
process.standardInput = try FileHandle(forWritingTo: fileManager.bash(file: .input, in: domain))
process.standardOutput = try FileHandle(forWritingTo: fileManager.bash(file: .output, in: domain))
process.standardError = try FileHandle(forWritingTo: fileManager.bash(file: .error, in: domain))
} else {
process.standardInput = Pipe()
process.standardOutput = Pipe()
process.standardError = Pipe()
}
return process
}
}
// MARK: Routing
private extension FileManager {
enum File: String {
case input
case output
case error
}
enum Error: LocalizedError {
case pathRetrieving
}
func url(creatingAt url: URL) throws -> URL {
!url.pathExtension.isEmpty
? _ = createFile(atPath: url.path, contents: nil, attributes: nil)
: try createDirectory(at: url, withIntermediateDirectories: true, attributes: nil)
return url
}
func bash(file: File, in domain: URL) throws -> URL {
return try url(creatingAt: domain
.appendingPathComponent(file.rawValue)
.appendingPathExtension("raw")
)
}
static func path(fromDescriptor fileDescriptor: Int32) throws -> String {
var ptr = Array<CChar>(repeating: 32, count: 256)
guard fcntl(fileDescriptor, F_GETPATH, &ptr) == 0 else { throw Error.pathRetrieving }
return String(cString: ptr)
}
static func url(fromDescriptor fileDescriptor: Int32) throws -> URL {
return URL(fileURLWithPath: try path(fromDescriptor: fileDescriptor))
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment