-
Notifications
You must be signed in to change notification settings - Fork 30
Expand file tree
/
Copy pathShellCommandRunner.swift
More file actions
90 lines (78 loc) · 2.56 KB
/
ShellCommandRunner.swift
File metadata and controls
90 lines (78 loc) · 2.56 KB
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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
//
// ShellCommandRunner.swift
// ShellKit
//
// Created by Lukas Romsicki on 2022-01-02.
// Copyright © 2022 Shopify. All rights reserved.
//
import Foundation
import Logging
/// Runs a shell command using Bash and returns the messages printed to standard output.
/// - Parameters:
/// - command: The ``ShellCommand`` to run.
/// - log: The logger to use when printing log messages.
/// - standardOutputHandler: Optional closure that runs when a new message is printed to standard output.
/// - standardErrorHandler: Optional closure that runs the a new message is printed to standard error.
/// - Throws: An instance of ``ShellError`` describing the nature of the error.
/// - Returns: A string containing the output of the command.
@discardableResult
public func run(
command: ShellCommand,
timeout: TimeInterval? = nil,
log: Logger? = nil,
standardOutputHandler: StandardOutputHandler? = nil,
standardErrorHandler: StandardErrorHandler? = nil
) throws -> String {
let process = Process()
log?.info("Executing command: \(command.string)")
do {
return try process.launchBash(
command: command.string,
timeout: timeout,
standardOutputHandler: standardOutputHandler,
standardErrorHandler: standardErrorHandler
)
} catch let error as ShellError {
log?.error(
"An error occurred while executing command: \(error.message)",
metadata: [
"command": .string(command.string),
"code": .stringConvertible(error.terminationStatus)
]
)
throw error
}
}
/// Possible shell command output.
public enum ShellOutput {
case standardOutput(String)
case standardError(String)
}
/// Asynchronous version of `run` that returns an `AsyncThrowingStream` that contains the output of the command.
/// - Parameters:
/// - command: The ``ShellCommand`` to run.
/// - log: The logger to use when printing log messages.
/// - Returns: An `AsyncThrowingStream` containing the messages printed to standard output and standard error.
public func runAsync(command: ShellCommand, log: Logger? = nil) -> AsyncThrowingStream<ShellOutput, Error> {
AsyncThrowingStream { continuation in
let standardOutputHandler: StandardOutputHandler = { output in
continuation.yield(.standardOutput(output))
}
let standardErrorHandler: StandardErrorHandler = { output in
continuation.yield(.standardError(output))
}
Task {
do {
try run(
command: command,
log: log,
standardOutputHandler: standardOutputHandler,
standardErrorHandler: standardErrorHandler
)
continuation.finish()
} catch {
continuation.finish(throwing: error)
}
}
}
}