aboutsummaryrefslogtreecommitdiff
path: root/Sources/phbar/Models/Runner.swift
blob: be4dbff06c54178aaca85ca1a73cb97294d4b304 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import Foundation

/// Runs a shell command and returns its stdout as separate lines.
///
/// Blocking — always call from a background context (e.g. `Task.detached`).
enum Runner {
	/// Run a command and returns stdout as an array of separated lines.
	/// - Parameters:
	///   - command: The command to run.
	///   - env: An optional dictionary of strings to merge into
	///          the process environment.
	///
	/// - Returns: An array of the text lines printed to stdout.
	static func run(_ command: String, env: [String: String] = [:]) -> [String] {
		let process = Process()
		process.executableURL = URL(fileURLWithPath: "/bin/sh")
		process.arguments = ["-c", command]

		var environment = ProcessInfo.processInfo.environment
		environment.merge(env) { _, new in new }
		process.environment = environment

		let stdout = Pipe()
		let stderr = Pipe()
		process.standardOutput = stdout
		process.standardError = stderr

		guard (try? process.run()) != nil else {
			return ["ERROR"]
		}

		let data = stdout.fileHandleForReading.readDataToEndOfFile()
		let errData = stderr.fileHandleForReading.readDataToEndOfFile()
		process.waitUntilExit()

		guard process.terminationStatus == 0 else {
			write(errData, to: .standardError)
			return ["ERROR"]
		}

		guard let string = String(data: data, encoding: .utf8) else {
			return [""]
		}

		return parse(string)
	}

	/// Split output into lines. A single trailing newline
	/// (from a final `\n`) is dropped; blank lines elsewhere
	/// are kept so a script can emit intentional gaps.
	static func parse(_ text: String) -> [String] {
		var lines = text.components(separatedBy: "\n")
		if lines.last == "" { lines.removeLast() }
		return lines
	}

	/// Write data to the provided handle.
	/// - Parameters:
	///   - data: The data to write.
	///   - handle: The handle to which data should be written.
	static func write(_ data: Data, to handle: FileHandle) {
		handle.write(data)
	}

	/// Write string to the provided handle.
	/// - Parameters:
	///   - string: The string to write.
	///   - handle: The handle to which data should be written.
	static func write(_ string: String, to handle: FileHandle) {
		write(Data(string.utf8), to: handle)
	}
}