From d5f7edf049190a4e905c916fc768d950a152c5b3 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Thu, 20 Feb 2025 00:17:13 +0100 Subject: [PATCH 1/4] Defined the IDE enumeration in the library target. --- Library/Sources/Public/Enumerations/IDE.swift | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 Library/Sources/Public/Enumerations/IDE.swift diff --git a/Library/Sources/Public/Enumerations/IDE.swift b/Library/Sources/Public/Enumerations/IDE.swift new file mode 100644 index 0000000..23f1196 --- /dev/null +++ b/Library/Sources/Public/Enumerations/IDE.swift @@ -0,0 +1,4 @@ +public enum IDE: String { + case vscode + case xcode +} -- 2.47.1 From 0b2bcd6d1ee71d6004d2aae2efc17c6e6c4ca261 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Thu, 20 Feb 2025 00:18:33 +0100 Subject: [PATCH 2/4] Implemented the OpenProjectTask task in the library target. --- Library/Sources/Public/Enumerations/IDE.swift | 16 ++++++ .../Public/Tasks/OpenProjectTask.swift | 44 +++++++++++++++ .../Public/Tasks/OpenProjectTaskTests.swift | 56 +++++++++++++++++++ 3 files changed, 116 insertions(+) create mode 100644 Library/Sources/Public/Tasks/OpenProjectTask.swift create mode 100644 Test/Sources/Cases/Public/Tasks/OpenProjectTaskTests.swift diff --git a/Library/Sources/Public/Enumerations/IDE.swift b/Library/Sources/Public/Enumerations/IDE.swift index 23f1196..2c530ac 100644 --- a/Library/Sources/Public/Enumerations/IDE.swift +++ b/Library/Sources/Public/Enumerations/IDE.swift @@ -2,3 +2,19 @@ public enum IDE: String { case vscode case xcode } + +// MARK: - Extension + +extension IDE { + + // MARK: Functions + + static func random() -> IDE { + .allCases.randomElement() ?? .xcode + } + +} + +// MARK: - CaseIterable + +extension IDE: CaseIterable {} diff --git a/Library/Sources/Public/Tasks/OpenProjectTask.swift b/Library/Sources/Public/Tasks/OpenProjectTask.swift new file mode 100644 index 0000000..d609ebb --- /dev/null +++ b/Library/Sources/Public/Tasks/OpenProjectTask.swift @@ -0,0 +1,44 @@ +import Foundation + +public struct OpenProjectTask { + + // MARK: Properties + + private let terminalService: TerminalServicing + + // MARK: Initialisers + + public init(terminalService: TerminalServicing) { + self.terminalService = terminalService + } + + // MARK: Functions + + public func callAsFunction(with ide: IDE, at location: URL? = nil) async throws (TerminalServiceError) { + let executableURL: URL = switch ide { + case .vscode: .init(at: "/usr/local/bin/code") + case .xcode: .init(at: "/usr/bin/open") + } + + let locationPath = switch ide { + case .vscode: location?.pathString ?? "." + case .xcode: location?.appendingPath(.Path.package).pathString ?? .Path.package + } + + let arguments: [String] = switch ide { + case .vscode: [locationPath] + case .xcode: ["-a", "Xcode", locationPath] + } + + try await terminalService.run(executableURL, arguments: arguments) + } + +} + +// MARK: - String+Constants + +private extension String { + enum Path { + static let package = "Package.swift" + } +} diff --git a/Test/Sources/Cases/Public/Tasks/OpenProjectTaskTests.swift b/Test/Sources/Cases/Public/Tasks/OpenProjectTaskTests.swift new file mode 100644 index 0000000..7e4f748 --- /dev/null +++ b/Test/Sources/Cases/Public/Tasks/OpenProjectTaskTests.swift @@ -0,0 +1,56 @@ +import Foundation +import Testing + +@testable import ColibriLibrary + +struct OpenProjectTaskTests { + + @Test(arguments: [nil, URL.someCurrentFolder]) + func taskWithVSCodeIDE(at location: URL?) async throws { + // GIVEN + let terminalService = TerminalServiceSpy() + let task = OpenProjectTask(terminalService: terminalService) + + // WHEN + try await task(with: .vscode, at: location) + + // THEN + let executableURL = URL(at: "/usr/local/bin/code") + let arguments = [location?.pathString ?? "."] + + #expect(terminalService.actions.count == 1) + #expect(terminalService.actions[0] == .ran(executableURL, arguments)) + } + + @Test(arguments: [nil, URL.someCurrentFolder]) + func taskWithXcodeIDE(at location: URL?) async throws { + // GIVEN + let terminalService = TerminalServiceSpy() + let task = OpenProjectTask(terminalService: terminalService) + + // WHEN + try await task(with: .xcode, at: location) + + // THEN + let locationPath = location?.appendingPath("Package.swift").pathString ?? "Package.swift" + let executableURL = URL(at: "/usr/bin/open") + let arguments = ["-a", "Xcode", locationPath] + + #expect(terminalService.actions.count == 1) + #expect(terminalService.actions[0] == .ran(executableURL, arguments)) + } + + @Test(arguments: [nil, URL.someCurrentFolder], [TerminalServiceError.unexpected, .output(""), .captured("")]) + func task(at location: URL?, throws error: TerminalServiceError) async throws { + // GIVEN + let terminalService = TerminalServiceMock(action: .error(error)) + let task = OpenProjectTask(terminalService: terminalService) + + // WHEN + // THEN + await #expect(throws: error) { + try await task(with: .random(), at: location) + } + } + +} -- 2.47.1 From 77363a7c0e89403f93565691da9f1b94a9243ab8 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Thu, 20 Feb 2025 00:23:12 +0100 Subject: [PATCH 3/4] Implemented the OpenCommand subcommand in the executable target. --- Executable/Sources/Commands/OpenCommand.swift | 29 +++++++++++++++++++ .../Sources/Extensions/IDE+Conformances.swift | 6 ++++ Executable/Sources/Options/OpenOptions.swift | 16 ++++++++++ 3 files changed, 51 insertions(+) create mode 100644 Executable/Sources/Commands/OpenCommand.swift create mode 100644 Executable/Sources/Extensions/IDE+Conformances.swift create mode 100644 Executable/Sources/Options/OpenOptions.swift diff --git a/Executable/Sources/Commands/OpenCommand.swift b/Executable/Sources/Commands/OpenCommand.swift new file mode 100644 index 0000000..688659c --- /dev/null +++ b/Executable/Sources/Commands/OpenCommand.swift @@ -0,0 +1,29 @@ +import ArgumentParser +import ColibriLibrary + +extension Colibri { + struct Open: AsyncParsableCommand { + + // MARK: Properties + + static let configuration = CommandConfiguration( + commandName: "open-project", + abstract: "Open a Hummingbird app", + helpNames: .shortAndLong, + aliases: ["open"] + ) + + @OptionGroup var options: Options + + // MARK: Functions + + mutating func run() async throws { + let terminalService = TerminalService() + + let openProject = OpenProjectTask(terminalService: terminalService) + + try await openProject(with: options.ide, at: options.locationURL) + } + + } +} diff --git a/Executable/Sources/Extensions/IDE+Conformances.swift b/Executable/Sources/Extensions/IDE+Conformances.swift new file mode 100644 index 0000000..413ea49 --- /dev/null +++ b/Executable/Sources/Extensions/IDE+Conformances.swift @@ -0,0 +1,6 @@ +import ArgumentParser +import ColibriLibrary + +// MARK: - ExpressibleByArgument + +extension IDE: ExpressibleByArgument {} diff --git a/Executable/Sources/Options/OpenOptions.swift b/Executable/Sources/Options/OpenOptions.swift new file mode 100644 index 0000000..4f94ec7 --- /dev/null +++ b/Executable/Sources/Options/OpenOptions.swift @@ -0,0 +1,16 @@ +import ArgumentParser +import ColibriLibrary + +extension Colibri.Open { + struct Options: ParsableArguments, Locationable { + + // MARK: Properties + + @Option(name: .shortAndLong) + var ide: IDE = .xcode + + @Option(name: .shortAndLong) + var location: String? + + } +} -- 2.47.1 From e5e6dab51563c0048d549491d92f51c66ea072d1 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Thu, 20 Feb 2025 00:24:14 +0100 Subject: [PATCH 4/4] Added the Open command to the subcommands lists of the Colibri command in the executable target. --- Executable/Sources/Colibri.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Executable/Sources/Colibri.swift b/Executable/Sources/Colibri.swift index d39fac1..a32b262 100644 --- a/Executable/Sources/Colibri.swift +++ b/Executable/Sources/Colibri.swift @@ -11,6 +11,7 @@ struct Colibri: AsyncParsableCommand { Build.self, Clean.self, Create.self, + Open.self, Outdated.self, Update.self ], -- 2.47.1