Created
July 19, 2018 22:39
-
-
Save kjessup/d3a0a9ead58ace21ab0759037e9c4192 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
// | |
// Shell.swift | |
// PerfectAssistant2 | |
// | |
// Created by Kyle Jessup on 2017-03-30. | |
// Copyright © 2017 PerfectlySoft. All rights reserved. | |
// | |
import Foundation | |
import PerfectLib | |
extension File { | |
func switchToNonBlocking() { | |
guard self.isOpen else { | |
return | |
} | |
let fd = Int32(self.fd) | |
let flags = fcntl(fd, F_GETFL) | |
guard flags >= 0 else { | |
return | |
} | |
let _ = fcntl(fd, F_SETFL, flags | O_NONBLOCK) | |
var one = Int32(1) | |
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, UInt32(MemoryLayout<Int32>.size)) | |
setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &one, UInt32(MemoryLayout<Int32>.size)); | |
} | |
} | |
private var pathFromShell: String { | |
do { | |
let shell = ProcessInfo.processInfo.environment["SHELL"] ?? "/bin/bash" | |
let env = [ | |
("HOME", ProcessInfo.processInfo.environment["HOME"]!), | |
("LANG", "en_CA.UTF-8"), | |
("DISABLE_AUTO_UPDATE", "true")] | |
let proc: SysProcess | |
switch shell.lastFilePathComponent { | |
case "zsh": | |
proc = try SysProcess(shell, args: ["-ci", "echo $PATH"], env: env) | |
default: | |
proc = try SysProcess(shell, args: ["-cl", "echo $PATH"], env: env) | |
} | |
var read = "" | |
while true { | |
do { | |
guard let s = try proc.stdout?.readSomeBytes(count: 4096) , s.count > 0 else { | |
break | |
} | |
let str = UTF8Encoding.encode(bytes: s) | |
read += str | |
} catch PerfectLib.PerfectError.fileError(let code, _) { | |
if code != EINTR { | |
break | |
} | |
} | |
} | |
let res = try proc.wait(hang: true) | |
if res == 0 && !read.isEmpty { | |
return read | |
} | |
} catch { | |
print("Error thrown while echoing $PATH: \(error)") | |
} | |
// print("Returning fallback path") | |
return "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local:/usr/local/Cellar:~/.swiftenv/" | |
} | |
func sleep(seconds: Double) { | |
guard seconds >= 0.0 else { | |
return | |
} | |
let milliseconds = Int(seconds * 1000.0) | |
var tv = timeval() | |
tv.tv_sec = milliseconds/1000 | |
tv.tv_usec = Int32((milliseconds%1000)*1000) | |
select(0, nil, nil, nil, &tv) | |
} | |
func runProc(_ cmd: String, args: [String], envs: [String:String] = [:], quoteArgs: Bool = true, stderr: Bool = false, cd: String? = nil, read: ((String) -> ())? = nil) throws { | |
var ienvs = [("PATH", pathFromShell), | |
("HOME", ProcessInfo.processInfo.environment["HOME"]!), | |
("LANG", "en_CA.UTF-8"), | |
("DISABLE_AUTO_UPDATE", "true")] | |
for e in envs { | |
ienvs.append(e) | |
} | |
let cmdPath = File(cmd).path | |
var newCmd: String | |
if let cd = cd { | |
newCmd = "cd '\(cd)' && '\(cmdPath)\'" | |
} else { | |
newCmd = "'\(cmdPath)\'" | |
} | |
for n in 1...args.count { | |
if quoteArgs { | |
newCmd.append(" \"${\(n)}\"") | |
} else { | |
newCmd.append(" ${\(n)}") | |
} | |
} | |
let shell = "/bin/sh" | |
let proc = try SysProcess(shell, args: ["--login", "-ci", newCmd, cmdPath] + args, env: ienvs) | |
let out: File? = stderr ? proc.stderr : proc.stdout | |
if let read = read { | |
while true { | |
do { | |
guard let s = try out?.readSomeBytes(count: 4096) , s.count > 0 else { | |
break | |
} | |
let str = UTF8Encoding.encode(bytes: s) | |
read(str.replacingOccurrences(of: "sh: no job control in this shell\n", with: "")) | |
} catch PerfectLib.PerfectError.fileError(let code, _) { | |
if code != EINTR { | |
break | |
} | |
} | |
} | |
} | |
let res = try proc.wait(hang: true) | |
if res != 0 { | |
let s = try proc.stderr?.readString() | |
throw PerfectError.systemError(Int32(res), s!.replacingOccurrences(of: "sh: no job control in this shell\n", with: "")) | |
} | |
} | |
func runProc(_ cmd: String, args: [String], envs: [String:String], quoteArgs: Bool, cd: String? = nil, readStdin: ((String) -> ()), readStderr: ((String) -> ())) throws -> Int { | |
var ienvs = [("PATH", pathFromShell), | |
("HOME", ProcessInfo.processInfo.environment["HOME"]!), | |
("LANG", "en_CA.UTF-8"), | |
("DISABLE_AUTO_UPDATE", "true")] | |
for e in envs { | |
ienvs.append(e) | |
} | |
let cmdPath = File(cmd).path | |
var newCmd: String | |
if let cd = cd { | |
newCmd = "cd '\(cd)' && '\(cmdPath)\'" | |
} else { | |
newCmd = "'\(cmdPath)\'" | |
} | |
for n in 1...args.count { | |
if quoteArgs { | |
newCmd.append(" \"${\(n)}\"") | |
} else { | |
newCmd.append(" ${\(n)}") | |
} | |
} | |
let shell = "/bin/sh" | |
let proc = try SysProcess(shell, args: ["--login", "-ci", newCmd, cmdPath] + args, env: ienvs) | |
proc.stdout?.switchToNonBlocking() | |
proc.stderr?.switchToNonBlocking() | |
var res = -1 as Int32 | |
while proc.isOpen() { | |
do { | |
res = try proc.wait(hang: false) | |
var writeStr = "" | |
do { | |
while let s = try proc.stdout?.readSomeBytes(count: 4096), s.count > 0 { | |
let str = UTF8Encoding.encode(bytes: s) | |
writeStr.append(str) | |
} | |
} catch PerfectLib.PerfectError.fileError(let code, _) where code == EAGAIN {} | |
if !writeStr.isEmpty { | |
readStdin(writeStr.replacingOccurrences(of: "sh: no job control in this shell\n", with: "")) | |
} | |
writeStr = "" | |
do { | |
while let s = try proc.stderr?.readSomeBytes(count: 4096), s.count > 0 { | |
let str = UTF8Encoding.encode(bytes: s) | |
writeStr.append(str) | |
} | |
} catch PerfectLib.PerfectError.fileError(let code, _) where code == EAGAIN {} | |
if !writeStr.isEmpty { | |
readStderr(writeStr.replacingOccurrences(of: "sh: no job control in this shell\n", with: "")) | |
} | |
sleep(seconds: 0.25) | |
} catch PerfectLib.PerfectError.fileError(let code, _) { | |
if code != EINTR { | |
break | |
} | |
} | |
} | |
return Int(res) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment