From 683d9d00269512cb9dc12492498209fbe543722b Mon Sep 17 00:00:00 2001 From: Stephen Martinez Date: Mon, 16 Dec 2019 21:16:50 -0800 Subject: [PATCH] [CPBugFix-002][UpdatedDocs] Fixed the bug, updated some docs, and re-organized a bit. --- .../xcschemes/commitPrefix.xcscheme | 10 + Package.swift | 12 +- Sources/CommitPrefix/CPFileHandler.swift | 16 +- Sources/CommitPrefix/CPInteractor.swift | 26 +-- ...{CommitPrefixModel.swift => CPModel.swift} | 40 ++-- Sources/CommitPrefix/CommitMessageHook.swift | 218 ------------------ Sources/CommitPrefix/Constants.swift | 16 +- .../{ => Error+Debug}/CPDebugPrint.swift | 2 +- .../{ => Error+Debug}/CPError.swift | 0 .../CommitPrefix/Hook/CommitMessageHook.swift | 117 ++++++++++ .../Hook/CommitMessageHookContents.swift | 170 ++++++++++++++ .../Interface/ArgumentBuilder.swift | 133 +++++++++++ .../{ => Interface}/CLIArguments.swift | 111 +-------- .../CommitPrefix/{ => Utilities}/Shell.swift | 0 .../{ => Utilities}/String+Extensions.swift | 2 +- 15 files changed, 490 insertions(+), 383 deletions(-) rename Sources/CommitPrefix/{CommitPrefixModel.swift => CPModel.swift} (83%) delete mode 100644 Sources/CommitPrefix/CommitMessageHook.swift rename Sources/CommitPrefix/{ => Error+Debug}/CPDebugPrint.swift (93%) rename Sources/CommitPrefix/{ => Error+Debug}/CPError.swift (100%) create mode 100644 Sources/CommitPrefix/Hook/CommitMessageHook.swift create mode 100644 Sources/CommitPrefix/Hook/CommitMessageHookContents.swift create mode 100644 Sources/CommitPrefix/Interface/ArgumentBuilder.swift rename Sources/CommitPrefix/{ => Interface}/CLIArguments.swift (60%) rename Sources/CommitPrefix/{ => Utilities}/Shell.swift (100%) rename Sources/CommitPrefix/{ => Utilities}/String+Extensions.swift (98%) diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/commitPrefix.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/commitPrefix.xcscheme index fd2af23..5b968ef 100644 --- a/.swiftpm/xcode/xcshareddata/xcschemes/commitPrefix.xcscheme +++ b/.swiftpm/xcode/xcshareddata/xcschemes/commitPrefix.xcscheme @@ -52,6 +52,16 @@ ReferencedContainer = "container:"> + + + + String { + func outputPrefixes() throws -> String { try cpInteractor.outputPrefixes() } - public func viewState() throws -> String { + func viewState() throws -> String { let cpState = try cpInteractor.getCommitPrefixState() switch cpState.mode { case .normal: @@ -61,19 +61,19 @@ public struct CPFileHandler { } } - public func deletePrefixes() throws -> String { + func deletePrefixes() throws -> String { try cpInteractor.deletePrefixes() } - public func writeNew(prefixes rawValue: String) throws -> String { + func writeNew(prefixes rawValue: String) throws -> String { try cpInteractor.writeNew(prefixes: rawValue) } - public func activateBranchMode(with validator: String) throws -> String { + func activateBranchMode(with validator: String) throws -> String { try cpInteractor.activateBranchMode(with: validator) } - public func activateNormalMode() throws -> String { + func activateNormalMode() throws -> String { try cpInteractor.activateNormalMode() } diff --git a/Sources/CommitPrefix/CPInteractor.swift b/Sources/CommitPrefix/CPInteractor.swift index 99ed5fd..35990ee 100644 --- a/Sources/CommitPrefix/CPInteractor.swift +++ b/Sources/CommitPrefix/CPInteractor.swift @@ -30,7 +30,7 @@ import Files struct CPInteractor { private let commitPrefixFile: File - private let commitPrefixModel: CommitPrefixModel + private let commitPrefixModel: CPModel init (gitDirectory: Folder) throws { let (commitPrefixFile, commitPrefixModel) = try Self.build(using: gitDirectory) @@ -38,14 +38,14 @@ struct CPInteractor { self.commitPrefixModel = commitPrefixModel } - private static func build(using gitDirectory: Folder) throws -> (File, CommitPrefixModel) { + private static func build(using gitDirectory: Folder) throws -> (File, CPModel) { do { - let initialModelData = try JSONEncoder().encode(CommitPrefixModel.empty()) + let initialModelData = try JSONEncoder().encode(CPModel.empty()) let cpFile = try gitDirectory.createFileIfNeeded( withName: FileName.commitPrefix, contents: initialModelData) let cpFileData = try cpFile.read() - let cpModel = try JSONDecoder().decode(CommitPrefixModel.self, from: cpFileData) + let cpModel = try JSONDecoder().decode(CPModel.self, from: cpFileData) return (cpFile, cpModel) } catch { cpDebugPrint(error) @@ -53,7 +53,7 @@ struct CPInteractor { } } - private func saveCommitPrefix(model: CommitPrefixModel) throws { + private func saveCommitPrefix(model: CPModel) throws { do { let jsonEncoder = JSONEncoder() let modelData = try jsonEncoder.encode(model) @@ -99,7 +99,7 @@ struct CPInteractor { return validator } - public func outputPrefixes() throws -> String { + func outputPrefixes() throws -> String { switch commitPrefixModel.prefixMode { case .normal: return commitPrefixModel.prefixes.joined() @@ -111,10 +111,10 @@ struct CPInteractor { } } - public func getCommitPrefixState() throws -> CommitPrefixState { + func getCommitPrefixState() throws -> CPState { switch commitPrefixModel.prefixMode { case .normal: - return CommitPrefixState( + return CPState( mode: .normal, branchPrefixes: [], normalPrefixes: commitPrefixModel.prefixes @@ -123,7 +123,7 @@ struct CPInteractor { let retrievedBranchPrefixes = try branchPrefixes() let branchPrefixes = retrievedBranchPrefixes.map { "[\($0)]" } let normalPrefixes = commitPrefixModel.prefixes - return CommitPrefixState( + return CPState( mode: .branchParse, branchPrefixes: branchPrefixes, normalPrefixes: normalPrefixes @@ -131,27 +131,27 @@ struct CPInteractor { } } - public func deletePrefixes() throws -> String { + func deletePrefixes() throws -> String { let newModel = commitPrefixModel.updated(with: []) try saveCommitPrefix(model: newModel) return "CommitPrefix DELETED" } - public func writeNew(prefixes rawValue: String) throws -> String { + func writeNew(prefixes rawValue: String) throws -> String { let newPrefixes = prefixFormatter(rawValue) let newModel = commitPrefixModel.updated(with: newPrefixes) try saveCommitPrefix(model: newModel) return "CommitPrefix STORED \(newPrefixes.joined())" } - public func activateBranchMode(with validator: String) throws -> String { + func activateBranchMode(with validator: String) throws -> String { let formattedValidator = try validatorFormatter(validator) let newModel = commitPrefixModel.updatedAsBranchMode(with: formattedValidator) try saveCommitPrefix(model: newModel) return "CommitPrefix MODE BRANCH_PARSE \(formattedValidator)" } - public func activateNormalMode() throws -> String { + func activateNormalMode() throws -> String { switch commitPrefixModel.prefixMode { case .normal: return "CommitPrefix already in MODE NORMAL" diff --git a/Sources/CommitPrefix/CommitPrefixModel.swift b/Sources/CommitPrefix/CPModel.swift similarity index 83% rename from Sources/CommitPrefix/CommitPrefixModel.swift rename to Sources/CommitPrefix/CPModel.swift index 2e3327b..20a8ef5 100644 --- a/Sources/CommitPrefix/CommitPrefixModel.swift +++ b/Sources/CommitPrefix/CPModel.swift @@ -1,5 +1,5 @@ // -// CommitPrefixModel.swift +// CPModel.swift // commitPrefix // // MIT License @@ -26,14 +26,18 @@ import Foundation -public enum PrefixMode: Int { - +enum PrefixMode: Int { case normal case branchParse - } -public struct CommitPrefixModel: Codable { +struct CPState { + let mode: PrefixMode + let branchPrefixes: [String] + let normalPrefixes: [String] +} + +struct CPModel: Codable { let prefixMode: PrefixMode let branchValidator: String? @@ -58,7 +62,7 @@ public struct CommitPrefixModel: Codable { self.prefixes = prefixes } - public init(from decoder: Decoder) throws { + init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) let prefixModeRawValue = try values.decode(Int.self, forKey: .prefixMode) self.prefixMode = PrefixMode(rawValue: prefixModeRawValue) ?? PrefixMode.normal @@ -66,39 +70,39 @@ public struct CommitPrefixModel: Codable { self.prefixes = try values.decode([String].self, forKey: .prefixes) } - public func encode(to encoder: Encoder) throws { + func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(prefixMode.rawValue, forKey: .prefixMode) try container.encodeIfPresent(branchValidator, forKey: .branchValidator) try container.encode(prefixes, forKey: .prefixes) } - static public func empty() -> CommitPrefixModel { - return CommitPrefixModel( + static func empty() -> CPModel { + return CPModel( prefixMode: .normal, branchValidator: nil, prefixes: [] ) } - public func updated(with newPrefixes: [String]) -> CommitPrefixModel { - return CommitPrefixModel( + func updated(with newPrefixes: [String]) -> CPModel { + return CPModel( prefixMode: prefixMode, branchValidator: branchValidator, prefixes: newPrefixes ) } - public func updatedAsBranchMode(with newBranchValidator: String) -> CommitPrefixModel { - return CommitPrefixModel( + func updatedAsBranchMode(with newBranchValidator: String) -> CPModel { + return CPModel( prefixMode: .branchParse, branchValidator: newBranchValidator, prefixes: prefixes ) } - public func updatedAsNormalMode() -> CommitPrefixModel { - return CommitPrefixModel( + func updatedAsNormalMode() -> CPModel { + return CPModel( prefixMode: .normal, branchValidator: nil, prefixes: prefixes @@ -106,9 +110,3 @@ public struct CommitPrefixModel: Codable { } } - -public struct CommitPrefixState { - let mode: PrefixMode - let branchPrefixes: [String] - let normalPrefixes: [String] -} diff --git a/Sources/CommitPrefix/CommitMessageHook.swift b/Sources/CommitPrefix/CommitMessageHook.swift deleted file mode 100644 index 4941cc8..0000000 --- a/Sources/CommitPrefix/CommitMessageHook.swift +++ /dev/null @@ -1,218 +0,0 @@ -// -// CommitMessageHook.swift -// commitPrefix -// -// MIT License -// -// Copyright (c) 2019 STEPHEN L. MARTINEZ -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -import Files -import Foundation - -public struct CommitMessageHook { - - private let fileIdentifier = "Created by CommitPrefix \(CPInfo.version)" - - private let hooksDirectory: Folder - - public init(gitDirectory: Folder) throws { - guard let hooksDirectory = try? gitDirectory.subfolder(named: FolderName.hooks) else { - throw CPError.directoryNotFound(name: FolderName.hooks, path: gitDirectory.path) - } - self.hooksDirectory = hooksDirectory - } - - public static func findOrCreate(with gitDirectory: Folder) throws { - let cpHook = try CommitMessageHook(gitDirectory: gitDirectory) - try cpHook.locateOrCreateHook() - } - - private var currentDate: String { - let formatter = DateFormatter() - formatter.dateFormat = "MM/dd/yyyy" - return formatter.string(from: Date()) - } - - private var contents: String { """ - #!/usr/bin/swift - // - // Commit-msg - // - // \(fileIdentifier) on \(currentDate) - // - - import Foundation - - enum IOError: Error { - - case invalidArgument - case overwriteError - - var message: String { - switch self { - case .invalidArgument: - return "Intended to recieve .git/COMMIT_EDITMSG arg" - case .overwriteError: - return "There was an error writting to the commit message" - } - } - - } - - struct IOCommitPrefix { - - let commitMsgPath: String - - init(filePath: [String] = Array(CommandLine.arguments.dropFirst())) throws { - guard let firstArg = filePath.first else { throw IOError.invalidArgument } - self.commitMsgPath = firstArg - } - - func getPrefixes() -> String { - let readProcess = Process() - readProcess.launchPath = "/usr/bin/env" - readProcess.arguments = ["commitPrefix", "-o"] - - let pipe = Pipe() - readProcess.standardOutput = pipe - readProcess.launch() - - readProcess.waitUntilExit() - - let data = pipe.fileHandleForReading.readDataToEndOfFile() - let contents = String(data: data, encoding: .utf8) - - return contents ?? "" - } - - func getCommitMessage() -> String { - let readProcess = Process() - readProcess.launchPath = "/usr/bin/env" - readProcess.arguments = ["cat", commitMsgPath] - - let pipe = Pipe() - readProcess.standardOutput = pipe - readProcess.launch() - - readProcess.waitUntilExit() - - let data = pipe.fileHandleForReading.readDataToEndOfFile() - let contents = String(data: data, encoding: .utf8) - - return contents ?? "" - } - - func overwriteContents(with contents: String) throws { - do { - try contents.write(toFile: commitMsgPath, atomically: true, encoding: .utf8) - } catch { - throw IOError.overwriteError - } - } - - } - - - do { - - let ioCommitPrefix = try IOCommitPrefix() - - let prefixes = ioCommitPrefix.getPrefixes() - .trimmingCharacters(in: .newlines) - - let commitMessage = ioCommitPrefix.getCommitMessage() - .trimmingCharacters(in: .newlines) - - let newCommitMessage = [prefixes, commitMessage].joined(separator: " ") - try ioCommitPrefix.overwriteContents(with: newCommitMessage) - - } catch let ioError as IOError { - - print(ioError) - - } - - """ - } - - private func getCommitHookFile() throws -> File? { - - guard let foundCommitHookFile = try? hooksDirectory.file(named: FileName.commitMessage) else { - - do { - let commitHookFile = try hooksDirectory.createFile(named: FileName.commitMessage) - try commitHookFile.write(contents, encoding: .utf8) - cpDebugPrint(commitHookFile.path) - Shell.makeExecutable(commitHookFile.path) - } catch { - throw CPError.hookReadWriteError - } - - return nil - - } - - return foundCommitHookFile - - } - - private func overwriteCommitHook(_ commitHookFile: File) throws { - print("There seems to be an existing commit-msg found in the hooks directory") - print("Would you like to overwrite? [y/n]") - let answer = readLine() ?? "" - - switch answer { - - case "y": - print("Overwritting existing commit-msg with generated hook") - - do { - // TODO: - Theres a case where the file is not executable in the first place this will not correct that - try commitHookFile.write(contents, encoding: .utf8) - } catch { - throw CPError.hookReadWriteError - } - - case "n": - throw CPTermination.overwriteCancelled - - default: - throw CPTermination.expectedYesOrNo - - } - } - - private func hookIsCommitPrefix(_ hookFile: File) throws -> Bool { - - guard let hookContents = try? hookFile.readAsString(encodedAs: .utf8) else { - throw CPError.hookReadWriteError - } - - return hookContents.contains(fileIdentifier) - } - - private func locateOrCreateHook() throws { - guard let foundCommitHookFile = try getCommitHookFile() else { return } - guard try !hookIsCommitPrefix(foundCommitHookFile) else { return } - try overwriteCommitHook(foundCommitHookFile) - } - -} diff --git a/Sources/CommitPrefix/Constants.swift b/Sources/CommitPrefix/Constants.swift index 856c13a..bc76ae9 100644 --- a/Sources/CommitPrefix/Constants.swift +++ b/Sources/CommitPrefix/Constants.swift @@ -26,22 +26,22 @@ import Foundation -public struct CPInfo { +struct CPInfo { - public static let version = "1.3.0" + static let version = "1.3.1" } -public struct FileName { +struct FileName { - public static let commitPrefix = "CommitPrefix.JSON" - public static let commitMessage = "commit-msg" + static let commitPrefix = "CommitPrefix.JSON" + static let commitMessage = "commit-msg" } -public struct FolderName { +struct FolderName { - public static let git = ".git" - public static let hooks = "hooks" + static let git = ".git" + static let hooks = "hooks" } diff --git a/Sources/CommitPrefix/CPDebugPrint.swift b/Sources/CommitPrefix/Error+Debug/CPDebugPrint.swift similarity index 93% rename from Sources/CommitPrefix/CPDebugPrint.swift rename to Sources/CommitPrefix/Error+Debug/CPDebugPrint.swift index 929038c..393abcd 100644 --- a/Sources/CommitPrefix/CPDebugPrint.swift +++ b/Sources/CommitPrefix/Error+Debug/CPDebugPrint.swift @@ -33,7 +33,7 @@ private let isDebugMode = false #endif /// A Debug Printer that only prints in debug mode -public func cpDebugPrint(_ value: Any, file: String = #file, line: Int = #line, function: String = #function) { +func cpDebugPrint(_ value: Any, file: String = #file, line: Int = #line, function: String = #function) { guard isDebugMode else { return } print("********** Commit Prefix Debug **********") print("File: \(file)") diff --git a/Sources/CommitPrefix/CPError.swift b/Sources/CommitPrefix/Error+Debug/CPError.swift similarity index 100% rename from Sources/CommitPrefix/CPError.swift rename to Sources/CommitPrefix/Error+Debug/CPError.swift diff --git a/Sources/CommitPrefix/Hook/CommitMessageHook.swift b/Sources/CommitPrefix/Hook/CommitMessageHook.swift new file mode 100644 index 0000000..2fb6171 --- /dev/null +++ b/Sources/CommitPrefix/Hook/CommitMessageHook.swift @@ -0,0 +1,117 @@ +// +// CommitMessageHook.swift +// commitPrefix +// +// MIT License +// +// Copyright (c) 2019 STEPHEN L. MARTINEZ +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +import Files +import Foundation + +struct CommitMessageHook { + + private let hooksDirectory: Folder + private let cmHookContents: CommitMessageHookContents + + private init(gitDirectory: Folder) throws { + guard let hooksDirectory = try? gitDirectory.subfolder(named: FolderName.hooks) else { + throw CPError.directoryNotFound(name: FolderName.hooks, path: gitDirectory.path) + } + self.hooksDirectory = hooksDirectory + self.cmHookContents = CommitMessageHookContents() + } + + /// A commit-message hook is required for this application to work properly. This method + /// checks to see if the hook can be located, if it is one that is generated by this + /// application and then generates one if it can't be found. + /// + /// - parameters: + /// - gitDirectory: A `Folder` representing the git directory + /// + static func findOrCreate(with gitDirectory: Folder) throws { + let cpHook = try CommitMessageHook(gitDirectory: gitDirectory) + try cpHook.locateOrCreateHook() + } + + private func getCommitHookFile() throws -> File? { + + guard let foundCommitHookFile = try? hooksDirectory.file(named: FileName.commitMessage) else { + + do { + let commitHookFile = try hooksDirectory.createFile(named: FileName.commitMessage) + try commitHookFile.write(cmHookContents.renderScript(), encoding: .utf8) + cpDebugPrint(commitHookFile.path) + Shell.makeExecutable(commitHookFile.path) + } catch { + throw CPError.hookReadWriteError + } + + return nil + + } + + return foundCommitHookFile + + } + + private func overwriteCommitHook(_ commitHookFile: File) throws { + print("There seems to be an existing commit-msg found in the hooks directory") + print("Would you like to overwrite? [y/n]") + let answer = readLine() ?? "" + + switch answer { + + case "y": + print("Overwritting existing commit-msg with generated hook") + + do { + // TODO: - Theres a case where the file is not executable in the first place this will not correct that + try commitHookFile.write(cmHookContents.renderScript(), encoding: .utf8) + } catch { + throw CPError.hookReadWriteError + } + + case "n": + throw CPTermination.overwriteCancelled + + default: + throw CPTermination.expectedYesOrNo + + } + } + + private func hookIsCommitPrefix(_ hookFile: File) throws -> Bool { + + guard let hookContents = try? hookFile.readAsString(encodedAs: .utf8) else { + throw CPError.hookReadWriteError + } + + return hookContents.contains(cmHookContents.fileIdentifier) + } + + private func locateOrCreateHook() throws { + guard let foundCommitHookFile = try getCommitHookFile() else { return } + guard try !hookIsCommitPrefix(foundCommitHookFile) else { return } + try overwriteCommitHook(foundCommitHookFile) + } + +} diff --git a/Sources/CommitPrefix/Hook/CommitMessageHookContents.swift b/Sources/CommitPrefix/Hook/CommitMessageHookContents.swift new file mode 100644 index 0000000..428a112 --- /dev/null +++ b/Sources/CommitPrefix/Hook/CommitMessageHookContents.swift @@ -0,0 +1,170 @@ +// +// CommitMessageHookContents.swift +// commitPrefix +// +// MIT License +// +// Copyright (c) 2019 STEPHEN L. MARTINEZ +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +import Foundation + +struct CommitMessageHookContents { + + let fileIdentifier = "Created by CommitPrefix \(CPInfo.version)" + + private var currentDate: String { + let formatter = DateFormatter() + formatter.dateFormat = "MM/dd/yyyy" + return formatter.string(from: Date()) + } + + func renderScript() -> String { """ + #!/usr/bin/swift + // + // Commit-msg + // + // \(fileIdentifier) on \(currentDate) + // + + import Foundation + + \(renderEnumIOError()) + + \(renderStructIOCommitPrefix()) + + \(renderMainDoTryCatch()) + + """ + } + + private func renderEnumIOError() -> String { """ + enum IOError: Error { + + case invalidArgument + case overwriteError + + var message: String { + switch self { + case .invalidArgument: + return "Intended to recieve .git/COMMIT_EDITMSG arg" + case .overwriteError: + return "There was an error writting to the commit message" + } + } + + } + """ + } + + private func renderStructIOCommitPrefix() -> String { """ + struct IOCommitPrefix { + + let commitMsgPath: String + + init(filePath: [String] = Array(CommandLine.arguments.dropFirst())) throws { + guard let firstArg = filePath.first else { throw IOError.invalidArgument } + self.commitMsgPath = firstArg + } + + \(renderIOCPMethodGetPrefixes()) + + \(renderIOCPMethodGetCommitMessage()) + + \(renderIOCPMethodOverwriteContents()) + + } + """ + } + + private func renderIOCPMethodGetPrefixes() -> String { """ + func getPrefixes() -> String { + let readProcess = Process() + readProcess.launchPath = "/usr/bin/env" + readProcess.arguments = ["commitPrefix", "-o"] + + let pipe = Pipe() + readProcess.standardOutput = pipe + readProcess.launch() + + readProcess.waitUntilExit() + + let data = pipe.fileHandleForReading.readDataToEndOfFile() + let contents = String(data: data, encoding: .utf8) + + return contents ?? "" + } + """ + } + + private func renderIOCPMethodGetCommitMessage() -> String { """ + func getCommitMessage() -> String { + let readProcess = Process() + readProcess.launchPath = "/usr/bin/env" + readProcess.arguments = ["cat", commitMsgPath] + + let pipe = Pipe() + readProcess.standardOutput = pipe + readProcess.launch() + + readProcess.waitUntilExit() + + let data = pipe.fileHandleForReading.readDataToEndOfFile() + let contents = String(data: data, encoding: .utf8) + + return contents ?? "" + } + """ + } + + private func renderIOCPMethodOverwriteContents() -> String { """ + func overwriteContents(with contents: String) throws { + do { + try contents.write(toFile: commitMsgPath, atomically: true, encoding: .utf8) + } catch { + throw IOError.overwriteError + } + } + """ + } + + private func renderMainDoTryCatch() -> String { """ + do { + + let ioCommitPrefix = try IOCommitPrefix() + + let prefixes = ioCommitPrefix.getPrefixes() + .trimmingCharacters(in: .newlines) + + let commitMessage = ioCommitPrefix.getCommitMessage() + .trimmingCharacters(in: .newlines) + + let newCommitMessage = [prefixes, commitMessage].joined(separator: " ") + try ioCommitPrefix.overwriteContents(with: newCommitMessage) + + } catch let ioError as IOError { + + print(ioError) + + } + """ + } + +} diff --git a/Sources/CommitPrefix/Interface/ArgumentBuilder.swift b/Sources/CommitPrefix/Interface/ArgumentBuilder.swift new file mode 100644 index 0000000..1da4820 --- /dev/null +++ b/Sources/CommitPrefix/Interface/ArgumentBuilder.swift @@ -0,0 +1,133 @@ +// +// ArgumentBuilder.swift +// commitPrefix +// +// MIT License +// +// Copyright (c) 2019 STEPHEN L. MARTINEZ +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +import Foundation +import SPMUtility + +struct ArgumentBuilder { + + let usage: String = """ + [,,...] [-o | --output] [-d | --delete] + [-n | -normal] [ -b | --branchParse ] [-v | --version] + """ + + let overview: String = """ + + The CommitPrefix stores a desired set of prefixes for your commit messages. It + stores it within the .git folder of the current repository. A commit-msg hook is + also generated and stored within the .git folder which is used to prefix the + commit message. + + Modes: + CommitPrefix has two modes, normal and branch parse. + + - NORMAL + example: commitPrefix ,,... + + You can add normal prefixes by entering comma seperated values. These values will + be parsed as prefixes and prepended to future commit messages. + + - BRANCH_PARSE + example commitPrefix -b + + Branch parse mode checks the current branch for an issue number validated by the + value passed in as an argument. For example if you passed in a validator value of + "eng" and your current branch was named ENG-342-SomeFeatureBranchLinkedToENG-101, + commitPrefix will pickup [ENG-342] and [ENG-101] as branch prefixes to be + prepended to you next commit along with any other normal prefixes you might have. + + You can change back to NORMAL mode by entering: + example: commitPrefix -n + + To view the current state of prefixes and mode, enter: + example: commitPrefix + """ + + func buildParser() -> ArgumentParser { + ArgumentParser(usage: usage, overview: overview) + } + + func buildVersionArgument(parser: ArgumentParser) -> OptionArgument { + return parser.add( + option: "--version", + shortName: "-v", + kind: Bool.self, + usage: "Outputs the current version information", + completion: nil + ) + } + + func buildOutputArgument(parser: ArgumentParser) -> OptionArgument { + return parser.add( + option: "--output", + shortName: "-o", + kind: Bool.self, + usage: "Outputs the full, formated prefix to standard output", + completion: nil + ) + } + + func buildDeleteArgument(parser: ArgumentParser) -> OptionArgument { + return parser.add( + option: "--delete", + shortName: "-d", + kind: Bool.self, + usage: "Deletes the stored prefixes", + completion: nil + ) + } + + func buildNormalArgument(parser: ArgumentParser) -> OptionArgument { + return parser.add( + option: "--normal", + shortName: "-n", + kind: Bool.self, + usage: "Sets the mode to NORMAL", + completion: nil + ) + } + + func buildBranchParseArgument(parser: ArgumentParser) -> OptionArgument { + return parser.add( + option: "--branchParse", + shortName: "-b", + kind: Bool.self, + usage: "Sets the mode to BRANCH_PARSE. Requires a validator argument", + completion: nil + ) + } + + func buildUserEntryArgument(parser: ArgumentParser) -> PositionalArgument<[String]> { + return parser.add( + positional: "UserEntry", + kind: [String].self, + optional: true, + usage: nil, + completion: nil + ) + } + +} diff --git a/Sources/CommitPrefix/CLIArguments.swift b/Sources/CommitPrefix/Interface/CLIArguments.swift similarity index 60% rename from Sources/CommitPrefix/CLIArguments.swift rename to Sources/CommitPrefix/Interface/CLIArguments.swift index 0af2b64..26cd26d 100644 --- a/Sources/CommitPrefix/CLIArguments.swift +++ b/Sources/CommitPrefix/Interface/CLIArguments.swift @@ -27,9 +27,9 @@ import Foundation import SPMUtility -public struct CLIArguments { +struct CLIArguments { - public enum UserCommand { + enum UserCommand { case outputVersion case viewState case outputPrefixes @@ -59,7 +59,7 @@ public struct CLIArguments { private let modeBranchParse: OptionArgument private let userEntry: PositionalArgument<[String]> - public init(arguments: [String] = CommandLine.arguments) { + init(arguments: [String] = CommandLine.arguments) { // The first argument specifies the path of the executable file self.rawArgs = Array(arguments.dropFirst()) let argBuilder = ArgumentBuilder() @@ -144,108 +144,3 @@ public struct CLIArguments { } } - -private struct ArgumentBuilder { - - let usage: String = """ - [,,...] [-o | --output] [-d | --delete] - [-n | -normal] [ -b | --branchParse ] [-v | --version] - """ - - let overview: String = """ - - The CommitPrefix stores a desired set of prefixes for your commit messages. It - stores it within the .git folder of the current repository. A commit-msg hook is - also generated and stored within the .git folder which is used to prefix the - commit message. - - Modes: - CommitPrefix has two modes, normal and branch parse. - - - NORMAL - example: commitPrefix ,,... - - You can add normal prefixes by entering comma seperated values. These values will - be parsed as prefixes and prepended to future commit messages. - - - BRANCH_PARSE - example commitPrefix -b - - Branch parse mode checks the current branch for an issue number validated by the - value passed in as an argument. For example if you passed in a validator value of - "eng" and your current branch was named ENG-342-SomeFeatureBranchLinkedToENG-101, - commitPrefix will pickup [ENG-342] and [ENG-101] as branch prefixes to be - prepended to you next commit along with any other normal prefixes you might have. - - You can change back to NORMAL mode by entering: - example: commitPrefix -n - - To view the current state of prefixes and mode, enter: - example: commitPrefix - """ - - func buildParser() -> ArgumentParser { - ArgumentParser(usage: usage, overview: overview) - } - - func buildVersionArgument(parser: ArgumentParser) -> OptionArgument { - return parser.add( - option: "--version", - shortName: "-v", - kind: Bool.self, - usage: "Outputs the current version information", - completion: nil - ) - } - - func buildOutputArgument(parser: ArgumentParser) -> OptionArgument { - return parser.add( - option: "--output", - shortName: "-o", - kind: Bool.self, - usage: "Outputs the full, formated prefix to standard output", - completion: nil - ) - } - - func buildDeleteArgument(parser: ArgumentParser) -> OptionArgument { - return parser.add( - option: "--delete", - shortName: "-d", - kind: Bool.self, - usage: "Deletes the stored prefixes", - completion: nil - ) - } - - func buildNormalArgument(parser: ArgumentParser) -> OptionArgument { - return parser.add( - option: "--normal", - shortName: "-n", - kind: Bool.self, - usage: "Sets the mode to NORMAL", - completion: nil - ) - } - - func buildBranchParseArgument(parser: ArgumentParser) -> OptionArgument { - return parser.add( - option: "--branchParse", - shortName: "-b", - kind: Bool.self, - usage: "Sets the mode to BRANCH_PARSE. Requires a validator argument", - completion: nil - ) - } - - func buildUserEntryArgument(parser: ArgumentParser) -> PositionalArgument<[String]> { - return parser.add( - positional: "UserEntry", - kind: [String].self, - optional: true, - usage: nil, - completion: nil - ) - } - -} diff --git a/Sources/CommitPrefix/Shell.swift b/Sources/CommitPrefix/Utilities/Shell.swift similarity index 100% rename from Sources/CommitPrefix/Shell.swift rename to Sources/CommitPrefix/Utilities/Shell.swift diff --git a/Sources/CommitPrefix/String+Extensions.swift b/Sources/CommitPrefix/Utilities/String+Extensions.swift similarity index 98% rename from Sources/CommitPrefix/String+Extensions.swift rename to Sources/CommitPrefix/Utilities/String+Extensions.swift index 9a4fac6..db7995c 100644 --- a/Sources/CommitPrefix/String+Extensions.swift +++ b/Sources/CommitPrefix/Utilities/String+Extensions.swift @@ -26,7 +26,7 @@ import Foundation -public extension String { +extension String { private func findMatches(in string: String, using regex: String) -> [String] {