Created
January 9, 2020 13:01
-
-
Save dilames/a09b2c6fd59c735c844492cfbec2b143 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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