From a3420423d66243e3e09e1158d8ca152c46c5737c Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Tue, 7 Jan 2025 01:41:10 +0100 Subject: [PATCH 01/19] Removed unnecessary comments from the Package file. --- Package.swift | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Package.swift b/Package.swift index 80334c3..7c0e04b 100644 --- a/Package.swift +++ b/Package.swift @@ -1,14 +1,10 @@ // swift-tools-version: 6.0 -// The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription let package = Package( name: "Colibri", targets: [ - // Targets are the basic building blocks of a package, defining a module or a test suite. - // Targets can depend on other targets in this package and products from dependencies. - .executableTarget( - name: "Colibri"), + .executableTarget(name: "Colibri"), ] ) -- 2.47.1 From 98dca62dcad48231bf6f5747d2c0811855863479 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Thu, 9 Jan 2025 09:24:55 +0100 Subject: [PATCH 02/19] Added the ArgumentParser package dependency to the package, and plugged it in into the executable target. --- Package.swift | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 7c0e04b..d632c08 100644 --- a/Package.swift +++ b/Package.swift @@ -4,7 +4,18 @@ import PackageDescription let package = Package( name: "Colibri", + dependencies: [ + .package(url: "https://github.com/apple/swift-argument-parser", + from: "1.0.0") + ], targets: [ - .executableTarget(name: "Colibri"), + .executableTarget( + name: "Colibri", + dependencies: [ + .product(name: "ArgumentParser", + package: "swift-argument-parser") + ], + path: "Sources" + ), ] ) -- 2.47.1 From 3e8e321c73978865939b72805fef1aebfc8edc47 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Thu, 9 Jan 2025 09:56:47 +0100 Subject: [PATCH 03/19] Created the Colibri command. --- Sources/Colibri.swift | 12 ++++++++++++ Sources/main.swift | 4 ---- 2 files changed, 12 insertions(+), 4 deletions(-) create mode 100644 Sources/Colibri.swift delete mode 100644 Sources/main.swift diff --git a/Sources/Colibri.swift b/Sources/Colibri.swift new file mode 100644 index 0000000..a9a5b9f --- /dev/null +++ b/Sources/Colibri.swift @@ -0,0 +1,12 @@ +import ArgumentParser + +@main +struct Colibri: AsyncParsableCommand { + + // MARK: Functions + + func run() async throws { + // ... + } + +} diff --git a/Sources/main.swift b/Sources/main.swift deleted file mode 100644 index 44e20d5..0000000 --- a/Sources/main.swift +++ /dev/null @@ -1,4 +0,0 @@ -// The Swift Programming Language -// https://docs.swift.org/swift-book - -print("Hello, world!") -- 2.47.1 From ecbec1f4c8cf41b92bf207cd4a6ed54cd54b4854 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sat, 11 Jan 2025 00:06:58 +0100 Subject: [PATCH 04/19] Created the library and test targets as well as the executable and library products for the Package file. --- Package.swift | 35 +++++++++++++++++++++---- Sources/{ => Executable}/Colibri.swift | 1 + Sources/Library/ColibriLibrary.swift | 1 + Tests/Library/ColibriLibraryTests.swift | 3 +++ 4 files changed, 35 insertions(+), 5 deletions(-) rename Sources/{ => Executable}/Colibri.swift (87%) create mode 100644 Sources/Library/ColibriLibrary.swift create mode 100644 Tests/Library/ColibriLibraryTests.swift diff --git a/Package.swift b/Package.swift index d632c08..5026c61 100644 --- a/Package.swift +++ b/Package.swift @@ -4,18 +4,43 @@ import PackageDescription let package = Package( name: "Colibri", + products: [ + .executable( + name: "colibri", + targets: ["Colibri"] + ), + .library( + name: "ColibriLibrary", + targets: ["ColibriLibrary"] + ) + ], dependencies: [ - .package(url: "https://github.com/apple/swift-argument-parser", - from: "1.0.0") + .package( + url: "https://github.com/apple/swift-argument-parser", + from: "1.0.0" + ) ], targets: [ .executableTarget( name: "Colibri", dependencies: [ - .product(name: "ArgumentParser", - package: "swift-argument-parser") + .product( + name: "ArgumentParser", + package: "swift-argument-parser" + ), + .target(name: "ColibriLibrary") ], - path: "Sources" + path: "Sources/Executable" ), + .target( + name: "ColibriLibrary", + dependencies: [], + path: "Sources/Library" + ), + .testTarget( + name: "ColibriTests", + dependencies: ["ColibriLibrary"], + path: "Tests/Library" + ) ] ) diff --git a/Sources/Colibri.swift b/Sources/Executable/Colibri.swift similarity index 87% rename from Sources/Colibri.swift rename to Sources/Executable/Colibri.swift index a9a5b9f..02690d1 100644 --- a/Sources/Colibri.swift +++ b/Sources/Executable/Colibri.swift @@ -1,4 +1,5 @@ import ArgumentParser +import ColibriLibrary @main struct Colibri: AsyncParsableCommand { diff --git a/Sources/Library/ColibriLibrary.swift b/Sources/Library/ColibriLibrary.swift new file mode 100644 index 0000000..fecc4ab --- /dev/null +++ b/Sources/Library/ColibriLibrary.swift @@ -0,0 +1 @@ +import Foundation diff --git a/Tests/Library/ColibriLibraryTests.swift b/Tests/Library/ColibriLibraryTests.swift new file mode 100644 index 0000000..5cfd2da --- /dev/null +++ b/Tests/Library/ColibriLibraryTests.swift @@ -0,0 +1,3 @@ +import Testing + +struct ColibriLibraryTests {} -- 2.47.1 From 6938b358e100d7cd9da162827aa5105d686b8998 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sat, 11 Jan 2025 01:26:26 +0100 Subject: [PATCH 05/19] Implemented the "currentFolder" property for the FileService service in the Library target. --- Sources/Library/Protocols/FileServicing.swift | 9 +++++++ Sources/Library/Services/FileService.swift | 27 +++++++++++++++++++ Tests/Library/Services/FileServiceTests.swift | 22 +++++++++++++++ 3 files changed, 58 insertions(+) create mode 100644 Sources/Library/Protocols/FileServicing.swift create mode 100644 Sources/Library/Services/FileService.swift create mode 100644 Tests/Library/Services/FileServiceTests.swift diff --git a/Sources/Library/Protocols/FileServicing.swift b/Sources/Library/Protocols/FileServicing.swift new file mode 100644 index 0000000..55dc203 --- /dev/null +++ b/Sources/Library/Protocols/FileServicing.swift @@ -0,0 +1,9 @@ +import Foundation + +protocol FileServicing { + + // MARK: Properties + + var currentFolder: URL { get async } + +} diff --git a/Sources/Library/Services/FileService.swift b/Sources/Library/Services/FileService.swift new file mode 100644 index 0000000..a81b2ce --- /dev/null +++ b/Sources/Library/Services/FileService.swift @@ -0,0 +1,27 @@ +import Foundation + +struct FileService: FileServicing { + + // MARK: Properties + + private let fileManager: FileManager + + // MARK: Initialisers + + init(fileManager: FileManager = .default) { + self.fileManager = fileManager + } + + // MARK: Computed + + var currentFolder: URL { + get async { + if #available(macOS 13.0, *) { + .init(filePath: fileManager.currentDirectoryPath) + } else { + .init(fileURLWithPath: fileManager.currentDirectoryPath) + } + } + } + +} diff --git a/Tests/Library/Services/FileServiceTests.swift b/Tests/Library/Services/FileServiceTests.swift new file mode 100644 index 0000000..01878a2 --- /dev/null +++ b/Tests/Library/Services/FileServiceTests.swift @@ -0,0 +1,22 @@ +import Testing + +@testable import ColibriLibrary + +struct FileServiceTests { + + // MARK: Properties tests + + @Test("Test the file service provides a current folder URL") + func currentFolder() async { + // GIVEN + let service = FileService() + + // WHEN + let url = await service.currentFolder + + // THEN + #expect(url.path() == "/private/tmp") + #expect(url.isFileURL == true) + } + +} -- 2.47.1 From 2128c0cde29d10b1cab33ff4c6a1df10c8435976 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sat, 11 Jan 2025 01:35:51 +0100 Subject: [PATCH 06/19] Removed boilerplate files from the Library and Tests targets. --- Sources/Library/ColibriLibrary.swift | 1 - Tests/Library/ColibriLibraryTests.swift | 3 --- 2 files changed, 4 deletions(-) delete mode 100644 Sources/Library/ColibriLibrary.swift delete mode 100644 Tests/Library/ColibriLibraryTests.swift diff --git a/Sources/Library/ColibriLibrary.swift b/Sources/Library/ColibriLibrary.swift deleted file mode 100644 index fecc4ab..0000000 --- a/Sources/Library/ColibriLibrary.swift +++ /dev/null @@ -1 +0,0 @@ -import Foundation diff --git a/Tests/Library/ColibriLibraryTests.swift b/Tests/Library/ColibriLibraryTests.swift deleted file mode 100644 index 5cfd2da..0000000 --- a/Tests/Library/ColibriLibraryTests.swift +++ /dev/null @@ -1,3 +0,0 @@ -import Testing - -struct ColibriLibraryTests {} -- 2.47.1 From 739fe0c8def2fdb09a6e17d9098ace40f2349668 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sat, 11 Jan 2025 03:19:59 +0100 Subject: [PATCH 07/19] Implemented the "init(at: )" initialiser function for the URL+Inits extension in the Library target. --- Sources/Library/Extensions/URL+Inits.swift | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 Sources/Library/Extensions/URL+Inits.swift diff --git a/Sources/Library/Extensions/URL+Inits.swift b/Sources/Library/Extensions/URL+Inits.swift new file mode 100644 index 0000000..7c31114 --- /dev/null +++ b/Sources/Library/Extensions/URL+Inits.swift @@ -0,0 +1,15 @@ +import Foundation + +extension URL { + + // MARK: Initialisers + + init(at filePath: String) { + if #available(macOS 13.0, *) { + self = URL(filePath: filePath) + } else { + self = URL(fileURLWithPath: filePath) + } + } + +} -- 2.47.1 From 7d0ad3461ad117f92c260c50fc96c56e6ad90576 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sat, 11 Jan 2025 03:24:22 +0100 Subject: [PATCH 08/19] Implemented the "exists(at: )" function for the FileService service in the module target. --- Sources/Library/Protocols/FileServicing.swift | 18 +++++- Sources/Library/Services/FileService.swift | 38 ++++++++++--- Tests/Library/Services/FileServiceTests.swift | 57 +++++++++++++++++-- 3 files changed, 98 insertions(+), 15 deletions(-) diff --git a/Sources/Library/Protocols/FileServicing.swift b/Sources/Library/Protocols/FileServicing.swift index 55dc203..cff10db 100644 --- a/Sources/Library/Protocols/FileServicing.swift +++ b/Sources/Library/Protocols/FileServicing.swift @@ -1,9 +1,23 @@ import Foundation -protocol FileServicing { +public protocol FileServicing { - // MARK: Properties + // MARK: Computed var currentFolder: URL { get async } + // MARK: Functions + + func exists(at url: URL) async throws (FileServiceError) -> Bool + +} + +// MARK: - Errors + +public enum FileServiceError: Error, Equatable { + case folderNotCreated + case folderNotDeleted + case urlAlreadyExists + case urlNotExists + case urlNotFileURL } diff --git a/Sources/Library/Services/FileService.swift b/Sources/Library/Services/FileService.swift index a81b2ce..4c164e7 100644 --- a/Sources/Library/Services/FileService.swift +++ b/Sources/Library/Services/FileService.swift @@ -1,6 +1,6 @@ import Foundation -struct FileService: FileServicing { +public struct FileService: FileServicing { // MARK: Properties @@ -8,19 +8,41 @@ struct FileService: FileServicing { // MARK: Initialisers - init(fileManager: FileManager = .default) { + public init(fileManager: FileManager = .default) { self.fileManager = fileManager } // MARK: Computed - var currentFolder: URL { + public var currentFolder: URL { get async { - if #available(macOS 13.0, *) { - .init(filePath: fileManager.currentDirectoryPath) - } else { - .init(fileURLWithPath: fileManager.currentDirectoryPath) - } + .init(at: fileManager.currentDirectoryPath) + } + } + + public func exists(at url: URL) async throws (FileServiceError) -> Bool { + guard url.isFileURL else { + throw FileServiceError.urlNotFileURL + } + + let filePath = getPath(for: url) + + return fileManager.fileExists(atPath: filePath) + } + +} + +// MARK: - Helpers + +private extension FileService { + + // MARK: Functions + + func getPath(for url: URL) -> String { + if #available(macOS 13.0, *) { + return url.path() + } else { + return url.path } } diff --git a/Tests/Library/Services/FileServiceTests.swift b/Tests/Library/Services/FileServiceTests.swift index 01878a2..7dde431 100644 --- a/Tests/Library/Services/FileServiceTests.swift +++ b/Tests/Library/Services/FileServiceTests.swift @@ -1,22 +1,69 @@ +import Foundation import Testing @testable import ColibriLibrary struct FileServiceTests { + + // MARK: Properties + + private let service: FileServicing + + // MARK: Initialisers + + init() { + self.service = FileService() + } // MARK: Properties tests - @Test("Test the file service provides a current folder URL") - func currentFolder() async { + @Test func currentFolder() async { // GIVEN - let service = FileService() - // WHEN let url = await service.currentFolder // THEN - #expect(url.path() == "/private/tmp") + #expect(url == .someExistingFolder) #expect(url.isFileURL == true) } + // GIVEN + // WHEN + // THEN + } + + @Test(arguments: zip([URL.someExistingFolder, .someExistingFile, .someNonExistingFolder, .someNonExistingFile], + [true, true, false, false])) + func exists( + with url: URL, + expects outcome: Bool + ) async throws { + // GIVEN + // WHEN + let result = try await service.exists(at: url) + + // THEN + #expect(result == outcome) + } + + @Test func existsThrows() async throws { + // GIVEN + let url = URL.someRandomURL + + // WHEN + // THEN + await #expect(throws: FileServiceError.urlNotFileURL) { + try await service.exists(at: url) + } + } } + +// MARK: - URL+Constants + +private extension URL { + static let someExistingFolder = URL(at: "/private/tmp") + static let someExistingFile = URL(at: "/etc/null") + static let someNonExistingFolder = URL(at: "/some/random/folder") + static let someNonExistingFile = URL(at: "/some/random/file.ext") + static let someRandomURL = URL(string: "https://some.random.url")! +} -- 2.47.1 From 58151a4e5a800da069165f2bc32c053f1100a0d5 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sat, 11 Jan 2025 04:44:02 +0100 Subject: [PATCH 09/19] Implemented the "delete(at: )" function for the FileService service in the module target. --- Sources/Library/Protocols/FileServicing.swift | 3 +- Sources/Library/Services/FileService.swift | 14 +++++++++ Tests/Library/Services/FileServiceTests.swift | 29 ++++++++++++++++++- 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/Sources/Library/Protocols/FileServicing.swift b/Sources/Library/Protocols/FileServicing.swift index cff10db..9f8d20a 100644 --- a/Sources/Library/Protocols/FileServicing.swift +++ b/Sources/Library/Protocols/FileServicing.swift @@ -8,6 +8,7 @@ public protocol FileServicing { // MARK: Functions + func delete(at url: URL) async throws (FileServiceError) func exists(at url: URL) async throws (FileServiceError) -> Bool } @@ -16,8 +17,8 @@ public protocol FileServicing { public enum FileServiceError: Error, Equatable { case folderNotCreated - case folderNotDeleted case urlAlreadyExists + case urlNotDeleted case urlNotExists case urlNotFileURL } diff --git a/Sources/Library/Services/FileService.swift b/Sources/Library/Services/FileService.swift index 4c164e7..d1d6397 100644 --- a/Sources/Library/Services/FileService.swift +++ b/Sources/Library/Services/FileService.swift @@ -20,6 +20,20 @@ public struct FileService: FileServicing { } } + // MARK: Functions + + public func delete(at url: URL) async throws (FileServiceError) { + guard try await exists(at: url) else { + throw FileServiceError.urlNotExists + } + + do { + try fileManager.removeItem(at: url) + } catch { + throw FileServiceError.urlNotDeleted + } + } + public func exists(at url: URL) async throws (FileServiceError) -> Bool { guard url.isFileURL else { throw FileServiceError.urlNotFileURL diff --git a/Tests/Library/Services/FileServiceTests.swift b/Tests/Library/Services/FileServiceTests.swift index 7dde431..9193264 100644 --- a/Tests/Library/Services/FileServiceTests.swift +++ b/Tests/Library/Services/FileServiceTests.swift @@ -26,9 +26,34 @@ struct FileServiceTests { #expect(url == .someExistingFolder) #expect(url.isFileURL == true) } + @Test(arguments: [URL.someNewFolder, .someNewFile]) + func delete(with url: URL) async throws { + // GIVEN + if try await !service.exists(at: url) { + try await service.createFolder(at: url) + } + + // WHEN + try await service.delete(at: url) + + // THEN + let result = try await service.exists(at: url) + + #expect(result == false) + } + + @Test(arguments: zip([URL.someNonExistingFolder, .someNonExistingFile, .someRandomURL], + [FileServiceError.urlNotExists, .urlNotExists, .urlNotFileURL])) + func deleteThrows( + with url: URL, + expects error: FileServiceError + ) async throws { // GIVEN // WHEN // THEN + await #expect(throws: error) { + try await service.delete(at: url) + } } @Test(arguments: zip([URL.someExistingFolder, .someExistingFile, .someNonExistingFolder, .someNonExistingFile], @@ -62,7 +87,9 @@ struct FileServiceTests { private extension URL { static let someExistingFolder = URL(at: "/private/tmp") - static let someExistingFile = URL(at: "/etc/null") + static let someExistingFile = URL(at: "/etc/ssh/ssh_config") + static let someNewFolder = URL(at: "/private/tmp/folder") + static let someNewFile = URL(at: "/private/tmp/file.ext") static let someNonExistingFolder = URL(at: "/some/random/folder") static let someNonExistingFile = URL(at: "/some/random/file.ext") static let someRandomURL = URL(string: "https://some.random.url")! -- 2.47.1 From 1d080cce45210644ef0e906cbe5fd8e225b2704a Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sat, 11 Jan 2025 05:08:06 +0100 Subject: [PATCH 10/19] Implemented the "createFolder(at: )" function for the FileService service in the module target. --- Sources/Library/Protocols/FileServicing.swift | 1 + Sources/Library/Services/FileService.swift | 15 +++++++ Tests/Library/Services/FileServiceTests.swift | 41 +++++++++++++++++-- 3 files changed, 53 insertions(+), 4 deletions(-) diff --git a/Sources/Library/Protocols/FileServicing.swift b/Sources/Library/Protocols/FileServicing.swift index 9f8d20a..5f85840 100644 --- a/Sources/Library/Protocols/FileServicing.swift +++ b/Sources/Library/Protocols/FileServicing.swift @@ -8,6 +8,7 @@ public protocol FileServicing { // MARK: Functions + func createFolder(at url: URL) async throws (FileServiceError) func delete(at url: URL) async throws (FileServiceError) func exists(at url: URL) async throws (FileServiceError) -> Bool diff --git a/Sources/Library/Services/FileService.swift b/Sources/Library/Services/FileService.swift index d1d6397..3e91ad2 100644 --- a/Sources/Library/Services/FileService.swift +++ b/Sources/Library/Services/FileService.swift @@ -22,6 +22,21 @@ public struct FileService: FileServicing { // MARK: Functions + public func createFolder(at url: URL) async throws (FileServiceError) { + guard try await !exists(at: url) else { + throw FileServiceError.urlAlreadyExists + } + + do { + try fileManager.createDirectory( + at: url, + withIntermediateDirectories: true + ) + } catch { + throw FileServiceError.folderNotCreated + } + } + public func delete(at url: URL) async throws (FileServiceError) { guard try await exists(at: url) else { throw FileServiceError.urlNotExists diff --git a/Tests/Library/Services/FileServiceTests.swift b/Tests/Library/Services/FileServiceTests.swift index 9193264..8a53c0e 100644 --- a/Tests/Library/Services/FileServiceTests.swift +++ b/Tests/Library/Services/FileServiceTests.swift @@ -26,6 +26,41 @@ struct FileServiceTests { #expect(url == .someExistingFolder) #expect(url.isFileURL == true) } + + // MARK: Functions + + @Test(arguments: [URL.someNewFolder, .someNewFile]) + func createFolder(with url: URL) async throws { + // GIVEN + if try await service.exists(at: url) { + try await service.delete(at: url) + } + + // WHEN + try await service.createFolder(at: url) + + // THEN + let result = try await service.exists(at: url) + + #expect(result == true) + + try await service.delete(at: url) + } + + @Test(arguments: zip([URL.someExistingFolder, .someExistingFile, .someRandomURL], + [FileServiceError.urlAlreadyExists, .urlAlreadyExists, .urlNotFileURL])) + func createFolderThrows( + with url: URL, + expects error: FileServiceError + ) async throws { + // GIVEN + // WHEN + // THEN + await #expect(throws: error) { + try await service.createFolder(at: url) + } + } + @Test(arguments: [URL.someNewFolder, .someNewFile]) func delete(with url: URL) async throws { // GIVEN @@ -42,7 +77,7 @@ struct FileServiceTests { #expect(result == false) } - @Test(arguments: zip([URL.someNonExistingFolder, .someNonExistingFile, .someRandomURL], + @Test(arguments: zip([URL.someNewFolder, .someNewFile, .someRandomURL], [FileServiceError.urlNotExists, .urlNotExists, .urlNotFileURL])) func deleteThrows( with url: URL, @@ -56,7 +91,7 @@ struct FileServiceTests { } } - @Test(arguments: zip([URL.someExistingFolder, .someExistingFile, .someNonExistingFolder, .someNonExistingFile], + @Test(arguments: zip([URL.someExistingFolder, .someExistingFile, .someNewFolder, .someNewFile], [true, true, false, false])) func exists( with url: URL, @@ -90,7 +125,5 @@ private extension URL { static let someExistingFile = URL(at: "/etc/ssh/ssh_config") static let someNewFolder = URL(at: "/private/tmp/folder") static let someNewFile = URL(at: "/private/tmp/file.ext") - static let someNonExistingFolder = URL(at: "/some/random/folder") - static let someNonExistingFile = URL(at: "/some/random/file.ext") static let someRandomURL = URL(string: "https://some.random.url")! } -- 2.47.1 From 4b900818ec384030d88f4d98f58ac1a234a27bbb Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sat, 11 Jan 2025 11:27:39 +0100 Subject: [PATCH 11/19] Implemented the FileServiceSpy spy in the Tests target. --- .../Helpers/Spies/FileServiceSpy.swift | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 Tests/Library/Helpers/Spies/FileServiceSpy.swift diff --git a/Tests/Library/Helpers/Spies/FileServiceSpy.swift b/Tests/Library/Helpers/Spies/FileServiceSpy.swift new file mode 100644 index 0000000..c149c14 --- /dev/null +++ b/Tests/Library/Helpers/Spies/FileServiceSpy.swift @@ -0,0 +1,47 @@ +import Foundation + +@testable import ColibriLibrary + +final class FileServiceSpy { + + // MARK: Properties + + private(set) var isCreateFolderCalled: Bool = false + private(set) var isDeleteCalled: Bool = false + private(set) var isExistsAtCalled: Bool = false + private(set) var urlCalled: URL? + +} + +// MARK: - FileServicing + +extension FileServiceSpy: FileServicing { + var currentFolder: URL { + get async { .someCurrentFolder } + } + + func createFolder(at url: URL) async throws(FileServiceError) { + isCreateFolderCalled = true + urlCalled = url + } + + func delete(at url: URL) async throws(FileServiceError) { + isDeleteCalled = true + urlCalled = url + } + + @discardableResult + func exists(at url: URL) async throws(FileServiceError) -> Bool { + isExistsAtCalled = true + urlCalled = url + + return .random() + } + +} + +// MARK: - URL+Constants + +private extension URL { + static let someCurrentFolder = URL(at: "some/current/folder") +} -- 2.47.1 From a7be1ec0b094ae3073115280f0764f9d857a37f9 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sat, 11 Jan 2025 11:28:27 +0100 Subject: [PATCH 12/19] Implemented the FileServiceMock mock in the Tests target. --- .../Helpers/Mocks/FileServiceMock.swift | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 Tests/Library/Helpers/Mocks/FileServiceMock.swift diff --git a/Tests/Library/Helpers/Mocks/FileServiceMock.swift b/Tests/Library/Helpers/Mocks/FileServiceMock.swift new file mode 100644 index 0000000..fa7b6d7 --- /dev/null +++ b/Tests/Library/Helpers/Mocks/FileServiceMock.swift @@ -0,0 +1,84 @@ +import ColibriLibrary +import Foundation + +struct FileServiceMock { + + // MARK: Properties + + private let action: Action? + private let folder: URL + + private weak var spy: FileServiceSpy? + + // MARK: Initialisers + + init( + currentFolder: URL, + action: Action? = nil, + spy: FileServiceSpy? = nil + ) { + self.action = action + self.folder = currentFolder + self.spy = spy + } + +} + +// MARK: - FileServicing + +extension FileServiceMock: FileServicing { + + // MARK: Computed + + var currentFolder: URL { + get async { folder } + } + + // MARK: Functions + + func createFolder(at url: URL) async throws(FileServiceError) { + switch action { + case .error(let error): + throw error + case let .createFolder(url): + try await spy?.createFolder(at: url) + default: + break + } + } + + func delete(at url: URL) async throws(FileServiceError) { + switch action { + case .error(let error): + throw error + case let .delete(url): + try await spy?.delete(at: url) + default: + break + } + } + + func exists(at url: URL) async throws(FileServiceError) -> Bool { + switch action { + case .error(let error): + throw error + case let .exists(url, exists): + try await spy?.exists(at: url) + return exists + default: + return false + } + } + +} + +// MARK: - Enumerations + +extension FileServiceMock { + enum Action { + case createFolder(URL) + case delete(URL) + case error(FileServiceError) + case exists(URL, Bool) + } +} -- 2.47.1 From 3f98d08a00b3a1afa95faa3724a82c517548f6c6 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sat, 11 Jan 2025 11:33:19 +0100 Subject: [PATCH 13/19] Written test cases for the FileServicing conformance using relevant mock and spy in the Tests target. --- .../Cases/Services/FileServiceTests.swift | 166 ++++++++++++++++++ Tests/Library/Services/FileServiceTests.swift | 129 -------------- 2 files changed, 166 insertions(+), 129 deletions(-) create mode 100644 Tests/Library/Cases/Services/FileServiceTests.swift delete mode 100644 Tests/Library/Services/FileServiceTests.swift diff --git a/Tests/Library/Cases/Services/FileServiceTests.swift b/Tests/Library/Cases/Services/FileServiceTests.swift new file mode 100644 index 0000000..092ee9c --- /dev/null +++ b/Tests/Library/Cases/Services/FileServiceTests.swift @@ -0,0 +1,166 @@ +import Foundation +import Testing + +@testable import ColibriLibrary + +struct FileServiceTests { + + // MARK: Properties + + private let spy = FileServiceSpy() + + // MARK: Properties tests + + @Test func currentFolder() async { + // GIVEN + let url: URL = .someCurrentFolder + + let service = FileServiceMock(currentFolder: url) + + // WHEN + let folder = await service.currentFolder + + // THEN + #expect(folder == url) + #expect(folder.isFileURL == true) + } + + // MARK: Functions + + @Test(arguments: [URL.someNewFolder, .someNewFile]) + func createFolder(with url: URL) async throws { + // GIVEN + let service = FileServiceMock( + currentFolder: .someCurrentFolder, + action: .createFolder(url), + spy: spy + ) + + // WHEN + try await service.createFolder(at: url) + + // THEN + #expect(spy.isCreateFolderCalled == true) + #expect(spy.urlCalled == url) + } + + @Test(arguments: zip([URL.someExistingFolder, .someExistingFile, .someRandomURL], + [FileServiceError.urlAlreadyExists, .urlAlreadyExists, .urlNotFileURL])) + func createFolder( + with url: URL, + throws error: FileServiceError + ) async throws { + // GIVEN + let service = FileServiceMock( + currentFolder: .someCurrentFolder, + action: .error(error), + spy: spy + ) + + // WHEN + // THEN + await #expect(throws: error) { + try await service.createFolder(at: url) + } + + #expect(spy.isCreateFolderCalled == false) + #expect(spy.urlCalled == nil) + } + + @Test(arguments: [URL.someNewFolder, .someNewFile]) + func delete(with url: URL) async throws { + // GIVEN + let service = FileServiceMock( + currentFolder: .someCurrentFolder, + action: .delete(url), + spy: spy + ) + + // WHEN + try await service.delete(at: url) + + // THEN + #expect(spy.isDeleteCalled == true) + #expect(spy.urlCalled == url) + } + + @Test(arguments: zip([URL.someNewFolder, .someNewFile, .someRandomURL], + [FileServiceError.urlNotExists, .urlNotExists, .urlNotFileURL])) + func delete( + with url: URL, + throws error: FileServiceError + ) async throws { + // GIVEN + let service = FileServiceMock( + currentFolder: .someCurrentFolder, + action: .error(error), + spy: spy + ) + + // WHEN + // THEN + await #expect(throws: error) { + try await service.delete(at: url) + } + + #expect(spy.isDeleteCalled == false) + #expect(spy.urlCalled == nil) + } + + @Test(arguments: zip([URL.someExistingFolder, .someExistingFile, .someNewFolder, .someNewFile], + [true, true, false, false])) + func exists( + with url: URL, + expects outcome: Bool + ) async throws { + // GIVEN + let service = FileServiceMock( + currentFolder: .someCurrentFolder, + action: .exists(url, outcome), + spy: spy + ) + + // WHEN + let result = try await service.exists(at: url) + + // THEN + #expect(result == outcome) + + #expect(spy.isExistsAtCalled == true) + #expect(spy.urlCalled == url) + } + + @Test(arguments: zip([URL.someRandomURL], [FileServiceError.urlNotFileURL])) + func exists( + with url: URL, + throws error: FileServiceError + ) async throws { + // GIVEN + let service = FileServiceMock( + currentFolder: .someCurrentFolder, + action: .error(error), + spy: spy + ) + + // WHEN + // THEN + await #expect(throws: error) { + try await service.exists(at: url) + } + + #expect(spy.isExistsAtCalled == false) + #expect(spy.urlCalled == nil) + } + +} + +// MARK: - URL+Constants + +private extension URL { + static let someCurrentFolder = URL(at: "/some/current/folder") + static let someExistingFolder = URL(at: "/some/existing/folder") + static let someExistingFile = URL(at: "/some/existing/file") + static let someNewFolder = URL(at: "/some/new/folder") + static let someNewFile = URL(at: "/some/new/file") + static let someRandomURL = URL(string: "some.random.url")! +} diff --git a/Tests/Library/Services/FileServiceTests.swift b/Tests/Library/Services/FileServiceTests.swift deleted file mode 100644 index 8a53c0e..0000000 --- a/Tests/Library/Services/FileServiceTests.swift +++ /dev/null @@ -1,129 +0,0 @@ -import Foundation -import Testing - -@testable import ColibriLibrary - -struct FileServiceTests { - - // MARK: Properties - - private let service: FileServicing - - // MARK: Initialisers - - init() { - self.service = FileService() - } - - // MARK: Properties tests - - @Test func currentFolder() async { - // GIVEN - // WHEN - let url = await service.currentFolder - - // THEN - #expect(url == .someExistingFolder) - #expect(url.isFileURL == true) - } - - // MARK: Functions - - @Test(arguments: [URL.someNewFolder, .someNewFile]) - func createFolder(with url: URL) async throws { - // GIVEN - if try await service.exists(at: url) { - try await service.delete(at: url) - } - - // WHEN - try await service.createFolder(at: url) - - // THEN - let result = try await service.exists(at: url) - - #expect(result == true) - - try await service.delete(at: url) - } - - @Test(arguments: zip([URL.someExistingFolder, .someExistingFile, .someRandomURL], - [FileServiceError.urlAlreadyExists, .urlAlreadyExists, .urlNotFileURL])) - func createFolderThrows( - with url: URL, - expects error: FileServiceError - ) async throws { - // GIVEN - // WHEN - // THEN - await #expect(throws: error) { - try await service.createFolder(at: url) - } - } - - @Test(arguments: [URL.someNewFolder, .someNewFile]) - func delete(with url: URL) async throws { - // GIVEN - if try await !service.exists(at: url) { - try await service.createFolder(at: url) - } - - // WHEN - try await service.delete(at: url) - - // THEN - let result = try await service.exists(at: url) - - #expect(result == false) - } - - @Test(arguments: zip([URL.someNewFolder, .someNewFile, .someRandomURL], - [FileServiceError.urlNotExists, .urlNotExists, .urlNotFileURL])) - func deleteThrows( - with url: URL, - expects error: FileServiceError - ) async throws { - // GIVEN - // WHEN - // THEN - await #expect(throws: error) { - try await service.delete(at: url) - } - } - - @Test(arguments: zip([URL.someExistingFolder, .someExistingFile, .someNewFolder, .someNewFile], - [true, true, false, false])) - func exists( - with url: URL, - expects outcome: Bool - ) async throws { - // GIVEN - // WHEN - let result = try await service.exists(at: url) - - // THEN - #expect(result == outcome) - } - - @Test func existsThrows() async throws { - // GIVEN - let url = URL.someRandomURL - - // WHEN - // THEN - await #expect(throws: FileServiceError.urlNotFileURL) { - try await service.exists(at: url) - } - } - -} - -// MARK: - URL+Constants - -private extension URL { - static let someExistingFolder = URL(at: "/private/tmp") - static let someExistingFile = URL(at: "/etc/ssh/ssh_config") - static let someNewFolder = URL(at: "/private/tmp/folder") - static let someNewFile = URL(at: "/private/tmp/file.ext") - static let someRandomURL = URL(string: "https://some.random.url")! -} -- 2.47.1 From af1703e6c165a0db9e3c2181e30b2069c18a9bb2 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sat, 11 Jan 2025 18:40:09 +0100 Subject: [PATCH 14/19] Implemented the CreatedRootFolderTask task in the Library target. --- .../Library/Tasks/CreateRootFolderTask.swift | 38 +++++++++++ .../Cases/Services/FileServiceTests.swift | 14 +--- .../Tasks/CreateRootFolderTaskTests.swift | 64 +++++++++++++++++++ .../Helpers/Extensions/URL+Samples.swift | 16 +++++ .../Helpers/Spies/FileServiceSpy.swift | 6 -- 5 files changed, 119 insertions(+), 19 deletions(-) create mode 100644 Sources/Library/Tasks/CreateRootFolderTask.swift create mode 100644 Tests/Library/Cases/Tasks/CreateRootFolderTaskTests.swift create mode 100644 Tests/Library/Helpers/Extensions/URL+Samples.swift diff --git a/Sources/Library/Tasks/CreateRootFolderTask.swift b/Sources/Library/Tasks/CreateRootFolderTask.swift new file mode 100644 index 0000000..805b7c2 --- /dev/null +++ b/Sources/Library/Tasks/CreateRootFolderTask.swift @@ -0,0 +1,38 @@ +import Foundation + +public struct CreateRootFolderTask { + + // MARK: Properties + + private let fileService: FileServicing + + // MARK: Initialisers + + public init(fileService: FileServicing) { + self.fileService = fileService + } + + // MARK: Functions + + public func callAsFunction( + name: String, + at location: URL? = nil + ) async throws -> URL { + let rootFolder = if let location { + location + } else { + await fileService.currentFolder + } + + let newFolder = if #available(macOS 13.0, *) { + rootFolder.appending(path: name) + } else { + rootFolder.appendingPathComponent(name) + } + + try await fileService.createFolder(at: newFolder) + + return newFolder + } + +} diff --git a/Tests/Library/Cases/Services/FileServiceTests.swift b/Tests/Library/Cases/Services/FileServiceTests.swift index 092ee9c..f686fe2 100644 --- a/Tests/Library/Cases/Services/FileServiceTests.swift +++ b/Tests/Library/Cases/Services/FileServiceTests.swift @@ -1,8 +1,7 @@ +import ColibriLibrary import Foundation import Testing -@testable import ColibriLibrary - struct FileServiceTests { // MARK: Properties @@ -153,14 +152,3 @@ struct FileServiceTests { } } - -// MARK: - URL+Constants - -private extension URL { - static let someCurrentFolder = URL(at: "/some/current/folder") - static let someExistingFolder = URL(at: "/some/existing/folder") - static let someExistingFile = URL(at: "/some/existing/file") - static let someNewFolder = URL(at: "/some/new/folder") - static let someNewFile = URL(at: "/some/new/file") - static let someRandomURL = URL(string: "some.random.url")! -} diff --git a/Tests/Library/Cases/Tasks/CreateRootFolderTaskTests.swift b/Tests/Library/Cases/Tasks/CreateRootFolderTaskTests.swift new file mode 100644 index 0000000..72ce72e --- /dev/null +++ b/Tests/Library/Cases/Tasks/CreateRootFolderTaskTests.swift @@ -0,0 +1,64 @@ +import ColibriLibrary +import Foundation +import Testing + +struct CreateRootFolderTaskTests { + + // MARK: Functions tests + + @Test(arguments: [String.someProjectName], [URL.someCurrentProjectFolder, .someNewProjectFolder]) + func task( + name: String, + expects folder: URL + ) async throws { + // GIVEN + let fileService = FileServiceMock( + currentFolder: .someCurrentFolder, + action: .createFolder(folder) + ) + + let task = CreateRootFolderTask(fileService: fileService) + + // WHEN + let result = try await task(name: name, + at: folder == .someNewProjectFolder ? .someNewFolder : nil) + + // THEN + #expect(result == folder) + #expect(result.isFileURL == true) + } + + @Test(arguments: [String.someProjectName], [FileServiceError.urlAlreadyExists]) + func task( + name: String, + throws error: FileServiceError + ) async throws { + // GIVEN + let fileService = FileServiceMock( + currentFolder: .someCurrentFolder, + action: .error(error) + ) + + let task = CreateRootFolderTask(fileService: fileService) + + // WHEN + // THEN + await #expect(throws: error) { + try await task(name: name) + } + } + +} + +// MARK: - String+Constants + +private extension String { + static let someProjectName = "SomeProjectName" +} + +// MARK: - URL+Constants + +private extension URL { + static let someCurrentProjectFolder = URL.someCurrentFolder.appending(component: String.someProjectName) + static let someNewProjectFolder = URL.someNewFolder.appending(component: String.someProjectName) +} diff --git a/Tests/Library/Helpers/Extensions/URL+Samples.swift b/Tests/Library/Helpers/Extensions/URL+Samples.swift new file mode 100644 index 0000000..1d0622a --- /dev/null +++ b/Tests/Library/Helpers/Extensions/URL+Samples.swift @@ -0,0 +1,16 @@ +import Foundation + +@testable import ColibriLibrary + +extension URL { + + // MARK: Constants + + static let someCurrentFolder = URL(at: "/some/current/folder") + static let someExistingFolder = URL(at: "/some/existing/folder") + static let someExistingFile = URL(at: "/some/existing/file") + static let someNewFolder = URL(at: "/some/new/folder") + static let someNewFile = URL(at: "/some/new/file") + static let someRandomURL = URL(string: "some.random.url")! + +} diff --git a/Tests/Library/Helpers/Spies/FileServiceSpy.swift b/Tests/Library/Helpers/Spies/FileServiceSpy.swift index c149c14..6422bc9 100644 --- a/Tests/Library/Helpers/Spies/FileServiceSpy.swift +++ b/Tests/Library/Helpers/Spies/FileServiceSpy.swift @@ -39,9 +39,3 @@ extension FileServiceSpy: FileServicing { } } - -// MARK: - URL+Constants - -private extension URL { - static let someCurrentFolder = URL(at: "some/current/folder") -} -- 2.47.1 From 15d1e22f1c33e6574f382870e159a94cfc7fe36c Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sun, 12 Jan 2025 01:33:52 +0100 Subject: [PATCH 15/19] Defined the minimum platform requirements for the Package file. --- Package.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Package.swift b/Package.swift index 5026c61..d8870c1 100644 --- a/Package.swift +++ b/Package.swift @@ -4,6 +4,9 @@ import PackageDescription let package = Package( name: "Colibri", + platforms: [ + .macOS(.v10_15) + ], products: [ .executable( name: "colibri", -- 2.47.1 From db1df0ec62bf505d0583dee541d747c18cabd1d7 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sun, 12 Jan 2025 01:42:43 +0100 Subject: [PATCH 16/19] Created the basis of the Create command in the executable target. --- Sources/Executable/Colibri.swift | 12 +++--- Sources/Executable/Commands/Create.swift | 41 +++++++++++++++++++ .../Helpers/Extensions/URL+Samples.swift | 2 +- 3 files changed, 48 insertions(+), 7 deletions(-) create mode 100644 Sources/Executable/Commands/Create.swift diff --git a/Sources/Executable/Colibri.swift b/Sources/Executable/Colibri.swift index 02690d1..a27ae37 100644 --- a/Sources/Executable/Colibri.swift +++ b/Sources/Executable/Colibri.swift @@ -1,13 +1,13 @@ import ArgumentParser -import ColibriLibrary @main struct Colibri: AsyncParsableCommand { - // MARK: Functions - - func run() async throws { - // ... - } + // MARK: Properties + + static let configuration = CommandConfiguration( + abstract: "The utility to manage your Hummingbird projects", + subcommands: [Create.self] + ) } diff --git a/Sources/Executable/Commands/Create.swift b/Sources/Executable/Commands/Create.swift new file mode 100644 index 0000000..d65c947 --- /dev/null +++ b/Sources/Executable/Commands/Create.swift @@ -0,0 +1,41 @@ +import ArgumentParser +import ColibriLibrary + +extension Colibri { + struct Create: AsyncParsableCommand { + + // MARK: Properties + + static let configuration = CommandConfiguration( + commandName: "create project", + abstract: "Create a new, tailored Colibri project.", + helpNames: .shortAndLong, + aliases: ["create"] + ) + + @OptionGroup var options: Options + + // MARK: Functions + + mutating func run() async throws { + + } + + } +} + +// MARK: - Options + +extension Colibri.Create { + struct Options: ParsableArguments { + + // MARK: Properties + + @Option(name: .shortAndLong) + var name: String + + @Option(name: .shortAndLong) + var location: String? + + } +} diff --git a/Tests/Library/Helpers/Extensions/URL+Samples.swift b/Tests/Library/Helpers/Extensions/URL+Samples.swift index 1d0622a..83c9f6a 100644 --- a/Tests/Library/Helpers/Extensions/URL+Samples.swift +++ b/Tests/Library/Helpers/Extensions/URL+Samples.swift @@ -11,6 +11,6 @@ extension URL { static let someExistingFile = URL(at: "/some/existing/file") static let someNewFolder = URL(at: "/some/new/folder") static let someNewFile = URL(at: "/some/new/file") - static let someRandomURL = URL(string: "some.random.url")! + static let someRandomURL = URL(string: "http://some.random.url")! } -- 2.47.1 From 6bf9c30ad17e33d7cec45af8ffc1dd97949feea9 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sun, 12 Jan 2025 02:16:30 +0100 Subject: [PATCH 17/19] Improved the CreatedRootFolderTask task in the library target to throw error in case the function receives an empty name. --- .../Library/Tasks/CreateRootFolderTask.swift | 10 ++++++ .../Tasks/CreateRootFolderTaskTests.swift | 31 +++++++++++++++++-- .../Helpers/Extensions/URL+Samples.swift | 2 ++ 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/Sources/Library/Tasks/CreateRootFolderTask.swift b/Sources/Library/Tasks/CreateRootFolderTask.swift index 805b7c2..fdee7e0 100644 --- a/Sources/Library/Tasks/CreateRootFolderTask.swift +++ b/Sources/Library/Tasks/CreateRootFolderTask.swift @@ -18,6 +18,10 @@ public struct CreateRootFolderTask { name: String, at location: URL? = nil ) async throws -> URL { + guard !name.isEmpty else { + throw CreateRootFolderError.nameIsEmpty + } + let rootFolder = if let location { location } else { @@ -36,3 +40,9 @@ public struct CreateRootFolderTask { } } + +// MARK: - Errors + +public enum CreateRootFolderError: Error { + case nameIsEmpty +} diff --git a/Tests/Library/Cases/Tasks/CreateRootFolderTaskTests.swift b/Tests/Library/Cases/Tasks/CreateRootFolderTaskTests.swift index 72ce72e..a3f6795 100644 --- a/Tests/Library/Cases/Tasks/CreateRootFolderTaskTests.swift +++ b/Tests/Library/Cases/Tasks/CreateRootFolderTaskTests.swift @@ -6,12 +6,19 @@ struct CreateRootFolderTaskTests { // MARK: Functions tests - @Test(arguments: [String.someProjectName], [URL.someCurrentProjectFolder, .someNewProjectFolder]) + @Test(arguments: [String.someProjectName], [URL.someCurrentProjectFolder, .someNewProjectFolder, .someDotProjectFolder, .someTildeProjectFolder]) func task( name: String, expects folder: URL ) async throws { // GIVEN + let location: URL? = switch folder { + case .someNewProjectFolder: .someNewFolder + case .someDotProjectFolder: .someDotFolder + case .someTildeProjectFolder: .someTildeFolder + default: nil + } + let fileService = FileServiceMock( currentFolder: .someCurrentFolder, action: .createFolder(folder) @@ -21,7 +28,7 @@ struct CreateRootFolderTaskTests { // WHEN let result = try await task(name: name, - at: folder == .someNewProjectFolder ? .someNewFolder : nil) + at: location) // THEN #expect(result == folder) @@ -47,12 +54,30 @@ struct CreateRootFolderTaskTests { try await task(name: name) } } + + @Test(arguments: [String.someEmptyName], [CreateRootFolderError.nameIsEmpty]) + func task( + name: String, + throws error: CreateRootFolderError + ) async throws { + // GIVEN + let fileService = FileServiceMock(currentFolder: .someCurrentFolder) + + let task = CreateRootFolderTask(fileService: fileService) + + // WHEN + // THEN + await #expect(throws: error) { + try await task(name: name) + } + } } // MARK: - String+Constants private extension String { + static let someEmptyName = "" static let someProjectName = "SomeProjectName" } @@ -60,5 +85,7 @@ private extension String { private extension URL { static let someCurrentProjectFolder = URL.someCurrentFolder.appending(component: String.someProjectName) + static let someDotProjectFolder = URL.someDotFolder.appending(component: String.someProjectName) static let someNewProjectFolder = URL.someNewFolder.appending(component: String.someProjectName) + static let someTildeProjectFolder = URL.someTildeFolder.appending(component: String.someProjectName) } diff --git a/Tests/Library/Helpers/Extensions/URL+Samples.swift b/Tests/Library/Helpers/Extensions/URL+Samples.swift index 83c9f6a..5869bb5 100644 --- a/Tests/Library/Helpers/Extensions/URL+Samples.swift +++ b/Tests/Library/Helpers/Extensions/URL+Samples.swift @@ -7,10 +7,12 @@ extension URL { // MARK: Constants static let someCurrentFolder = URL(at: "/some/current/folder") + static let someDotFolder = URL(at: ".") static let someExistingFolder = URL(at: "/some/existing/folder") static let someExistingFile = URL(at: "/some/existing/file") static let someNewFolder = URL(at: "/some/new/folder") static let someNewFile = URL(at: "/some/new/file") static let someRandomURL = URL(string: "http://some.random.url")! + static let someTildeFolder = URL(at: "~") } -- 2.47.1 From c1b3fb50a3e91f70ddc873c1b88dded6ff7ec42b Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sun, 12 Jan 2025 02:41:17 +0100 Subject: [PATCH 18/19] Implemented the creation of a root folder within the Create command in the executable target. --- Sources/Executable/Colibri.swift | 2 +- Sources/Executable/Commands/Create.swift | 21 +++++++++++++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/Sources/Executable/Colibri.swift b/Sources/Executable/Colibri.swift index a27ae37..194d287 100644 --- a/Sources/Executable/Colibri.swift +++ b/Sources/Executable/Colibri.swift @@ -6,7 +6,7 @@ struct Colibri: AsyncParsableCommand { // MARK: Properties static let configuration = CommandConfiguration( - abstract: "The utility to manage your Hummingbird projects", + abstract: "The utility to manage your Hummingbird apps", subcommands: [Create.self] ) diff --git a/Sources/Executable/Commands/Create.swift b/Sources/Executable/Commands/Create.swift index d65c947..83787f1 100644 --- a/Sources/Executable/Commands/Create.swift +++ b/Sources/Executable/Commands/Create.swift @@ -1,5 +1,6 @@ import ArgumentParser import ColibriLibrary +import Foundation extension Colibri { struct Create: AsyncParsableCommand { @@ -7,18 +8,24 @@ extension Colibri { // MARK: Properties static let configuration = CommandConfiguration( - commandName: "create project", - abstract: "Create a new, tailored Colibri project.", + commandName: "create-project", + abstract: "Create a new, tailored Hummingbird app", helpNames: .shortAndLong, aliases: ["create"] ) @OptionGroup var options: Options - + // MARK: Functions mutating func run() async throws { - + let fileService = FileService() + let createRootFolder = CreateRootFolderTask(fileService: fileService) + + let rootFolder = try await createRootFolder(name: options.name, + at: options.locationURL) + + print(rootFolder) } } @@ -36,6 +43,12 @@ extension Colibri.Create { @Option(name: .shortAndLong) var location: String? + + // MARK: Computed + + var locationURL: URL? { + location.flatMap { URL(fileURLWithPath: $0) } + } } } -- 2.47.1 From cbd3789ca7e45e13be4faf9107473b5f3b45d50b Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sun, 12 Jan 2025 02:41:17 +0100 Subject: [PATCH 19/19] Implemented the creation of a root folder within the Create command in the executable target. --- Sources/Executable/Colibri.swift | 2 +- Sources/Executable/Commands/Create.swift | 23 +++++++++++++++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/Sources/Executable/Colibri.swift b/Sources/Executable/Colibri.swift index a27ae37..194d287 100644 --- a/Sources/Executable/Colibri.swift +++ b/Sources/Executable/Colibri.swift @@ -6,7 +6,7 @@ struct Colibri: AsyncParsableCommand { // MARK: Properties static let configuration = CommandConfiguration( - abstract: "The utility to manage your Hummingbird projects", + abstract: "The utility to manage your Hummingbird apps", subcommands: [Create.self] ) diff --git a/Sources/Executable/Commands/Create.swift b/Sources/Executable/Commands/Create.swift index d65c947..a8d4373 100644 --- a/Sources/Executable/Commands/Create.swift +++ b/Sources/Executable/Commands/Create.swift @@ -1,5 +1,6 @@ import ArgumentParser import ColibriLibrary +import Foundation extension Colibri { struct Create: AsyncParsableCommand { @@ -7,18 +8,26 @@ extension Colibri { // MARK: Properties static let configuration = CommandConfiguration( - commandName: "create project", - abstract: "Create a new, tailored Colibri project.", + commandName: "create-project", + abstract: "Create a new, tailored Hummingbird app", helpNames: .shortAndLong, aliases: ["create"] ) @OptionGroup var options: Options - + // MARK: Functions mutating func run() async throws { - + let fileService = FileService() + let createRootFolder = CreateRootFolderTask(fileService: fileService) + + let rootFolder = try await createRootFolder( + name: options.name, + at: options.locationURL + ) + + print(rootFolder) } } @@ -36,6 +45,12 @@ extension Colibri.Create { @Option(name: .shortAndLong) var location: String? + + // MARK: Computed + + var locationURL: URL? { + location.flatMap { URL(fileURLWithPath: $0) } + } } } -- 2.47.1