Last active
June 5, 2020 10:55
-
-
Save KaQuMiQ/076df5b75293291f0a9303eb250e8b8a to your computer and use it in GitHub Desktop.
Cockoo
This file contains hidden or 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 class Foundation.NSTask.Process | |
| import class Foundation.Pipe | |
| import class Foundation.FileHandle | |
| import struct Foundation.NSData.Data | |
| import class Foundation.NSLock.NSConditionLock | |
| public final class Command { | |
| public var isRunning: Bool { lock.condition == 1 } | |
| public var isCompleted: Bool { lock.condition == 0 } | |
| private let process: Process | |
| private let lock: NSConditionLock = NSConditionLock(condition: -1) | |
| private var stdInPipe: Pipe? = nil | |
| private var stdOutPipe: Pipe? = nil | |
| private var stdErrPipe: Pipe? = nil | |
| public init( | |
| _ command: String, | |
| arguments: Array<String> = [], | |
| commandSearchPath: String? = nil, | |
| workingDirectory: String? = nil | |
| ) { | |
| self.process = Process.prepare( | |
| command, | |
| arguments: arguments, | |
| workingDirectory: workingDirectory, | |
| commandSearchPath: commandSearchPath | |
| ) | |
| } | |
| @discardableResult | |
| public func bindInWithOut(of command: Command) -> Bool { | |
| guard !isCompleted else { return false } | |
| guard stdInPipe == nil, command.stdOutPipe == nil else { return false } | |
| let pipe: Pipe = Pipe() | |
| stdInPipe = pipe | |
| command.stdOutPipe = pipe | |
| return true | |
| } | |
| @discardableResult | |
| public func bindInWithErr(of command: Command) -> Bool { | |
| guard !isCompleted else { return false } | |
| guard stdInPipe == nil, command.stdErrPipe == nil else { return false } | |
| let pipe: Pipe = Pipe() | |
| stdInPipe = pipe | |
| command.stdErrPipe = pipe | |
| return true | |
| } | |
| @discardableResult | |
| public func writeIn(_ write: @escaping (@escaping (Data) -> Void) -> Void) -> Bool { | |
| guard !isCompleted else { return false } | |
| guard stdInPipe == nil else { return false } | |
| let pipe: Pipe = Pipe() | |
| let fileHandle: FileHandle = pipe.fileHandleForWriting | |
| write { data in | |
| if data.isEmpty { | |
| fileHandle.closeFile() | |
| } else { | |
| fileHandle.write(data) | |
| } | |
| } | |
| stdOutPipe = pipe | |
| return true | |
| } | |
| @discardableResult | |
| public func receiveOut(_ handler: @escaping (Data) -> Void) -> Bool { | |
| guard !isCompleted else { return false } | |
| guard stdOutPipe == nil else { return false } | |
| let pipe: Pipe = Pipe() | |
| let fileHandle: FileHandle = pipe.fileHandleForReading | |
| fileHandle.readabilityHandler = { file in | |
| let data = file.availableData | |
| if data.isEmpty { | |
| fileHandle.closeFile() | |
| } else { | |
| handler(data) | |
| } | |
| } | |
| stdOutPipe = pipe | |
| return true | |
| } | |
| @discardableResult | |
| public func receiveErr(_ handler: @escaping (Data) -> Void) -> Bool { | |
| guard !isCompleted else { return false } | |
| guard stdErrPipe == nil else { return false } | |
| let pipe: Pipe = Pipe() | |
| let fileHandle: FileHandle = pipe.fileHandleForReading | |
| fileHandle.readabilityHandler = { file in | |
| let data = file.availableData | |
| if data.isEmpty { | |
| fileHandle.closeFile() | |
| } else { | |
| handler(data) | |
| } | |
| } | |
| stdErrPipe = pipe | |
| return true | |
| } | |
| @discardableResult | |
| public func runSync() -> Result<Void, CommandError> { | |
| guard lock.tryLock(whenCondition: -1) else { fatalError("TODO") } | |
| lock.unlock(withCondition: 1) | |
| return Result { | |
| try process.execute( | |
| stdInPipe: stdInPipe, | |
| stdOutPipe: stdOutPipe, | |
| stdErrPipe: stdErrPipe, | |
| completion: { [weak self] _ in | |
| guard let self = self else { return } | |
| guard self.lock.tryLock(whenCondition: 1) else { fatalError("TODO") } | |
| self.lock.unlock(withCondition: 0) | |
| } | |
| ) | |
| if process.waitForExit() == 0 { | |
| // ok | |
| } else { | |
| throw CommandError.todoErrors | |
| } | |
| }.mapError { _ in | |
| return .todoErrors | |
| } | |
| } | |
| public func runAsync(_ completion: ((Result<Void, CommandError>) -> Void)? = nil) { | |
| guard lock.tryLock(whenCondition: -1) else { fatalError("TODO") } | |
| lock.unlock(withCondition: 1) | |
| do { | |
| try process.execute( | |
| stdInPipe: stdInPipe, | |
| stdOutPipe: stdOutPipe, | |
| stdErrPipe: stdErrPipe, | |
| completion: { [weak self] exitCode in | |
| guard let self = self else { return } | |
| guard self.lock.tryLock(whenCondition: 1) else { fatalError("TODO") } | |
| self.lock.unlock(withCondition: 0) | |
| if exitCode == 0 { | |
| completion?(.success(())) | |
| } else { | |
| completion?(.failure(.todoErrors)) | |
| } | |
| } | |
| ) | |
| } catch { | |
| completion?(.failure(.todoErrors)) | |
| } | |
| } | |
| } | |
| public enum CommandError: Error { | |
| case todoErrors | |
| } |
This file contains hidden or 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 class Foundation.NSTask.Process | |
| import class Foundation.NSTask.ProcessInfo | |
| import struct Foundation.NSData.Data | |
| import struct Foundation.NSURL.URL | |
| import class Foundation.Pipe | |
| import class Foundation.FileHandle | |
| internal extension Process { | |
| static func prepare( | |
| _ command: String, | |
| arguments: Array<String> = [], | |
| workingDirectory: String? = nil, | |
| commandSearchPath: String? = nil | |
| ) -> Process { | |
| let process = Process() | |
| process.executableURL = URL(string: "file:///usr/bin/env") | |
| process.arguments = [command] + arguments | |
| workingDirectory.map { process.currentDirectoryURL = URL(string: "file://\($0)") } | |
| if let commandSearchPath = commandSearchPath { | |
| var environment = ProcessInfo.processInfo.environment | |
| if let path = environment["PATH"] { | |
| environment["PATH"] = "\(path): \(commandSearchPath)" | |
| } else { | |
| environment["PATH"] = commandSearchPath | |
| } | |
| process.environment = environment | |
| } else { /* use inherited from current process */ } | |
| return process | |
| } | |
| @discardableResult func waitForExit() -> Int32 { | |
| waitUntilExit() | |
| return terminationStatus | |
| } | |
| func execute( | |
| stdInPipe: Pipe? = nil, | |
| stdOutPipe: Pipe? = nil, | |
| stdErrPipe: Pipe? = nil, | |
| completion: @escaping (Int32) -> Void = { _ in } | |
| ) throws { | |
| if let stdInPipe = stdInPipe { | |
| standardInput = stdInPipe | |
| } else { | |
| standardInput = FileHandle.nullDevice | |
| } | |
| if let stdOutPipe = stdOutPipe { | |
| standardOutput = stdOutPipe | |
| } else { | |
| standardOutput = FileHandle.nullDevice | |
| } | |
| if let stdErrPipe = stdErrPipe { | |
| standardError = stdErrPipe | |
| } else { | |
| standardError = FileHandle.nullDevice | |
| } | |
| terminationHandler = { process in | |
| stdInPipe?.fileHandleForWriting.closeFile() | |
| stdOutPipe?.fileHandleForReading.closeFile() | |
| stdErrPipe?.fileHandleForReading.closeFile() | |
| completion(process.terminationStatus) | |
| } | |
| try run() | |
| } | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Example:
Example2: