Skip to content

Commit

Permalink
created a new task for execution - Log Task Executor
Browse files Browse the repository at this point in the history
  • Loading branch information
miguelangel-dev committed Sep 19, 2019
1 parent 968a4f4 commit 0515b3f
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 61 deletions.
84 changes: 73 additions & 11 deletions Source/Swiftline/CommandExecutor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import Foundation


typealias ExecutorReturnValue = (status: Int, standardOutput: TaskPipe, standardError: TaskPipe)
typealias ExecutorReturnValue = (status: Int, standardOutput: String, standardError: String)

class CommandExecutor {

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

extension TaskExecutor {
func readPipes(stdoutPipe: Pipe, stderrPipe: Pipe) -> (stdout: String, stderr: String) {
let stdout = readPipe(stdoutPipe).trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
let stderr = readPipe(stderrPipe).trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)

return (stdout, stderr)
}
}

class DryTaskExecutor: TaskExecutor {

func execute(_ commandParts: [String]) -> ExecutorReturnValue {
let command = commandParts.joined(separator: " ")
PromptSettings.print("Executed command '\(command)'")
return (0,
Dryipe(dataToReturn: "".data(using: String.Encoding.utf8)!),
Dryipe(dataToReturn: "".data(using: String.Encoding.utf8)!))
return (0, "", "")
}
}

Expand All @@ -52,7 +59,8 @@ class ActualTaskExecutor: TaskExecutor {
task.launch()
task.waitUntilExit()

return (Int(task.terminationStatus), stdoutPipe, stderrPipe)
let (stdout, stderr) = readPipes(stdoutPipe: stdoutPipe, stderrPipe: stderrPipe)
return (Int(task.terminationStatus), stdout, stderr)
}
}

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


var pid: pid_t = 0
let result = posix_spawn(&pid, argv[0], &childFDActions, nil, argv + [nil], nil)

let emptyPipe = Dryipe(dataToReturn: "".data(using: String.Encoding.utf8)!)
return (Int(result), emptyPipe, emptyPipe)
return (Int(result), "", "")
}
}

class LogTaskExecutor: TaskExecutor {
let logPath: String

init(logPath: String) {
self.logPath = logPath
}

func execute(_ commandParts: [String]) -> ExecutorReturnValue {
let argv: [UnsafeMutablePointer<CChar>?] = commandParts.map{ $0.withCString(strdup) }
defer { for case let arg? in argv { free(arg) } }

var pid: pid_t = 0
var childFDActions: posix_spawn_file_actions_t? = nil
let outputPipe: Int32 = 69
let outerrPipe: Int32 = 70

posix_spawn_file_actions_init(&childFDActions)
posix_spawn_file_actions_addopen(&childFDActions, outputPipe, stdoutLogPath, O_CREAT | O_TRUNC | O_WRONLY, ~0)
posix_spawn_file_actions_addopen(&childFDActions, outerrPipe, stderrLogPath, O_CREAT | O_TRUNC | O_WRONLY, ~0)
posix_spawn_file_actions_adddup2(&childFDActions, outputPipe, 1)
posix_spawn_file_actions_adddup2(&childFDActions, outerrPipe, 2)

var result = posix_spawn(&pid, argv[0], &childFDActions, nil, argv + [nil], nil)
guard result == 0 else { return (Int(1), "", "") }

waitpid(pid, &result, 0)
posix_spawn_file_actions_addclose(&childFDActions, outputPipe)
posix_spawn_file_actions_addclose(&childFDActions, outerrPipe)
posix_spawn_file_actions_destroy(&childFDActions)

let (stdout, stderr) = read(outputPath: stdoutLogPath, outerrPath: stderrLogPath)
removeFiles(stdoutLogPath, stderrLogPath)
write(atPath: logPath, content: "\(stdout)\n\(stderr)")

return (Int(0), stdout, stderr)
}

private var stdoutLogPath: String { return "\(logPath)-stdout.log" }
private var stderrLogPath: String { return "\(logPath)-stderr.log" }

private func removeFiles(_ files: String...) {
files.forEach { file in
try? FileManager.default.removeItem(atPath: file)
}
}

private func read(outputPath: String, outerrPath: String) -> (stdout: String, stderr: String) {
let stdout = String(data: FileManager.default.contents(atPath: outputPath) ?? Data(), encoding: .utf8) ?? ""
let stderr = String(data: FileManager.default.contents(atPath: outerrPath) ?? Data(), encoding: .utf8) ?? ""

return (stdout, stderr)
}

private func write(atPath path: String, content: String) {
FileManager.default.createFile(atPath: path, contents: content.data(using: .utf8), attributes: nil)
}
}

class DummyTaskExecutor: TaskExecutor {

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

return (statusCodeToReturn,
Dryipe(dataToReturn: outputToReturn.data(using: String.Encoding.utf8)!),
Dryipe(dataToReturn: errorToReturn.data(using: String.Encoding.utf8)!))
return (statusCodeToReturn, outputToReturn, errorToReturn)
}
}
22 changes: 13 additions & 9 deletions Source/Swiftline/Runner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class 🏃{

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

Expand Down Expand Up @@ -60,12 +60,15 @@ class 🏃{

echoCommand(commandParts, settings: settings)

if settings.dryRun {
switch settings.execution {
case .default:
result = executeActualCommand(commandParts)
case .dryRun:
result = executeDryCommand(commandParts)
} else if settings.interactive {
case .interactive:
result = executeIneractiveCommand(commandParts)
} else {
result = executeActualCommand(commandParts)
case .log(let path):
result = executeLogCommand(commandParts, logPath: path)
}

echoResult(result, settings: settings)
Expand All @@ -81,15 +84,16 @@ class 🏃{
return execute(commandParts, withExecutor: InteractiveTaskExecutor())
}

fileprivate class func executeLogCommand(_ commandParts: [String], logPath: String) -> RunResults {
return execute(commandParts, withExecutor: LogTaskExecutor(logPath: logPath))
}

fileprivate class func executeActualCommand(_ commandParts: [String]) -> RunResults {
return execute(commandParts, withExecutor: CommandExecutor.currentTaskExecutor)
}

fileprivate class func execute(_ commandParts: [String], withExecutor executor: TaskExecutor) -> RunResults {
let (status, stdoutPipe, stderrPipe) = executor.execute(commandParts)

let stdout = readPipe(stdoutPipe).trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
let stderr = readPipe(stderrPipe).trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
let (status, stdout, stderr) = executor.execute(commandParts)
return RunResults(exitStatus: status, stdout: stdout, stderr: stderr)
}

Expand Down
22 changes: 18 additions & 4 deletions Source/Swiftline/RunnerSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,28 @@
/// Settings to costumize the run function
public class RunSettings {

/// If set to true, the command wont be run on the system, the stdout will contain the command executed
public var dryRun = false

/// Which parts of the command to be echoed during execution
public var echo = EchoSettings.None

/// Wich executed will be used to run the commands
public var execution = ExecutionSettings.default
}


/// Execution settings
public enum ExecutionSettings {

/// Run the command on the system
case `default`

/// Wont be run on the system, the stdout will contain the command executed
case dryRun

/// Run the command in interactive mode; output wont be captured
public var interactive = false
case interactive

/// Run the command on the system and the output will be captured in a log file
case log(file: String)
}


Expand Down
8 changes: 4 additions & 4 deletions Swiftline.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
objects = {

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

/* Begin PBXFileReference section */
8B6AA75C23042E370008C1D0 /* SwiftlineTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftlineTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
8B6AA75E23042E370008C1D0 /* SwiftlineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftlineTests.swift; sourceTree = "<group>"; };
8B6AA76023042E370008C1D0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
8B6AA76923042E910008C1D0 /* ArgsParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArgsParser.swift; sourceTree = "<group>"; };
8B6AA76A23042E910008C1D0 /* AskSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AskSettings.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -145,7 +143,6 @@
8B6AA7AD23042EDE0008C1D0 /* ENVTests.swift */,
8B6AA7A923042EDE0008C1D0 /* GlobTests.swift */,
8B6AA7A123042EDD0008C1D0 /* RunnerTests.swift */,
8B6AA75E23042E370008C1D0 /* SwiftlineTests.swift */,
8B6AA76023042E370008C1D0 /* Info.plist */,
);
path = SwiftlineTests;
Expand Down Expand Up @@ -328,7 +325,6 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
8B6AA75F23042E370008C1D0 /* SwiftlineTests.swift in Sources */,
8B6AA7B523042EDE0008C1D0 /* AgreeTests.swift in Sources */,
8B6AA7AE23042EDE0008C1D0 /* RunnerTests.swift in Sources */,
8B6AA7B923042EDE0008C1D0 /* ChooseTests.swift in Sources */,
Expand Down Expand Up @@ -563,6 +559,8 @@
isa = XCBuildConfiguration;
baseConfigurationReference = __PBXFileRef_Swiftline.xcodeproj/Configs/Project.xcconfig /* Swiftline.xcodeproj/Configs/Project.xcconfig */;
buildSettings = {
MACOSX_DEPLOYMENT_TARGET = 10.13;
SDKROOT = macosx;
SWIFT_VERSION = 5.0;
};
name = Release;
Expand All @@ -571,6 +569,8 @@
isa = XCBuildConfiguration;
baseConfigurationReference = __PBXFileRef_Swiftline.xcodeproj/Configs/Project.xcconfig /* Swiftline.xcodeproj/Configs/Project.xcconfig */;
buildSettings = {
MACOSX_DEPLOYMENT_TARGET = 10.13;
SDKROOT = macosx;
SWIFT_VERSION = 5.0;
};
name = Debug;
Expand Down
2 changes: 1 addition & 1 deletion SwiftlineTests/RunnerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ class RunnerTests: QuickSpec {
it("execute ls") {
CommandExecutor.currentTaskExecutor = ActualTaskExecutor()
let res = 🏃.run("ls -all") {
$0.dryRun = true
$0.execution = .dryRun
}

expect(res.exitStatus).to(equal(0))
Expand Down
32 changes: 0 additions & 32 deletions SwiftlineTests/SwiftlineTests.swift

This file was deleted.

0 comments on commit 0515b3f

Please sign in to comment.