Skip to content

Commit 0515b3f

Browse files
created a new task for execution - Log Task Executor
1 parent 968a4f4 commit 0515b3f

File tree

6 files changed

+109
-61
lines changed

6 files changed

+109
-61
lines changed

Source/Swiftline/CommandExecutor.swift

Lines changed: 73 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import Foundation
1010

1111

12-
typealias ExecutorReturnValue = (status: Int, standardOutput: TaskPipe, standardError: TaskPipe)
12+
typealias ExecutorReturnValue = (status: Int, standardOutput: String, standardError: String)
1313

1414
class CommandExecutor {
1515

@@ -25,14 +25,21 @@ protocol TaskExecutor {
2525
func execute(_ commandParts: [String]) -> ExecutorReturnValue
2626
}
2727

28+
extension TaskExecutor {
29+
func readPipes(stdoutPipe: Pipe, stderrPipe: Pipe) -> (stdout: String, stderr: String) {
30+
let stdout = readPipe(stdoutPipe).trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
31+
let stderr = readPipe(stderrPipe).trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
32+
33+
return (stdout, stderr)
34+
}
35+
}
36+
2837
class DryTaskExecutor: TaskExecutor {
2938

3039
func execute(_ commandParts: [String]) -> ExecutorReturnValue {
3140
let command = commandParts.joined(separator: " ")
3241
PromptSettings.print("Executed command '\(command)'")
33-
return (0,
34-
Dryipe(dataToReturn: "".data(using: String.Encoding.utf8)!),
35-
Dryipe(dataToReturn: "".data(using: String.Encoding.utf8)!))
42+
return (0, "", "")
3643
}
3744
}
3845

@@ -52,7 +59,8 @@ class ActualTaskExecutor: TaskExecutor {
5259
task.launch()
5360
task.waitUntilExit()
5461

55-
return (Int(task.terminationStatus), stdoutPipe, stderrPipe)
62+
let (stdout, stderr) = readPipes(stdoutPipe: stdoutPipe, stderrPipe: stderrPipe)
63+
return (Int(task.terminationStatus), stdout, stderr)
5664
}
5765
}
5866

@@ -72,15 +80,71 @@ class InteractiveTaskExecutor: TaskExecutor {
7280
posix_spawn_file_actions_addclose(&childFDActions, outputPipe[0])
7381
posix_spawn_file_actions_addclose(&childFDActions, outputPipe[1])
7482

75-
7683
var pid: pid_t = 0
7784
let result = posix_spawn(&pid, argv[0], &childFDActions, nil, argv + [nil], nil)
7885

79-
let emptyPipe = Dryipe(dataToReturn: "".data(using: String.Encoding.utf8)!)
80-
return (Int(result), emptyPipe, emptyPipe)
86+
return (Int(result), "", "")
8187
}
8288
}
8389

90+
class LogTaskExecutor: TaskExecutor {
91+
let logPath: String
92+
93+
init(logPath: String) {
94+
self.logPath = logPath
95+
}
96+
97+
func execute(_ commandParts: [String]) -> ExecutorReturnValue {
98+
let argv: [UnsafeMutablePointer<CChar>?] = commandParts.map{ $0.withCString(strdup) }
99+
defer { for case let arg? in argv { free(arg) } }
100+
101+
var pid: pid_t = 0
102+
var childFDActions: posix_spawn_file_actions_t? = nil
103+
let outputPipe: Int32 = 69
104+
let outerrPipe: Int32 = 70
105+
106+
posix_spawn_file_actions_init(&childFDActions)
107+
posix_spawn_file_actions_addopen(&childFDActions, outputPipe, stdoutLogPath, O_CREAT | O_TRUNC | O_WRONLY, ~0)
108+
posix_spawn_file_actions_addopen(&childFDActions, outerrPipe, stderrLogPath, O_CREAT | O_TRUNC | O_WRONLY, ~0)
109+
posix_spawn_file_actions_adddup2(&childFDActions, outputPipe, 1)
110+
posix_spawn_file_actions_adddup2(&childFDActions, outerrPipe, 2)
111+
112+
var result = posix_spawn(&pid, argv[0], &childFDActions, nil, argv + [nil], nil)
113+
guard result == 0 else { return (Int(1), "", "") }
114+
115+
waitpid(pid, &result, 0)
116+
posix_spawn_file_actions_addclose(&childFDActions, outputPipe)
117+
posix_spawn_file_actions_addclose(&childFDActions, outerrPipe)
118+
posix_spawn_file_actions_destroy(&childFDActions)
119+
120+
let (stdout, stderr) = read(outputPath: stdoutLogPath, outerrPath: stderrLogPath)
121+
removeFiles(stdoutLogPath, stderrLogPath)
122+
write(atPath: logPath, content: "\(stdout)\n\(stderr)")
123+
124+
return (Int(0), stdout, stderr)
125+
}
126+
127+
private var stdoutLogPath: String { return "\(logPath)-stdout.log" }
128+
private var stderrLogPath: String { return "\(logPath)-stderr.log" }
129+
130+
private func removeFiles(_ files: String...) {
131+
files.forEach { file in
132+
try? FileManager.default.removeItem(atPath: file)
133+
}
134+
}
135+
136+
private func read(outputPath: String, outerrPath: String) -> (stdout: String, stderr: String) {
137+
let stdout = String(data: FileManager.default.contents(atPath: outputPath) ?? Data(), encoding: .utf8) ?? ""
138+
let stderr = String(data: FileManager.default.contents(atPath: outerrPath) ?? Data(), encoding: .utf8) ?? ""
139+
140+
return (stdout, stderr)
141+
}
142+
143+
private func write(atPath path: String, content: String) {
144+
FileManager.default.createFile(atPath: path, contents: content.data(using: .utf8), attributes: nil)
145+
}
146+
}
147+
84148
class DummyTaskExecutor: TaskExecutor {
85149

86150
var commandsExecuted: [String] = []
@@ -99,8 +163,6 @@ class DummyTaskExecutor: TaskExecutor {
99163
let command = commandParts.joined(separator: " ")
100164
commandsExecuted.append(command)
101165

102-
return (statusCodeToReturn,
103-
Dryipe(dataToReturn: outputToReturn.data(using: String.Encoding.utf8)!),
104-
Dryipe(dataToReturn: errorToReturn.data(using: String.Encoding.utf8)!))
166+
return (statusCodeToReturn, outputToReturn, errorToReturn)
105167
}
106168
}

Source/Swiftline/Runner.swift

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class 🏃{
1313

1414
class func runWithoutCapture(_ command: String) -> Int {
1515
let initalSettings = RunSettings()
16-
initalSettings.interactive = true
16+
initalSettings.execution = .interactive
1717
return run(command, args: [], settings: initalSettings).exitStatus
1818
}
1919

@@ -60,12 +60,15 @@ class 🏃{
6060

6161
echoCommand(commandParts, settings: settings)
6262

63-
if settings.dryRun {
63+
switch settings.execution {
64+
case .default:
65+
result = executeActualCommand(commandParts)
66+
case .dryRun:
6467
result = executeDryCommand(commandParts)
65-
} else if settings.interactive {
68+
case .interactive:
6669
result = executeIneractiveCommand(commandParts)
67-
} else {
68-
result = executeActualCommand(commandParts)
70+
case .log(let path):
71+
result = executeLogCommand(commandParts, logPath: path)
6972
}
7073

7174
echoResult(result, settings: settings)
@@ -81,15 +84,16 @@ class 🏃{
8184
return execute(commandParts, withExecutor: InteractiveTaskExecutor())
8285
}
8386

87+
fileprivate class func executeLogCommand(_ commandParts: [String], logPath: String) -> RunResults {
88+
return execute(commandParts, withExecutor: LogTaskExecutor(logPath: logPath))
89+
}
90+
8491
fileprivate class func executeActualCommand(_ commandParts: [String]) -> RunResults {
8592
return execute(commandParts, withExecutor: CommandExecutor.currentTaskExecutor)
8693
}
8794

8895
fileprivate class func execute(_ commandParts: [String], withExecutor executor: TaskExecutor) -> RunResults {
89-
let (status, stdoutPipe, stderrPipe) = executor.execute(commandParts)
90-
91-
let stdout = readPipe(stdoutPipe).trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
92-
let stderr = readPipe(stderrPipe).trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
96+
let (status, stdout, stderr) = executor.execute(commandParts)
9397
return RunResults(exitStatus: status, stdout: stdout, stderr: stderr)
9498
}
9599

Source/Swiftline/RunnerSettings.swift

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,28 @@
1010
/// Settings to costumize the run function
1111
public class RunSettings {
1212

13-
/// If set to true, the command wont be run on the system, the stdout will contain the command executed
14-
public var dryRun = false
15-
1613
/// Which parts of the command to be echoed during execution
1714
public var echo = EchoSettings.None
1815

16+
/// Wich executed will be used to run the commands
17+
public var execution = ExecutionSettings.default
18+
}
19+
20+
21+
/// Execution settings
22+
public enum ExecutionSettings {
23+
24+
/// Run the command on the system
25+
case `default`
26+
27+
/// Wont be run on the system, the stdout will contain the command executed
28+
case dryRun
29+
1930
/// Run the command in interactive mode; output wont be captured
20-
public var interactive = false
31+
case interactive
32+
33+
/// Run the command on the system and the output will be captured in a log file
34+
case log(file: String)
2135
}
2236

2337

Swiftline.xcodeproj/project.pbxproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10-
8B6AA75F23042E370008C1D0 /* SwiftlineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B6AA75E23042E370008C1D0 /* SwiftlineTests.swift */; };
1110
8B6AA76123042E370008C1D0 /* Swiftline.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = "_____Product_Swiftline" /* Swiftline.framework */; };
1211
8B6AA78523042E910008C1D0 /* ArgsParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B6AA76923042E910008C1D0 /* ArgsParser.swift */; };
1312
8B6AA78623042E910008C1D0 /* AskSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B6AA76A23042E910008C1D0 /* AskSettings.swift */; };
@@ -64,7 +63,6 @@
6463

6564
/* Begin PBXFileReference section */
6665
8B6AA75C23042E370008C1D0 /* SwiftlineTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftlineTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
67-
8B6AA75E23042E370008C1D0 /* SwiftlineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftlineTests.swift; sourceTree = "<group>"; };
6866
8B6AA76023042E370008C1D0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
6967
8B6AA76923042E910008C1D0 /* ArgsParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArgsParser.swift; sourceTree = "<group>"; };
7068
8B6AA76A23042E910008C1D0 /* AskSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AskSettings.swift; sourceTree = "<group>"; };
@@ -145,7 +143,6 @@
145143
8B6AA7AD23042EDE0008C1D0 /* ENVTests.swift */,
146144
8B6AA7A923042EDE0008C1D0 /* GlobTests.swift */,
147145
8B6AA7A123042EDD0008C1D0 /* RunnerTests.swift */,
148-
8B6AA75E23042E370008C1D0 /* SwiftlineTests.swift */,
149146
8B6AA76023042E370008C1D0 /* Info.plist */,
150147
);
151148
path = SwiftlineTests;
@@ -328,7 +325,6 @@
328325
isa = PBXSourcesBuildPhase;
329326
buildActionMask = 2147483647;
330327
files = (
331-
8B6AA75F23042E370008C1D0 /* SwiftlineTests.swift in Sources */,
332328
8B6AA7B523042EDE0008C1D0 /* AgreeTests.swift in Sources */,
333329
8B6AA7AE23042EDE0008C1D0 /* RunnerTests.swift in Sources */,
334330
8B6AA7B923042EDE0008C1D0 /* ChooseTests.swift in Sources */,
@@ -563,6 +559,8 @@
563559
isa = XCBuildConfiguration;
564560
baseConfigurationReference = __PBXFileRef_Swiftline.xcodeproj/Configs/Project.xcconfig /* Swiftline.xcodeproj/Configs/Project.xcconfig */;
565561
buildSettings = {
562+
MACOSX_DEPLOYMENT_TARGET = 10.13;
563+
SDKROOT = macosx;
566564
SWIFT_VERSION = 5.0;
567565
};
568566
name = Release;
@@ -571,6 +569,8 @@
571569
isa = XCBuildConfiguration;
572570
baseConfigurationReference = __PBXFileRef_Swiftline.xcodeproj/Configs/Project.xcconfig /* Swiftline.xcodeproj/Configs/Project.xcconfig */;
573571
buildSettings = {
572+
MACOSX_DEPLOYMENT_TARGET = 10.13;
573+
SDKROOT = macosx;
574574
SWIFT_VERSION = 5.0;
575575
};
576576
name = Debug;

SwiftlineTests/RunnerTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ class RunnerTests: QuickSpec {
176176
it("execute ls") {
177177
CommandExecutor.currentTaskExecutor = ActualTaskExecutor()
178178
let res = 🏃.run("ls -all") {
179-
$0.dryRun = true
179+
$0.execution = .dryRun
180180
}
181181

182182
expect(res.exitStatus).to(equal(0))

SwiftlineTests/SwiftlineTests.swift

Lines changed: 0 additions & 32 deletions
This file was deleted.

0 commit comments

Comments
 (0)