diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/GitVersionTests.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/GitVersionTests.xcscheme new file mode 100644 index 0000000..12529df --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/GitVersionTests.xcscheme @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/swift-git-version.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/swift-git-version.xcscheme new file mode 100644 index 0000000..8d3c550 --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/swift-git-version.xcscheme @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Package.swift b/Package.swift index 65a3d3b..f02a232 100644 --- a/Package.swift +++ b/Package.swift @@ -24,8 +24,8 @@ let package = Package( ] ), .testTarget( - name: "swift-git-versionTests", - dependencies: ["swift-git-version"] + name: "GitVersionTests", + dependencies: ["GitVersion"] ), ] ) diff --git a/Sources/GitVersion/FileClient.swift b/Sources/GitVersion/FileClient.swift new file mode 100644 index 0000000..95495c3 --- /dev/null +++ b/Sources/GitVersion/FileClient.swift @@ -0,0 +1,157 @@ +import Dependencies +import Foundation +#if canImport(FoundationNetworking) +import FoundationNetworking +#endif +import XCTestDynamicOverlay + +public struct FileClient { + + /// Read the file contents from the given `URL` as `Data`. + /// + public private(set) var read: (URL) throws -> Data + + /// Write `Data` to a file `URL`. + public private(set) var write: (Data, URL) throws -> Void + + /// Create a new ``GitVersion/FileClient`` instance. + /// + /// This is generally not interacted with directly, instead access as a dependency. + /// + ///```swift + /// @Dependency(\.fileClient) var fileClient + ///``` + /// + /// - Parameters: + /// - read: Read the file contents. + /// - write: Write the data to a file. + public init( + read: @escaping (URL) throws -> Data, + write: @escaping (Data, URL) throws -> Void + ) { + self.read = read + self.write = write + } + + /// Read a file at the given path. + /// + /// - Parameters: + /// - path: The path to read the file at. + public func read(path: String) throws -> Data { + let url = try url(for: path) + return try self.read(url) + } + + /// Read the file as a string. + /// + /// - Parameters: + /// - url: The url for the file. + public func readAsString(url: URL) throws -> String { + let data = try read(url) + return String(decoding: data, as: UTF8.self) + } + + /// Read the file as a string + /// + /// - Parameters: + /// - path: The file path to read. + public func readAsString(path: String) throws -> String { + try self.readAsString(url: url(for: path)) + } + + /// Read the contents of a file and decode as the decodable type. + /// + /// - Parameters: + /// - decodable: The type to decode. + /// - url: The file url. + /// - decoder: The decoder to use. + public func read( + _ decodable: D.Type, + from url: URL, + using decoder: JSONDecoder = .init() + ) throws -> D { + let data = try read(url) + return try decoder.decode(D.self, from: data) + } + + /// Read the contents of a file and decode as the decodable type. + /// + /// - Parameters: + /// - decodable: The type to decode. + /// - path: The file path. + /// - decoder: The decoder to use. + public func read( + _ decodable: D.Type, + from path: String, + using decoder: JSONDecoder = .init() + ) throws -> D { + let data = try read(path: path) + return try decoder.decode(D.self, from: data) + } + + /// Write the data to a file at the given path. + /// + /// - Parameters: + /// - data: The data to write to the file. + /// - path: The file path. + public func write(data: Data, to path: String) throws { + let url = try url(for: path) + try self.write(data, url) + } +} + +extension FileClient: DependencyKey { + + /// A ``FileClient`` that does not do anything. + public static let noop = FileClient.init( + read: { _ in Data() }, + write: { _, _ in } + ) + + /// An `unimplemented` ``FileClient``. + public static let testValue = FileClient( + read: unimplemented("\(Self.self).read", placeholder: Data()), + write: unimplemented("\(Self.self).write") + ) + + /// The live ``FileClient`` + public static let liveValue = FileClient( + read: { try Data(contentsOf: $0) }, + write: { try $0.write(to: $1, options: .atomic) } + ) + +} + +extension DependencyValues { + + /// Access a basic ``FileClient`` that can read / write data to the file system. + /// + public var fileClient: FileClient { + get { self[FileClient.self] } + set { self[FileClient.self] = newValue } + } +} + +// MARK: - Overrides +extension FileClient { + + /// Override the data that get's returned when a `read` operation is called. + /// + /// This is useful in a testing context. + /// + /// - Parameters: + /// - data: The data to return when a read operation is called. + public mutating func overrideRead(data: Data) { + self.read = { _ in data } + } +} + +// MARK: - Private +fileprivate func url(for path: String) throws -> URL { + if #available(macOS 13.0, *) { + return URL(filePath: path) + } else { + // Fallback on earlier versions + return URL(fileURLWithPath: path) + } +} diff --git a/Sources/swift-git-version/swift_git_version.swift b/Sources/swift-git-version/swift_git_version.swift index dec585b..01648c5 100644 --- a/Sources/swift-git-version/swift_git_version.swift +++ b/Sources/swift-git-version/swift_git_version.swift @@ -1,6 +1,8 @@ import GitVersion import ShellClient +// An example of using the git version client. + @main public struct swift_git_version { public static func main() { diff --git a/Tests/GitVersionTests/GitVersionTests.swift b/Tests/GitVersionTests/GitVersionTests.swift new file mode 100644 index 0000000..1632bf4 --- /dev/null +++ b/Tests/GitVersionTests/GitVersionTests.swift @@ -0,0 +1,39 @@ +import XCTest +import GitVersion +import ShellClient + +final class GitVersionTests: XCTestCase { + + func test_overrides_work() throws { + try withDependencies { + $0.gitVersionClient.override(with: "blob") + } operation: { + @Dependency(\.gitVersionClient) var versionClient + + let version = try versionClient.currentVersion() + XCTAssertEqual(version, "blob") + } + } + + func test_live() throws { + try withDependencies({ + $0.logger.logLevel = .debug + $0.logger = .liveValue + $0.shellClient = .liveValue + $0.gitVersionClient = .liveValue + }, operation: { + + @Dependency(\.gitVersionClient) var versionClient + + let gitDir = URL(fileURLWithPath: #file) + .deletingLastPathComponent() + .deletingLastPathComponent() + .deletingLastPathComponent() + + let version = try versionClient.currentVersion(in: gitDir.absoluteString) + // can't really have a predictable result for the live client. + XCTAssertNotEqual(version, "blob") + + }) + } +} diff --git a/Tests/swift-git-versionTests/swift_git_versionTests.swift b/Tests/swift-git-versionTests/swift_git_versionTests.swift deleted file mode 100644 index 1f97460..0000000 --- a/Tests/swift-git-versionTests/swift_git_versionTests.swift +++ /dev/null @@ -1,11 +0,0 @@ -import XCTest -@testable import swift_git_version - -final class swift_git_versionTests: XCTestCase { - func testExample() throws { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct - // results. - XCTAssertEqual(swift_git_version().text, "Hello, World!") - } -}