diff --git a/Makefile b/Makefile index 8ff3ea5..799989a 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ INSTALL_DIR=$(HOME)/.$(shell echo '$(APP_NAME)' | tr '[:upper:]' '[:lower:]') BUILD_NUMBER_FILE=./Sources/$(APP_NAME)/App/BuildNumber.swift VERSION_FILE=./Sources/$(APP_NAME)/App/Version.swift -SWIFT_BUILD_FLAGS=--configuration release -Xswiftc -static-stdlib +SWIFT_BUILD_FLAGS=--configuration release APP_EXECUTABLE=$(shell swift build $(SWIFT_BUILD_FLAGS) --show-bin-path)/$(APP_NAME) # ZSH_COMMAND · run single command in `zsh` shell, ignoring most `zsh` startup files. @@ -79,7 +79,7 @@ ifdef NEW_VERSION $(eval MAJOR:=$(word 1,$(VERSION_COMPONENTS))) $(eval MINOR:=$(word 2,$(VERSION_COMPONENTS))) $(eval PATCH:=$(word 3,$(VERSION_COMPONENTS))) - @echo "import $(APP_NAME)Core\n\nlet appVersion: Version = .init(major: $(MAJOR), minor: $(MINOR), patch: $(PATCH))" > $(VERSION_FILE) + @echo "import struct Utility.Version\n\nlet appVersion: Version = .init($(MAJOR), $(MINOR), $(PATCH))" > $(VERSION_FILE) endif %: diff --git a/Package.resolved b/Package.resolved index 6e93493..bbe40a7 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,6 +1,15 @@ { "object": { "pins": [ + { + "package": "SwiftPM", + "repositoryURL": "git@github.com:apple/swift-package-manager.git", + "state": { + "branch": null, + "revision": "235aacc514cb81a6881364b0fedcb3dd083228f3", + "version": "0.3.0" + } + }, { "package": "SnapshotTesting", "repositoryURL": "https://github.com/pointfreeco/swift-snapshot-testing", diff --git a/Package.swift b/Package.swift index 378389d..1409789 100644 --- a/Package.swift +++ b/Package.swift @@ -9,7 +9,8 @@ let package = Package( .library(name: "DiffFormatterApp", targets: ["DiffFormatterApp"]) ], dependencies: [ - .package(url: "https://github.com/pointfreeco/swift-snapshot-testing", from: "1.1.0") + .package(url: "git@github.com:pointfreeco/swift-snapshot-testing", from: "1.1.0"), + .package(url: "git@github.com:apple/swift-package-manager.git", from: "0.3.0") ], targets: [ .target( @@ -17,16 +18,10 @@ let package = Package( dependencies: ["DiffFormatterApp"]), .target( name: "DiffFormatterApp", - dependencies: ["DiffFormatterCore", "DiffFormatterRouting", "DiffFormatterTelemetry", "DiffFormatterUtilities"]), + dependencies: ["DiffFormatterCore", "Utility"]), .target( name: "DiffFormatterCore", dependencies: ["DiffFormatterUtilities"]), - .target( - name: "DiffFormatterRouting", - dependencies: ["DiffFormatterCore", "DiffFormatterUtilities"]), - .target( - name: "DiffFormatterTelemetry", - dependencies: []), .target( name: "DiffFormatterUtilities", dependencies: []), diff --git a/Sources/DiffFormatter/App/Version.swift b/Sources/DiffFormatter/App/Version.swift index b61c852..f41b850 100644 --- a/Sources/DiffFormatter/App/Version.swift +++ b/Sources/DiffFormatter/App/Version.swift @@ -1,3 +1,3 @@ -import DiffFormatterCore +import struct Utility.Version -let appVersion: Version = .init(major: 0, minor: 0, patch: 17) +let appVersion: Version = .init(0, 0, 17) diff --git a/Sources/DiffFormatter/main.swift b/Sources/DiffFormatter/main.swift index 015090f..649caa1 100644 --- a/Sources/DiffFormatter/main.swift +++ b/Sources/DiffFormatter/main.swift @@ -7,11 +7,24 @@ // import DiffFormatterApp -import DiffFormatterCore +import DiffFormatterUtilities import Foundation -App( +let processInfo: ProcessInfo = .processInfo + +let meta: App.Meta = .init( buildNumber: appBuildNumber, name: appName, version: appVersion -).run(processInfo: ProcessInfo.processInfo) +) + +let runner = AppRunner( + environment: processInfo.environment, + meta: meta +) + +do { + try runner.run(arguments: processInfo.arguments) +} catch { + Output.instance.print("\(error)", kind: .error) +} diff --git a/Sources/DiffFormatterApp/App.swift b/Sources/DiffFormatterApp/App.swift index 1629aaf..b0c5e03 100644 --- a/Sources/DiffFormatterApp/App.swift +++ b/Sources/DiffFormatterApp/App.swift @@ -6,34 +6,53 @@ // Copyright © 2019 DHL. All rights reserved. // -import DiffFormatterCore -import DiffFormatterRouting -import DiffFormatterTelemetry -import Foundation - -extension App { - public func run(processInfo: ProcessInfo) { - let args: [String] = Array(processInfo - .arguments - .dropFirst() // Remove app name/command - ) - - let scheme = ArgumentScheme(arguments: args) - - let configurator = Configurator(processInfo: processInfo, argScheme: scheme) - - let router = ArgumentRouter( - app: self, - configuration: configurator.configuration, - handlers: [ - ArgumentRouter.usageHandler, - ArgumentRouter.versionHandler, - ArgumentRouter.diffHandler - ] - ) - - if case .notHandled = router.route(argScheme: scheme) { - log.error("Unable to handle included arguments") +import struct Utility.Version +import struct DiffFormatterCore.Configuration +import DiffFormatterUtilities + +typealias Version = Utility.Version + +public struct App { + public struct Meta { + public typealias Version = Utility.Version + + let buildNumber: Int + let name: String + let version: Version + + public init(buildNumber: Int, name: String, version: Version) { + self.buildNumber = buildNumber + self.name = name + self.version = version } } + public struct Options { + var projectDir: String? + var shouldPrintVersion: Bool + var verbose: Bool + } + + public let configuration: Configuration + public let meta: Meta + public let options: Options + private let output: OutputType + + init(configuration: Configuration, meta: Meta, options: Options, output: OutputType = Output.instance) { + self.configuration = configuration + self.meta = meta + self.options = options + self.output = output + } + + func print(_ value: String, kind: Output.Kind = .default) { + output.print(value, kind: kind, verbose: options.verbose) + } +} + +extension App.Options { + static let blank: App.Options = .init( + projectDir: nil, + shouldPrintVersion: false, + verbose: false + ) } diff --git a/Sources/DiffFormatterApp/AppRunner.swift b/Sources/DiffFormatterApp/AppRunner.swift new file mode 100644 index 0000000..1114002 --- /dev/null +++ b/Sources/DiffFormatterApp/AppRunner.swift @@ -0,0 +1,51 @@ +// +// AppRunner.swift +// DiffFormatterApp +// +// Created by Dan Loman on 1/29/19. +// + +public class AppRunner { + private let environment: Environment + + private let meta: App.Meta + + private let registry: CommandRegistry + + public init(environment: Environment, meta: App.Meta) { + self.environment = environment + self.meta = meta + self.registry = .init(meta: meta) + + registry.register { + GenerateCommand(meta: meta, parser: $0).bindingGlobalOptions(to: $1) + } + } + + public func run(arguments: [String]) throws { + let args = Array(arguments.dropFirst()) + + let (command, options, result) = try registry.parse(arguments: args) + + let config = Configurator( + options: options, + meta: meta, + environment: environment + ).configuration + + let app: App = .init(configuration: config, meta: meta, options: options) + + if let command = command { + try registry.runCommand( + named: command, + with: result, + app: app, + env: environment + ) + } else if options.shouldPrintVersion { + app.print("\(app.meta.name) \(app.meta.version) (\(app.meta.buildNumber))") + } else { + registry.printUsage() + } + } +} diff --git a/Sources/DiffFormatterApp/Command-Routing/Command.swift b/Sources/DiffFormatterApp/Command-Routing/Command.swift new file mode 100644 index 0000000..75e1869 --- /dev/null +++ b/Sources/DiffFormatterApp/Command-Routing/Command.swift @@ -0,0 +1,18 @@ +// +// Command.swift +// DiffFormatterApp +// +// Created by Dan Loman on 1/29/19. +// + +import Utility + +public typealias ParsingResult = ArgumentParser.Result +public typealias Environment = [String: String] + +protocol Command { + var name: String { get } + + func run(with result: ParsingResult, app: App, env: Environment) throws + func bindingGlobalOptions(to binder: ArgumentBinder) -> Self +} diff --git a/Sources/DiffFormatterApp/Command-Routing/CommandRegistry.swift b/Sources/DiffFormatterApp/Command-Routing/CommandRegistry.swift new file mode 100644 index 0000000..1023aa3 --- /dev/null +++ b/Sources/DiffFormatterApp/Command-Routing/CommandRegistry.swift @@ -0,0 +1,69 @@ +// +// CommandRegistry.swift +// DiffFormatterApp +// +// Created by Dan Loman on 1/29/19. +// + +import Foundation +import Utility +import var Basic.stdoutStream + +final class CommandRegistry { + typealias Binder = ArgumentBinder + + private let binder: Binder = .init() + + private var commands: [Command] = [] + + private let parser: ArgumentParser + + init(meta: App.Meta) { + // swiftlint:disable line_length + self.parser = .init( + usage: "generate [--option]...", + overview: "\(meta.name) is a flexible tool for generating well-formatted changelogs between application versions", + seeAlso: "Visit https://github.com/namolnad/\(meta.name) for more information" + ) + // swiftlint:enable line_length + + bindOptions(to: binder, using: parser, meta: meta) + } + + // swiftlint:disable large_tuple + func parse(arguments: [String]) throws -> (String?, App.Options, ParsingResult) { + let result = try parser.parse(arguments) + + var options: App.Options = .blank + + try binder.fill(parseResult: result, into: &options) + + return (result.subparser(parser), options, result) + } + // swiftlint:enable large_tuple + + func printUsage() { + parser.printUsage(on: stdoutStream) + } + + func register(command: (ArgumentParser, Binder) -> Command) { + self.commands.append(command(parser, binder)) + } + + func runCommand(named name: String, with result: ParsingResult, app: App, env: Environment) throws { + guard let command = commands.first(where: { $0.name == name }) else { + return + } + + return try command.run(with: result, app: app, env: env) + } + + private func bindOptions(to binder: Binder, using parser: ArgumentParser, meta: App.Meta) { + binder.bind(option: parser.add( + option: "--version", + shortName: "-V", + kind: Bool.self, + usage: "Displays current \(meta.name) version and build number" + )) { $0.shouldPrintVersion = $1 } + } +} diff --git a/Sources/DiffFormatterApp/Command-Routing/GenerateCommand.swift b/Sources/DiffFormatterApp/Command-Routing/GenerateCommand.swift new file mode 100644 index 0000000..705d4d6 --- /dev/null +++ b/Sources/DiffFormatterApp/Command-Routing/GenerateCommand.swift @@ -0,0 +1,147 @@ +// +// GenerateCommand.swift +// DiffFormatterApp +// +// Created by Dan Loman on 1/29/19. +// + +import DiffFormatterUtilities +import Utility + +final class GenerateCommand: Command { + struct Options { + fileprivate(set) var versions: (old: Version, new: Version) + fileprivate(set) var buildNumber: Int? + fileprivate(set) var gitLog: String? + fileprivate(set) var noFetch: Bool + fileprivate(set) var noShowVersion: Bool + fileprivate(set) var releaseManager: String? + fileprivate(set) var toPasteBoard: Bool + } + + private typealias Binder = ArgumentBinder + + let name: String = "generate" + + private let binder: Binder = .init() + + private let subparser: ArgumentParser + + init(meta: App.Meta, parser: ArgumentParser) { + self.subparser = parser.add( + subparser: name, + overview: "Generates the changelog" + ) + + bindOptions(to: binder, meta: meta) + } + + func run(with result: ParsingResult, app: App, env: Environment) throws { + var options: Options = .blank + + try binder.fill(parseResult: result, into: &options) + + let outputGenerator: OutputGenerator = try .init( + options: options, + app: app, + env: env + ) + + let result = outputGenerator.generateOutput() + + if options.toPasteBoard { + app.print("Copying output to pasteboard", kind: .info) + pbCopy(text: result) + } + + app.print(result) + } + + @discardableResult + func bindingGlobalOptions(to binder: CommandRegistry.Binder) -> GenerateCommand { + binder.bind(option: subparser.add( + option: "--verbose", + shortName: "-v", + kind: Bool.self, + usage: "Run command with verbose output" + )) { $0.verbose = $1 } + + binder.bind(option: subparser.add( + option: "--project-dir", + kind: PathArgument.self, + usage: "Path to project if command is run from separate directory" + )) { $0.projectDir = $1.path.asString } + + return self + } + + // swiftlint:disable function_body_length line_length + private func bindOptions(to binder: Binder, meta: App.Meta) { + binder.bind(positional: subparser.add( + positional: "generate", + kind: [Version].self, + usage: " must be the first arguments to this command" + )) { options, versions in + guard versions.count == 2, let firstVersion = versions.first, let secondVersion = versions.last else { + throw ArgumentParserError.invalidValue( + argument: "versions", + error: .custom("Must receive 2 versions to properly generate changelog") + ) + } + if firstVersion < secondVersion { + options.versions = (old: firstVersion, new: secondVersion) + } else { + options.versions = (old: secondVersion, new: firstVersion) + } + } + + binder.bind(option: subparser.add( + option: "--build-number", + kind: Int.self, + usage: "Build number string to be included in version header. Takes precedence over build number command in config. e.g. `6.19.1 (6258)`" + )) { $0.buildNumber = $1 } + + binder.bind(option: subparser.add( + option: "--git-log", + kind: String.self, + usage: "Pass in the git-log string directly vs having \(meta.name) generate it. See README for details" + )) { $0.gitLog = $1 } + + binder.bind(option: subparser.add( + option: "--no-fetch", + kind: Bool.self, + usage: "Don't fetch origin before auto-generating log" + )) { $0.noFetch = $1 } + + binder.bind(option: subparser.add( + option: "--no-show-version", + kind: Bool.self, + usage: "The ability to hide the version header" + )) { $0.noShowVersion = $1 } + + binder.bind(option: subparser.add( + option: "--release-manager", + kind: String.self, + usage: "The release manager's email. e.g. `--release-manager=$(git config --get user.email)`" + )) { $0.releaseManager = $1 } + + binder.bind(option: subparser.add( + option: "--to-pasteboard", + kind: Bool.self, + usage: "Copy output to pasteboard in addition to stdout" + )) { $0.toPasteBoard = $1 } + } + // swiftlint:enable function_body_length line_length +} + +extension GenerateCommand.Options { + fileprivate static let blank: GenerateCommand.Options = .init( + versions: (.init(0, 0, 0), .init(0, 0, 0)), + buildNumber: nil, + gitLog: nil, + noFetch: false, + noShowVersion: false, + releaseManager: nil, + toPasteBoard: false + ) +} diff --git a/Sources/DiffFormatterApp/Extensions/Sequence+DiffFormatterApp.swift b/Sources/DiffFormatterApp/Extensions/Sequence+DiffFormatterApp.swift index c297475..4a53110 100644 --- a/Sources/DiffFormatterApp/Extensions/Sequence+DiffFormatterApp.swift +++ b/Sources/DiffFormatterApp/Extensions/Sequence+DiffFormatterApp.swift @@ -9,9 +9,9 @@ import Foundation extension Sequence { - func firstMap(_ transform: (Element) -> T?) -> T? { + func firstMap(_ transform: (Element) throws -> T?) rethrows -> T? { for element in self { - if let transformed = transform(element) { + if let transformed = try transform(element) { return transformed } } diff --git a/Sources/DiffFormatterApp/Extensions/Version+DiffFormatterApp.swift b/Sources/DiffFormatterApp/Extensions/Version+DiffFormatterApp.swift new file mode 100644 index 0000000..cf12de9 --- /dev/null +++ b/Sources/DiffFormatterApp/Extensions/Version+DiffFormatterApp.swift @@ -0,0 +1,22 @@ +// +// Version+DiffFormatterApp.swift +// DiffFormatterApp +// +// Created by Dan Loman on 1/29/19. +// + +import Utility + +extension Utility.Version: ArgumentKind { + public static var completion: ShellCompletion { + return .none + } + + public init(argument: String) throws { + guard let version = Version(string: argument) else { + throw ArgumentConversionError.typeMismatch(value: argument, expectedType: Version.self) + } + + self = version + } +} diff --git a/Sources/DiffFormatterApp/Models/Configurator.swift b/Sources/DiffFormatterApp/Models/Configurator.swift index a305a32..7200fdd 100644 --- a/Sources/DiffFormatterApp/Models/Configurator.swift +++ b/Sources/DiffFormatterApp/Models/Configurator.swift @@ -7,8 +7,6 @@ // import DiffFormatterCore -import DiffFormatterRouting -import DiffFormatterTelemetry import DiffFormatterUtilities import Foundation @@ -20,29 +18,43 @@ struct Configurator { // Paths for which the configurator should continue to modify the existing config with the next found config private let cascadingPaths: [String] - private let configResolver: FileResolver + private let cascadingResolver: FileResolver private let defaultConfig: Configuration // Paths for which the configurator should return the first valid configuration + private let immediateResolver: FileResolver + private let immediateReturnPaths: [String] - init(processInfo: ProcessInfo, argScheme: ArgumentScheme, fileManager: FileManager = .default) { - self.configResolver = .init( + private let output: OutputType + + init( + options: App.Options, + meta: App.Meta, + environment: Environment, + fileManager: FileManager = .default, + output: OutputType = Output.instance) { + self.cascadingResolver = .init( fileManager: fileManager, - pathComponent: "/.diffformatter/config.json", - logError: log.error + pathComponent: "/.\(meta.name.lowercased())/config.json" ) - self.defaultConfig = .default(currentDirectory: fileManager.currentDirectoryPath) + + // The immediate resolver expects an exact path to be passed in through the environment variable + self.immediateResolver = .init(fileManager: fileManager) let immediateReturnPaths = [ - processInfo.environment["DIFFFORMATTER_CONFIG"] + environment["\(meta.name.uppercased())_CONFIG"] ] self.immediateReturnPaths = immediateReturnPaths .compactMap { $0 } .filter { !$0.isEmpty } + self.defaultConfig = .default(projectDir: options.projectDir ?? fileManager.currentDirectoryPath) + + self.output = output + let homeDirectoryPath: String if #available(OSX 10.12, *) { homeDirectoryPath = fileManager.homeDirectoryForCurrentUser.path @@ -56,9 +68,8 @@ struct Configurator { ] // Append project dir if passed in as argument - for case let .actionable(.projectDir, value) in argScheme.args { + if let value = options.projectDir { cascadingPaths.append(value) - break } self.cascadingPaths = cascadingPaths.filter { !$0.isEmpty } @@ -68,13 +79,17 @@ struct Configurator { // Start with default configuration var configuration = defaultConfig - if let config = immediateReturnPaths.firstMap(configResolver.resolve) { - configuration.update(with: config) - return configuration - } - - for config in cascadingPaths.compactMap(configResolver.resolve) where !config.isBlank { - configuration.update(with: config) + do { + if let config = try immediateReturnPaths.firstMap(immediateResolver.resolve) { + configuration.update(with: config) + return configuration + } + + for config in try cascadingPaths.compactMap(cascadingResolver.resolve) where !config.isBlank { + configuration.update(with: config) + } + } catch { + output.print("\(error)", kind: .error, verbose: false) } return configuration diff --git a/Sources/DiffFormatterApp/Models/Git.swift b/Sources/DiffFormatterApp/Models/Git.swift index 9ee95a7..bf2a2db 100644 --- a/Sources/DiffFormatterApp/Models/Git.swift +++ b/Sources/DiffFormatterApp/Models/Git.swift @@ -11,22 +11,21 @@ import DiffFormatterUtilities struct Git { let configuration: Configuration - let projectDir: String } extension Git { - func diff(oldVersion: String, newVersion: String) -> String { + func log(oldVersion: Version, newVersion: Version) -> String { guard !isTest else { return "" } return shell( executablePath: gitExecutablePath, - arguments: gitDiffArguments( - oldVersion: oldVersion, - newVersion: newVersion + arguments: gitLogArguments( + oldVersion: oldVersion.description, + newVersion: newVersion.description ), - currentDirectoryPath: projectDir + currentDirectoryPath: configuration.projectDir ) ?? "" } @@ -34,7 +33,7 @@ extension Git { _ = shell( executablePath: gitExecutablePath, arguments: ["fetch"], - currentDirectoryPath: projectDir + currentDirectoryPath: configuration.projectDir ) } @@ -45,7 +44,7 @@ extension Git { guard let path = shell( executablePath: "/bin/bash", arguments: ["-c", "which git"], - currentDirectoryPath: projectDir + currentDirectoryPath: configuration.projectDir ) else { return "" } @@ -53,7 +52,7 @@ extension Git { return path.trimmingCharacters(in: .newlines) } - private func gitDiffArguments(oldVersion: String, newVersion: String) -> [String] { + private func gitLogArguments(oldVersion: String, newVersion: String) -> [String] { return [ "log", "--left-right", diff --git a/Sources/DiffFormatterApp/Output/OutputGenerator.swift b/Sources/DiffFormatterApp/Output/OutputGenerator.swift index ba3423f..1e05786 100644 --- a/Sources/DiffFormatterApp/Output/OutputGenerator.swift +++ b/Sources/DiffFormatterApp/Output/OutputGenerator.swift @@ -6,23 +6,29 @@ // Copyright © 2018 DHL. All rights reserved. // +import class Basic.Process import DiffFormatterCore struct OutputGenerator { + typealias Options = GenerateCommand.Options + private let version: String? private let releaseManager: Contributor? private let sections: [Section] private let footer: String? private let header: String? private let contributorHandlePrefix: String -} -extension OutputGenerator { - init(configuration: Configuration, rawDiff: String, version: String?, releaseManager: Contributor?) { + init(options: Options, app: App, env: Environment) throws { + let configuration = app.configuration + let rawGitLog: String = type(of: self).log(options: options, app: app) + let version: String? = try type(of: self).versionHeader(options: options, app: app, env: env) + let releaseManager: Contributor? = type(of: self).releaseManager(options: options, configuration: configuration) + let transformerFactory = TransformerFactory(configuration: configuration) let linesComponents = type(of: self) - .filteredLines(input: rawDiff, using: transformerFactory.initialTransformers) + .filteredLines(input: rawGitLog, using: transformerFactory.initialTransformers) .compactMap { LineComponents(rawLine: $0, configuration: configuration) } var sections: [Section] = configuration @@ -114,4 +120,84 @@ extension OutputGenerator { """ } + + private static func versionHeader(options: Options, app: App, env: Environment) throws -> String? { + guard !options.noShowVersion else { + return nil + } + + let versionHeader: String = options.versions.new.description + + if let buildNumber = options.buildNumber { + return versionHeader + " (\(buildNumber))" + } + + guard + let args = app.configuration.buildNumberCommandArgs, + !args.isEmpty, + let buildNumber = try getBuildNumber( + for: options.versions.new, + projectDir: app.configuration.projectDir, + using: args, + environment: env + )?.trimmingCharacters(in: .whitespacesAndNewlines) else { + return versionHeader + } + + return versionHeader + " (\(buildNumber))" + } + + private static func getBuildNumber( + for newVersion: Version, + projectDir: String, + using args: [String], + environment: Environment) throws -> String? { + guard !args.isEmpty else { + return nil + } + + let env: Environment = environment.merging([ + "NEW_VERSION": "\(newVersion)", + "PROJECT_DIR": "\(projectDir)" + ]) { $1 } + + let process: Process = .init( + arguments: args, + environment: env + ) + + try process.launch() + try process.waitUntilExit() + + if let result = process.result { + return try result.utf8Output() + } else { + return nil + } + } + + private static func log(options: Options, app: App) -> String { + if let log = options.gitLog { + return log + } + + let git = Git(configuration: app.configuration) + + if !options.noFetch { + app.print("Fetching origin", kind: .info) + git.fetch() + } + + app.print("Generating log", kind: .info) + + return git.log(oldVersion: options.versions.old, newVersion: options.versions.new) + } + + private static func releaseManager(options: Options, configuration: Configuration) -> Contributor? { + guard let email = options.releaseManager else { + return nil + } + + return configuration.contributors.first { $0.email == email } + } } diff --git a/Sources/DiffFormatterApp/Routing/ArgumentHandlers/DiffHandler.swift b/Sources/DiffFormatterApp/Routing/ArgumentHandlers/DiffHandler.swift deleted file mode 100644 index ad47442..0000000 --- a/Sources/DiffFormatterApp/Routing/ArgumentHandlers/DiffHandler.swift +++ /dev/null @@ -1,114 +0,0 @@ -// -// DiffHandler.swift -// DiffFormatterApp.swift -// -// Created by Dan Loman on 11/5/18. -// Copyright © 2018 DHL. All rights reserved. -// - -import DiffFormatterCore -import DiffFormatterRouting -import DiffFormatterTelemetry -import DiffFormatterUtilities - -extension ArgumentRouter { - static let diffHandler: RouterArgumentHandling = .init { context, scheme in - guard case let .diffable(versions, args) = scheme else { - return .notHandled - } - - let projDir = projectDir(context: context, scheme: scheme) - - let outputGenerator: OutputGenerator = .init( - configuration: context.configuration, - rawDiff: diff( - context: context, - scheme: scheme, - versions: versions, - projectDir: projDir - ), - version: versionHeader(context: context, scheme: scheme, projectDir: projDir), - releaseManager: releaseManager(context: context, scheme: scheme) - ) - - log.info("Output copied to pasteboard:") - - context.output(output(generator: outputGenerator)) - - return .handled - } - - private static func output(generator: OutputGenerator) -> String { - let result = generator.generateOutput() - - pbCopy(text: result) - - return result - } - - private static func versionHeader(context: RoutingContext, scheme: ArgumentScheme, projectDir: String) -> String? { - guard case let .diffable(versions, args) = scheme, !args.contains(.flag(.noShowVersion)) else { - return nil - } - - var versionHeader: String = versions.new - - for case let .actionable(.buildNumber, buildNumber) in args { - return versionHeader + " (\(buildNumber))" - } - - if var commandArgs = context.configuration.buildNumberCommandArgs, - !commandArgs.isEmpty, - case let exec = commandArgs.removeFirst(), - let buildNumber = shell( - executablePath: exec, - arguments: commandArgs, - currentDirectoryPath: context.configuration.currentDirectory, - environment: ["NEW_VERSION": "\(versions.new)", "PROJECT_DIR": "\(projectDir)"] - ) { - versionHeader.append(" (\(buildNumber.trimmingCharacters(in: .whitespacesAndNewlines)))") - } - - return versionHeader - } - - private static func diff( - context: RoutingContext, - scheme: ArgumentScheme, - versions: Versions, - projectDir: String) -> String { - for case let .actionable(.gitDiff, diff) in scheme.args { - return diff - } - - let git = Git( - configuration: context.configuration, - projectDir: projectDir - ) - - if !scheme.args.contains(.flag(.noFetch)) { - log.info("Fetching origin") - git.fetch() - } - - log.info("Generating diff") - - return git.diff(oldVersion: versions.old, newVersion: versions.new) - } - - private static func projectDir(context: RoutingContext, scheme: ArgumentScheme) -> String { - for case let .actionable(.projectDir, dir) in scheme.args { - return dir - } - - return context.configuration.currentDirectory - } - - private static func releaseManager(context: RoutingContext, scheme: ArgumentScheme) -> Contributor? { - for case let .actionable(.releaseManager, email) in scheme.args { - return context.configuration.contributors.first { $0.email == email } - } - - return nil - } -} diff --git a/Sources/DiffFormatterApp/Routing/ArgumentHandlers/UsageHandler.swift b/Sources/DiffFormatterApp/Routing/ArgumentHandlers/UsageHandler.swift deleted file mode 100644 index 3fbfe7e..0000000 --- a/Sources/DiffFormatterApp/Routing/ArgumentHandlers/UsageHandler.swift +++ /dev/null @@ -1,64 +0,0 @@ -// -// UsageHandler.swift -// DiffFormatterApp.swift -// -// Created by Dan Loman on 11/5/18. -// Copyright © 2018 DHL. All rights reserved. -// - -import DiffFormatterRouting - -extension ArgumentRouter { - private static let usageArguments: [Argument] = [.flag(.help), .flag(.helpAbbreviated)] - - static let usageHandler: RouterArgumentHandling = .init { context, scheme in - guard scheme.args.contains(where: usageArguments.contains) else { - return .notHandled - } - - // NOTE: - Long term, this should defer to sub-handlers for command-specific usage - - context.output(usageInformation(context: context)) - - return .handled - } - - // swiftlint:disable line_length - private static func usageInformation(context: RoutingContext) -> String { - return """ - - \(context.app.name) Info: - -h, --help - Displays this dialog - -v, --version - Displays \(context.app.name) version information - - Diff Formatting: - The first 2 arguments must be (branch or tag) version strings, given as: - - `\(context.app.name) OLD_VERSION NEW_VERSION` - - Other Arguments: - --no-show-version - The ability to hide the version header - --release-manager - The release manager's email. e.g. `--release-manager=$(git config --get user.email)` - --project-dir - Project directory if \(context.app.name) is not being called from project directory - --git-diff - Manually-passed git diff in expected format. See README for format details. - --no-fetch - Don't fetch origin before auto-generating diff - --build-number - Build number string to be included in version header. Takes precedence over build number command in config. e.g. `6.19.1 (6258)` - - Configuration: - Configuration instructions available in the README. - - - Additional information available at: https://github.com/namolnad/\(context.app.name) - - """ - } - // swiftlint:enable line_length -} diff --git a/Sources/DiffFormatterApp/Routing/ArgumentHandlers/VersionHandler.swift b/Sources/DiffFormatterApp/Routing/ArgumentHandlers/VersionHandler.swift deleted file mode 100644 index 0c2dc3a..0000000 --- a/Sources/DiffFormatterApp/Routing/ArgumentHandlers/VersionHandler.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// VersionHandler.swift -// DiffFormatterApp.swift -// -// Created by Dan Loman on 11/5/18. -// Copyright © 2018 DHL. All rights reserved. -// - -import DiffFormatterRouting - -extension ArgumentRouter { - private static let versionArguments: [Argument] = [.flag(.version), .flag(.versionAbbreviated)] - - static let versionHandler: RouterArgumentHandling = .init { context, scheme in - guard scheme.args.contains(where: versionArguments.contains) else { - return .notHandled - } - - context.output("\(context.app.name) \(context.app.version) (\(context.app.buildNumber))") - - return .handled - } -} diff --git a/Sources/DiffFormatterCore/App/App.swift b/Sources/DiffFormatterCore/App/App.swift deleted file mode 100644 index 7cced60..0000000 --- a/Sources/DiffFormatterCore/App/App.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// App.swift -// DiffFormatterCore -// -// Created by Dan Loman on 9/16/18. -// Copyright © 2018 DHL. All rights reserved. -// - -import Foundation - -public struct App { - public let buildNumber: Int - public let name: String - public let version: Version - - public init(buildNumber: Int, name: String, version: Version) { - self.buildNumber = buildNumber - self.name = name - self.version = version - } -} diff --git a/Sources/DiffFormatterCore/Configuration/Configuration.swift b/Sources/DiffFormatterCore/Configuration/Configuration.swift index 3e94d7e..86aba41 100644 --- a/Sources/DiffFormatterCore/Configuration/Configuration.swift +++ b/Sources/DiffFormatterCore/Configuration/Configuration.swift @@ -22,7 +22,7 @@ public struct Configuration: Decodable { public private(set) var buildNumberCommandArgs: [String]? public private(set) var contributorsConfig: ContributorsConfiguration - public private(set) var currentDirectory: String = "" + public private(set) var projectDir: String = "" public private(set) var delimiterConfig: DelimiterConfiguration public private(set) var footer: String? public private(set) var formatTemplate: FormatTemplate? @@ -169,14 +169,14 @@ extension Configuration { } extension Configuration { - public static func `default`(currentDirectory: String) -> Configuration { + public static func `default`(projectDir: String) -> Configuration { var config = Configuration( contributorsConfig: .default, sectionInfos: .default, delimiterConfig: .default, gitConfig: .default ) - config.currentDirectory = currentDirectory + config.projectDir = projectDir return config } @@ -192,6 +192,6 @@ extension Configuration { gitConfig.isBlank && (header?.isEmpty == true) && contributorsConfig.isBlank && - currentDirectory.isEmpty + projectDir.isEmpty } } diff --git a/Sources/DiffFormatterCore/Version.swift b/Sources/DiffFormatterCore/Version.swift deleted file mode 100644 index efdcc23..0000000 --- a/Sources/DiffFormatterCore/Version.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// Version.swift -// DiffFormatter -// -// Created by Dan Loman on 1/14/19. -// Copyright © 2019 DHL. All rights reserved. -// - -import Foundation - -public struct Version { - public let major: Int - public let minor: Int - public let patch: Int - - public init(major: Int, minor: Int, patch: Int) { - self.major = major - self.minor = minor - self.patch = patch - } - - public init(_ versionComponents: [Int]) { - self.init( - major: versionComponents.first ?? 0, - minor: versionComponents.count > 1 ? versionComponents[1] : 0, - patch: versionComponents.count > 2 ? versionComponents[2] : 0 - ) - } -} - -extension Version: CustomStringConvertible { - public var description: String { - return "\(major).\(minor).\(patch)" - } -} diff --git a/Sources/DiffFormatterRouting/Argument.swift b/Sources/DiffFormatterRouting/Argument.swift deleted file mode 100644 index 7786aee..0000000 --- a/Sources/DiffFormatterRouting/Argument.swift +++ /dev/null @@ -1,46 +0,0 @@ -// -// Argument.swift -// DiffFormatter -// -// Created by Dan Loman on 7/5/18. -// Copyright © 2018 DHL. All rights reserved. -// - -import Foundation - -public enum Argument: Equatable { - public enum ArgumentType: String { - case buildNumber = "--build-number" - case gitDiff = "--git-diff" - case help = "--help" - case helpAbbreviated = "-h" - case noFetch = "--no-fetch" - case noShowVersion = "--no-show-version" - case projectDir = "--project-dir" - case releaseManager = "--release-manager" - case version = "--version" - case versionAbbreviated = "-v" - } - - public typealias Value = String - - case flag(ArgumentType) - case actionable(ArgumentType, Value) - - public init?(value: String) { - let components = value.components(separatedBy: "=") - - guard let first = components.first, let argType = ArgumentType(rawValue: first) else { - return nil - } - - switch (components.count, components.last) { - case (1, _): - self = .flag(argType) - case (2, let value?): - self = .actionable(argType, value) - default: - return nil - } - } -} diff --git a/Sources/DiffFormatterRouting/ArgumentHandler.swift b/Sources/DiffFormatterRouting/ArgumentHandler.swift deleted file mode 100644 index 20e1b4d..0000000 --- a/Sources/DiffFormatterRouting/ArgumentHandler.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// ArgumentHandler.swift -// DiffFormatter -// -// Created by Dan Loman on 11/5/18. -// Copyright © 2018 DHL. All rights reserved. -// - -import Foundation - -extension ArgumentRouter { - public typealias RouterArgumentHandling = ArgumentHandling - - public struct ArgumentHandling { - let handle: (RoutingContext, Arguments) -> HandleResult - - public init(handle: @escaping (RoutingContext, Arguments) -> HandleResult) { - self.handle = handle - } - } -} diff --git a/Sources/DiffFormatterRouting/ArgumentRouter.swift b/Sources/DiffFormatterRouting/ArgumentRouter.swift deleted file mode 100644 index d99adb8..0000000 --- a/Sources/DiffFormatterRouting/ArgumentRouter.swift +++ /dev/null @@ -1,56 +0,0 @@ -// -// ArgumentRouter.swift -// DiffFormatter -// -// Created by Dan Loman on 7/5/18. -// Copyright © 2018 DHL. All rights reserved. -// - -import DiffFormatterCore - -public struct ArgumentRouter { - private let configuration: Configuration - private let app: App - - private let handlers: [RouterArgumentHandling] - - public init(app: App, configuration: Configuration, handlers: [RouterArgumentHandling]) { - self.app = app - self.configuration = configuration - self.handlers = handlers - } -} - -extension ArgumentRouter { - private var routingContext: RoutingContext { - return .init( - app: app, - configuration: configuration, - output: { print($0) } - ) - } - - public func route(argScheme: ArgumentScheme) -> HandleResult { - for handler in handlers { - switch handler.handle(routingContext, argScheme) { - case .handled: - return .handled - case .notHandled: - continue - case .partiallyHandled(unprocessedArgs: let unprocessedArgs): - let unprocessed: ArgumentScheme - - switch argScheme { - case .diffable(let versions, _): - unprocessed = .diffable(versions: versions, args: unprocessedArgs) - case .nonDiffable: - unprocessed = .nonDiffable(args: unprocessedArgs) - } - - return route(argScheme: unprocessed) - } - } - - return .notHandled - } -} diff --git a/Sources/DiffFormatterRouting/ArgumentScheme.swift b/Sources/DiffFormatterRouting/ArgumentScheme.swift deleted file mode 100644 index c202af9..0000000 --- a/Sources/DiffFormatterRouting/ArgumentScheme.swift +++ /dev/null @@ -1,40 +0,0 @@ -// -// ArgumentScheme.swift -// DiffFormatter -// -// Created by Dan Loman on 11/6/18. -// Copyright © 2018 DHL. All rights reserved. -// - -import Foundation - -public typealias Versions = (old: String, new: String) - -public enum ArgumentScheme { - case diffable(versions: Versions, args: [Argument]) - case nonDiffable(args: [Argument]) - - public var args: [Argument] { - switch self { - case .diffable(_, let args), .nonDiffable(let args): - return args - } - } -} - -extension ArgumentScheme { - public init(arguments: [String]) { - guard case let versions = arguments.prefix(while: { !$0.hasPrefix("-") }), - versions.count == 2, - let old = versions.first, - let new = versions.last else { - self = .nonDiffable(args: arguments.compactMap(Argument.init)) - return - } - - self = .diffable( - versions: (old: old, new: new), - args: arguments.compactMap(Argument.init) - ) - } -} diff --git a/Sources/DiffFormatterRouting/HandleResult.swift b/Sources/DiffFormatterRouting/HandleResult.swift deleted file mode 100644 index 22b1b6d..0000000 --- a/Sources/DiffFormatterRouting/HandleResult.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// HandleResult.swift -// DiffFormatter -// -// Created by Dan Loman on 11/5/18. -// Copyright © 2018 DHL. All rights reserved. -// - -import Foundation - -public enum HandleResult { - case handled - case notHandled - case partiallyHandled(unprocessedArgs: [Argument]) -} - -extension HandleResult: Equatable {} diff --git a/Sources/DiffFormatterRouting/Info.plist b/Sources/DiffFormatterRouting/Info.plist deleted file mode 100644 index c26bd32..0000000 --- a/Sources/DiffFormatterRouting/Info.plist +++ /dev/null @@ -1,24 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - NSHumanReadableCopyright - Copyright © 2019 DHL. All rights reserved. - - diff --git a/Sources/DiffFormatterRouting/Routing.h b/Sources/DiffFormatterRouting/Routing.h deleted file mode 100644 index dbe6852..0000000 --- a/Sources/DiffFormatterRouting/Routing.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// Routing.h -// Routing -// -// Created by Dan Loman on 1/6/19. -// Copyright © 2019 DHL. All rights reserved. -// - -#import - -//! Project version number for Routing. -FOUNDATION_EXPORT double RoutingVersionNumber; - -//! Project version string for Routing. -FOUNDATION_EXPORT const unsigned char RoutingVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - - diff --git a/Sources/DiffFormatterRouting/RoutingContext.swift b/Sources/DiffFormatterRouting/RoutingContext.swift deleted file mode 100644 index 9b81f3b..0000000 --- a/Sources/DiffFormatterRouting/RoutingContext.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// RoutingContext.swift -// DiffFormatter -// -// Created by Dan Loman on 11/5/18. -// Copyright © 2018 DHL. All rights reserved. -// - -import DiffFormatterCore - -public struct RoutingContext { - public let app: App - public let configuration: Configuration - public let output: (String) -> Void -} diff --git a/Sources/DiffFormatterTelemetry/Info.plist b/Sources/DiffFormatterTelemetry/Info.plist deleted file mode 100644 index c26bd32..0000000 --- a/Sources/DiffFormatterTelemetry/Info.plist +++ /dev/null @@ -1,24 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - NSHumanReadableCopyright - Copyright © 2019 DHL. All rights reserved. - - diff --git a/Sources/DiffFormatterTelemetry/Log.swift b/Sources/DiffFormatterTelemetry/Log.swift deleted file mode 100644 index 868b8c4..0000000 --- a/Sources/DiffFormatterTelemetry/Log.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// Log.swift -// DiffFormatter -// -// Created by Dan Loman on 12/29/18. -// Copyright © 2018 DHL. All rights reserved. -// - -import Foundation - -public protocol Logger { - func error(_ message: String) - func info(_ message: String) -} - -public let log: Logger = Log.instance - -struct Log: Logger { - fileprivate static let instance: Log = .init() - - private let formatter: DateFormatter = { - let formatter = DateFormatter() - formatter.dateFormat = "HH:mm:ss" - return formatter - }() - - private var timeStamp: String { - return formatter.string(from: Date()) - } - - private init() {} - - public func error(_ message: String) { - log(message: "🚨 \(message)") - } - - public func info(_ message: String) { - log(message: message) - } - - private func log(message: String) { - print("[\(timeStamp)]: \(message)") - } -} diff --git a/Sources/DiffFormatterTelemetry/Telemetry.h b/Sources/DiffFormatterTelemetry/Telemetry.h deleted file mode 100644 index a5ffabf..0000000 --- a/Sources/DiffFormatterTelemetry/Telemetry.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// Telemetry.h -// Telemetry -// -// Created by Dan Loman on 1/6/19. -// Copyright © 2019 DHL. All rights reserved. -// - -#import - -//! Project version number for Telemetry. -FOUNDATION_EXPORT double TelemetryVersionNumber; - -//! Project version string for Telemetry. -FOUNDATION_EXPORT const unsigned char TelemetryVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - - diff --git a/Sources/DiffFormatterUtilities/FileResolver.swift b/Sources/DiffFormatterUtilities/FileResolver.swift index 7b0f581..d0a01bb 100644 --- a/Sources/DiffFormatterUtilities/FileResolver.swift +++ b/Sources/DiffFormatterUtilities/FileResolver.swift @@ -20,24 +20,16 @@ public final class FileResolver { private let pathComponent: String - private let logError: (String) -> Void - - public init(fileManager: FileManager, pathComponent: String, logError: @escaping (String) -> Void) { + public init(fileManager: FileManager, pathComponent: String? = nil) { self.fileManager = fileManager - self.pathComponent = pathComponent - self.logError = logError + self.pathComponent = pathComponent ?? "" } - public func resolve(path: String) -> FileType? { + public func resolve(path: String) throws -> FileType? { guard case let filePath = path + pathComponent, let data = fileManager.contents(atPath: filePath) else { return nil } - do { - return try decoder.decode(FileType.self, from: data) - } catch { - logError("Error parsing file of type \(FileType.self) at path: \(filePath). \n\nError details: \n\(error)") - return nil - } + return try decoder.decode(FileType.self, from: data) } } diff --git a/Sources/DiffFormatterUtilities/Output.swift b/Sources/DiffFormatterUtilities/Output.swift new file mode 100644 index 0000000..07160e1 --- /dev/null +++ b/Sources/DiffFormatterUtilities/Output.swift @@ -0,0 +1,51 @@ +// +// Output.swift +// DiffFormatterUtilities +// +// Created by Dan Loman on 1/30/19. +// + +import Foundation + +public protocol OutputType { + func print(_ value: String, kind: Output.Kind, verbose: Bool) +} + +public struct Output: OutputType { + public enum Kind { + case `default` + case error + case info + } + + public static let instance: Output = .init() + + private let formatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateFormat = "HH:mm:ss" + return formatter + }() + + private var timeStamp: String { + return formatter.string(from: Date()) + } + + private init() {} + + public func print(_ value: String, kind: Kind, verbose: Bool = false) { + switch kind { + case .default: + Swift.print(value) + case .error: + fputs("🚨 \(value)\n", stderr) + + exit(EXIT_FAILURE) + case .info: + guard verbose else { + return + } + + Swift.print("[\(timeStamp)]: \(value)") + } + } +} diff --git a/Sources/DiffFormatterUtilities/Shell.swift b/Sources/DiffFormatterUtilities/Shell.swift index c01b4a8..e87d017 100644 --- a/Sources/DiffFormatterUtilities/Shell.swift +++ b/Sources/DiffFormatterUtilities/Shell.swift @@ -39,6 +39,19 @@ public func shell( task.launch() } + task.waitUntilExit() + let data = pipe.fileHandleForReading.readDataToEndOfFile() - return String(data: data, encoding: .utf8) + + let taskOutput = String(data: data, encoding: .utf8) + + if task.terminationStatus != 0 { + Output.instance.print( + taskOutput ?? + "Fatal failure running task: \(executablePath) \(arguments.joined(separator: " "))", + kind: .error + ) + } + + return taskOutput } diff --git a/Tests/DiffFormatterAppTests/CommandRegistryTests.swift b/Tests/DiffFormatterAppTests/CommandRegistryTests.swift new file mode 100644 index 0000000..188dbc7 --- /dev/null +++ b/Tests/DiffFormatterAppTests/CommandRegistryTests.swift @@ -0,0 +1,65 @@ +// +// CommandRegistryTests.swift +// DiffFormatterAppTests +// +// Created by Dan Loman on 1/31/19. +// + +@testable import DiffFormatterApp +import SnapshotTesting +import XCTest + +final class CommandRegistryTests: XCTestCase { + func testParsingVersion() { + let registry = CommandRegistry(meta: .mock) + + let parsingResult = try! registry.parse(arguments: ["-V"]) + + assertSnapshot(matching: (parsingResult.0, parsingResult.1), as: .dump) + } + + func testParsingGenerate() { + let args: [String] = [ + "generate", + "6.12.1", + "6.13.0", + "--git-log=\(defaultInputMock)" + ] + + let registry = CommandRegistry(meta: .mock) + + registry.register { + GenerateCommand(meta: .mock, parser: $0).bindingGlobalOptions(to: $1) + } + + let parsingResult = try! registry.parse(arguments: args) + + assertSnapshot(matching: (parsingResult.0, parsingResult.1), as: .dump) + } + + func testParsingGenerateManyOptions() { + let args: [String] = [ + "generate", + "6.12.1", + "6.13.0", + "--git-log=\(defaultInputMock)", + "--no-fetch", + "--no-show-version", + "--verbose", + "--to-pasteboard", + "--release-manager", + "frank", + "--build-number=15" + ] + + let registry = CommandRegistry(meta: .mock) + + registry.register { + GenerateCommand(meta: .mock, parser: $0).bindingGlobalOptions(to: $1) + } + + let parsingResult = try! registry.parse(arguments: args) + + assertSnapshot(matching: (parsingResult.0, parsingResult.1), as: .dump) + } +} diff --git a/Tests/DiffFormatterAppTests/ConfigurationTests.swift b/Tests/DiffFormatterAppTests/ConfigurationTests.swift index b32d6fc..188ff9b 100644 --- a/Tests/DiffFormatterAppTests/ConfigurationTests.swift +++ b/Tests/DiffFormatterAppTests/ConfigurationTests.swift @@ -14,8 +14,9 @@ final class ConfigurationTests: XCTestCase { func testConfigurator() { assertSnapshot( matching: Configurator( - processInfo: .mock, - argScheme: .mock, + options: .blank, + meta: .mock, + environment: [:], fileManager: .mock ).configuration, as: .dump diff --git a/Tests/DiffFormatterAppTests/GenerateCommandTests.swift b/Tests/DiffFormatterAppTests/GenerateCommandTests.swift new file mode 100644 index 0000000..74d7c3e --- /dev/null +++ b/Tests/DiffFormatterAppTests/GenerateCommandTests.swift @@ -0,0 +1,18 @@ +// +// GenerateCommandTests.swift +// DiffFormatterAppTests +// +// Created by Dan Loman on 1/31/19. +// + +@testable import DiffFormatterApp +import SnapshotTesting +import XCTest + +final class GenerateCommandTests: XCTestCase { + func testCommandName() { + let command = GenerateCommand(meta: .mock, parser: .init(usage: "blah", overview: "")) + + XCTAssertEqual(command.name, "generate") + } +} diff --git a/Tests/DiffFormatterAppTests/Mocks/Mocks.swift b/Tests/DiffFormatterAppTests/Mocks/Mocks.swift index cb49058..994bbcb 100644 --- a/Tests/DiffFormatterAppTests/Mocks/Mocks.swift +++ b/Tests/DiffFormatterAppTests/Mocks/Mocks.swift @@ -6,12 +6,12 @@ // Copyright © 2018 DHL. All rights reserved. // +@testable import DiffFormatterApp @testable import DiffFormatterCore -import DiffFormatterRouting extension Configuration { static let mock: Configuration = { - var config: Configuration = .default(currentDirectory: "") + var config: Configuration = .default(projectDir: "") config.update(with: TestHelper.model(for: "default_config")) return config @@ -32,15 +32,19 @@ extension Configuration { }() } -extension ArgumentScheme { - static let mock: ArgumentScheme = .nonDiffable(args: []) -} - -extension App { - static let mock: App = .init( +extension App.Meta { + static let mock: App.Meta = .init( buildNumber: 12345, name: "DiffFormatter", - version: .init([1, 0, 1]) + version: .init(1, 0, 1) + ) +} + +extension App.Options { + static let mock: App.Options = .init( + projectDir: "home/dir", + shouldPrintVersion: false, + verbose: false ) } diff --git a/Tests/DiffFormatterAppTests/Mocks/OutputMock.swift b/Tests/DiffFormatterAppTests/Mocks/OutputMock.swift new file mode 100644 index 0000000..d876d56 --- /dev/null +++ b/Tests/DiffFormatterAppTests/Mocks/OutputMock.swift @@ -0,0 +1,17 @@ +// +// OutputMock.swift +// DiffFormatterAppTests +// +// Created by Dan Loman on 1/31/19. +// + +import DiffFormatterUtilities + +final class OutputMock: OutputType { + + var lastOutput: String = "" + + func print(_ value: String, kind: Output.Kind, verbose: Bool) { + lastOutput = value + } +} diff --git a/Tests/DiffFormatterAppTests/OutputTests.swift b/Tests/DiffFormatterAppTests/OutputTests.swift index 279c498..ff00926 100644 --- a/Tests/DiffFormatterAppTests/OutputTests.swift +++ b/Tests/DiffFormatterAppTests/OutputTests.swift @@ -13,11 +13,10 @@ import XCTest final class OutputTests: XCTestCase { func testDefaultOutput() { - let outputGenerator: OutputGenerator = .init( - configuration: .mock, - rawDiff: defaultInputMock, - version: "6.13.0", - releaseManager: Configuration.mock.contributors.first + let outputGenerator: OutputGenerator = try! .init( + options: options(gitLog: defaultInputMock), + app: .mock(), + env: [:] ) assertSnapshot( @@ -27,11 +26,10 @@ final class OutputTests: XCTestCase { } func testCherryPickedSectionOutput() { - let outputGenerator: OutputGenerator = .init( - configuration: .mock, - rawDiff: cherryPickedInputMock, - version: "6.13.0", - releaseManager: Configuration.mock.contributors.first + let outputGenerator: OutputGenerator = try! .init( + options: options(gitLog: cherryPickedInputMock), + app: .mock(), + env: [:] ) assertSnapshot( @@ -41,11 +39,10 @@ final class OutputTests: XCTestCase { } func testExcludedSectionOutput() { - let outputGenerator: OutputGenerator = .init( - configuration: .mockExcludedSection, - rawDiff: defaultInputMock, - version: "6.13.0", - releaseManager: Configuration.mock.contributors.first + let outputGenerator: OutputGenerator = try! .init( + options: options(gitLog: cherryPickedInputMock), + app: .mock(configuration: .mockExcludedSection), + env: [:] ) assertSnapshot( @@ -57,18 +54,19 @@ final class OutputTests: XCTestCase { func testCustomDelimiterOutput() { let customPath = "custom_delimiter_config" - let processInfoMock = ProcessInfoMock(arguments: [], environment: ["DIFFFORMATTER_CONFIG": customPath]) let fileManagerMock = FileManagerMock(customConfigPath: customPath) - let outputGenerator: OutputGenerator = .init( - configuration: Configurator( - processInfo: processInfoMock, - argScheme: .mock, - fileManager: fileManagerMock - ).configuration, - rawDiff: defaultInputMock, - version: "6.13.0", - releaseManager: Configuration.mock.contributors.first + let configuration = Configurator( + options: .blank, + meta: .mock, + environment: ["DIFFFORMATTER_CONFIG": customPath], + fileManager: fileManagerMock + ).configuration + + let outputGenerator: OutputGenerator = try! .init( + options: options(gitLog: defaultInputMock), + app: .mock(configuration: configuration), + env: [:] ) assertSnapshot( @@ -100,4 +98,26 @@ final class OutputTests: XCTestCase { as: .dump ) } + + private func options(gitLog: String) -> GenerateCommand.Options { + return .init( + versions: (.init(0, 0, 1), .init(6, 13, 0)), + buildNumber: nil, + gitLog: gitLog, + noFetch: true, + noShowVersion: false, + releaseManager: Configuration.mock.contributors.first?.email, + toPasteBoard: false + ) + } +} + +extension App { + static func mock(configuration: Configuration = .mock) -> App { + return .init( + configuration: configuration, + meta: .mock, + options: .mock + ) + } } diff --git a/Tests/DiffFormatterAppTests/RoutingTests.swift b/Tests/DiffFormatterAppTests/RoutingTests.swift deleted file mode 100644 index 6d29c83..0000000 --- a/Tests/DiffFormatterAppTests/RoutingTests.swift +++ /dev/null @@ -1,122 +0,0 @@ -// -// RoutingTests.swift -// DiffFormatterTests -// -// Created by Dan Loman on 12/28/18. -// Copyright © 2018 DHL. All rights reserved. -// - -@testable import DiffFormatterApp -@testable import DiffFormatterRouting -import SnapshotTesting -import XCTest - -final class RoutingTests: XCTestCase { - func testArgumentRouter() { - let router: ArgumentRouter = .init( - app: .mock, - configuration: .mock, - handlers: [ - ArgumentRouter.usageHandler, - ArgumentRouter.versionHandler, - ArgumentRouter.diffHandler - ] - ) - - let routableArgs: [String] = [ - "6.12.1", - "6.13.0", - "--git-diff=\(defaultInputMock)" - ] - - let scheme: ArgumentScheme = .init(arguments: routableArgs) - - XCTAssertTrue(router.route(argScheme: scheme) == .handled) - - XCTAssertTrue(router.route(argScheme: .mock) == .notHandled) - } - - func testDiffHandler() { - var output: String! = "" - - let context: RoutingContext = .init( - app: .mock, - configuration: .mock, - output: { output = $0 } - ) - - let scheme: ArgumentScheme = .diffable( - versions: ("6.19.0", "6.19.1"), - args: [.actionable(.buildNumber, "56789"), .flag(.noFetch)] - ) - - XCTAssert(output.isEmpty) - - _ = ArgumentRouter.diffHandler.handle(context, scheme) - - assertSnapshot(matching: output, as: .dump) - } - - func testDiffHandlerBuildNumberCommand() { - var output: String! = "" - - let context: RoutingContext = .init( - app: .mock, - configuration: .mockBuildNumberCommand, - output: { output = $0 } - ) - - let scheme: ArgumentScheme = .diffable( - versions: ("6.19.0", "6.19.1"), - args: [.flag(.noFetch)] - ) - - XCTAssert(output.isEmpty) - - _ = ArgumentRouter.diffHandler.handle(context, scheme) - - assertSnapshot(matching: output, as: .dump) - } - - func testVersionHandler() { - var output: String! = "" - - let context: RoutingContext = .init( - app: .mock, - configuration: .mock, - output: { output = $0 } - ) - - let scheme: ArgumentScheme = .diffable( - versions: ("6.19.0", "6.19.1"), - args: [.flag(.version)] - ) - - XCTAssert(output.isEmpty) - - _ = ArgumentRouter.versionHandler.handle(context, scheme) - - assertSnapshot(matching: output, as: .dump) - } - - func testUsageHandler() { - var output: String! = "" - - let context: RoutingContext = .init( - app: .mock, - configuration: .mock, - output: { output = $0 } - ) - - let scheme: ArgumentScheme = .diffable( - versions: ("6.19.0", "6.19.1"), - args: [.flag(.help)] - ) - - XCTAssert(output.isEmpty) - - _ = ArgumentRouter.usageHandler.handle(context, scheme) - - assertSnapshot(matching: output, as: .dump) - } -} diff --git a/Tests/DiffFormatterAppTests/__Snapshots__/ChangeLogRunnerTests/testDefault.1.txt b/Tests/DiffFormatterAppTests/__Snapshots__/ChangeLogRunnerTests/testDefault.1.txt new file mode 100644 index 0000000..372d81a --- /dev/null +++ b/Tests/DiffFormatterAppTests/__Snapshots__/ChangeLogRunnerTests/testDefault.1.txt @@ -0,0 +1 @@ +- "\n# 0.1.3\n\n### Features\n - |tests| fix order snapshots — [PR #1018](https://github.com/citadel-of-ricks/C-137/pull/1018) — @Rick.Sanchez\n - |search| consolidate searchBar cornerRadius to 4, increase autocomplete-v3 term height — [PR #1011](https://github.com/citadel-of-ricks/C-137/pull/1011) — @Elvis.Presley\n - |rollbar| update to v1.0.0 final — [PR #1007](https://github.com/citadel-of-ricks/C-137/pull/1007) — @Elvis.Presley\n - |push-notificatons| Request for permission after user places an order with 90 day re-prompt — [PR #1024](https://github.com/citadel-of-ricks/C-137/pull/1024) — @Jony.Ive\n - |express-placement| build error fix — [PR #1025](https://github.com/citadel-of-ricks/C-137/pull/1025) — @Jony.Ive\n - |express-placement| additional clean-up for analytics and bugs — [PR #1021](https://github.com/citadel-of-ricks/C-137/pull/1021) — @Jony.Ive\n - |codable| better supported for AnyEncodables — [PR #1022](https://github.com/citadel-of-ricks/C-137/pull/1022) — @Elvis.Presley\n - |carthage| move google places to internal carthage — [PR #1008](https://github.com/citadel-of-ricks/C-137/pull/1008) — @Elvis.Presley\n - |autocomplete-v3| add analytics — [PR #1030](https://github.com/citadel-of-ricks/C-137/pull/1030) — @Elvis.Presley\n - |analytics| Current production express placements are missing subscription_id in express_start.purchase tracking events — [PR #1020](https://github.com/citadel-of-ricks/C-137/pull/1020) — @Jony.Ive\n - Update all express placements to one screen — [PR #975](https://github.com/citadel-of-ricks/C-137/pull/975) — @Jony.Ive\n - Syncing express params across checkout modules — [PR #1016](https://github.com/citadel-of-ricks/C-137/pull/1016) — @Rick.Sanchez\n - Order status V2.5 — [PR #988](https://github.com/citadel-of-ricks/C-137/pull/988) — @Elvis.Presley\n - Autocomplete V3 — [PR #1004](https://github.com/citadel-of-ricks/C-137/pull/1004) — @Elvis.Presley\n\n### Bug Fixes\n - |bug| Don\'t show express placement every cold start — [PR #1019](https://github.com/citadel-of-ricks/C-137/pull/1019) — @Jony.Ive\n - |bug fix| minor bug fixes for cubs/pbi — [PR #1010](https://github.com/citadel-of-ricks/C-137/pull/1010) — @Elvis.Presley\n - |bug fix| fix LossyCodableArray — [PR #1017](https://github.com/citadel-of-ricks/C-137/pull/1017) — @Rick.Sanchez\n\n### Platform Improvements\n - background actions — [PR #955](https://github.com/citadel-of-ricks/C-137/pull/955)\n\n### Timeline\n- Begin development:\n- Feature cut-off / Start of bake / dogfooding:\n- Submission:\n- Release (expected):\n- Release (actual):\n" diff --git a/Tests/DiffFormatterAppTests/__Snapshots__/CommandRegistryTests/testParsingGenerate.1.txt b/Tests/DiffFormatterAppTests/__Snapshots__/CommandRegistryTests/testParsingGenerate.1.txt new file mode 100644 index 0000000..8b1af2c --- /dev/null +++ b/Tests/DiffFormatterAppTests/__Snapshots__/CommandRegistryTests/testParsingGenerate.1.txt @@ -0,0 +1,7 @@ +▿ (2 elements) + ▿ .0: Optional + - some: "generate" + ▿ .1: Options + - projectDir: Optional.none + - shouldPrintVersion: false + - verbose: false diff --git a/Tests/DiffFormatterAppTests/__Snapshots__/CommandRegistryTests/testParsingGenerateManyOptions.1.txt b/Tests/DiffFormatterAppTests/__Snapshots__/CommandRegistryTests/testParsingGenerateManyOptions.1.txt new file mode 100644 index 0000000..640673d --- /dev/null +++ b/Tests/DiffFormatterAppTests/__Snapshots__/CommandRegistryTests/testParsingGenerateManyOptions.1.txt @@ -0,0 +1,7 @@ +▿ (2 elements) + ▿ .0: Optional + - some: "generate" + ▿ .1: Options + - projectDir: Optional.none + - shouldPrintVersion: false + - verbose: true diff --git a/Tests/DiffFormatterAppTests/__Snapshots__/CommandRegistryTests/testParsingVersion.1.txt b/Tests/DiffFormatterAppTests/__Snapshots__/CommandRegistryTests/testParsingVersion.1.txt new file mode 100644 index 0000000..8db2ead --- /dev/null +++ b/Tests/DiffFormatterAppTests/__Snapshots__/CommandRegistryTests/testParsingVersion.1.txt @@ -0,0 +1,6 @@ +▿ (2 elements) + - .0: Optional.none + ▿ .1: Options + - projectDir: Optional.none + - shouldPrintVersion: true + - verbose: false diff --git a/Tests/DiffFormatterAppTests/__Snapshots__/ConfigurationTests/testConfigurator.1.txt b/Tests/DiffFormatterAppTests/__Snapshots__/ConfigurationTests/testConfigurator.1.txt index 01f029f..79579e1 100644 --- a/Tests/DiffFormatterAppTests/__Snapshots__/ConfigurationTests/testConfigurator.1.txt +++ b/Tests/DiffFormatterAppTests/__Snapshots__/ConfigurationTests/testConfigurator.1.txt @@ -13,7 +13,6 @@ ▿ Contributor - email: "elvis1935+still-alive@theking.com" - handle: "Elvis.Presley" - - currentDirectory: "current" ▿ delimiterConfig: DelimiterConfiguration ▿ input: DelimiterPair - left: "[" @@ -30,6 +29,7 @@ - executablePath: Optional.none - repoBaseUrl: "https://github.com/citadel-of-ricks/C-137" - header: Optional.none + - projectDir: "current" ▿ sectionInfos: 3 elements ▿ SectionInfo - capitalizesMessage: false