Skip to content

Instantly share code, notes, and snippets.

@KaQuMiQ
Last active June 5, 2020 10:55
Show Gist options
  • Select an option

  • Save KaQuMiQ/076df5b75293291f0a9303eb250e8b8a to your computer and use it in GitHub Desktop.

Select an option

Save KaQuMiQ/076df5b75293291f0a9303eb250e8b8a to your computer and use it in GitHub Desktop.
Cockoo
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
}
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()
}
}
@KaQuMiQ
Copy link
Author

KaQuMiQ commented Jun 5, 2020

Example:

      let echo = Command("echo", arguments: ["HELLO swift!"])
      let cat = Command("cat")
      cat.bindInWithOut(of: echo)
      cat.receiveOut { data in
        print("OUT:\n\(String(data: data, encoding: .utf8)!)")
      }
      echo.runAsync()
      cat.runSync()

Example2:

      let gitStatus = Command("git", arguments: ["status"], workingDirectory: "my/git/dir")
      gitStatus.receiveOut { data in
        print("OUT:\n\(String(data: data, encoding: .utf8)!)")
      }
      gitStatus.receiveErr { data in
        print("ERR:\n\(String(data: data, encoding: .utf8)!)")
      }
      gitStatus.runSync()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment