diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml new file mode 100644 index 0000000..1a35012 --- /dev/null +++ b/.github/workflows/build-and-release.yml @@ -0,0 +1,51 @@ +name: Build and Release Artifacts + +on: + release: + types: [published] + +jobs: + build-macos: + runs-on: macos-15 + strategy: + matrix: + arch: [x86_64, arm64] + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Get swift version + run: swift --version + + - name: Build for macOS + run: | + swift build -c release --arch ${{ matrix.arch }} + mkdir -p artifacts/macos-${{ matrix.arch }} + cp .build/apple/Products/Release/fishee artifacts/macos-${{ matrix.arch }}/ + + - name: Upload macOS artifacts + uses: actions/upload-artifact@v4 + with: + name: fishee-macos-${{ matrix.arch }}-${{ github.ref_name }} + path: artifacts/macos-${{ matrix.arch }} + + build-linux: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Get swift version + run: swift --version + + - name: Build for Linux + run: | + swift build -c release + mkdir -p artifacts/linux + cp .build/release/fishee artifacts/linux/ + + - name: Upload Linux artifacts + uses: actions/upload-artifact@v4 + with: + name: fishee-linux-${{ github.ref_name }} + path: artifacts/linux diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..770da57 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,28 @@ +name: Run Tests + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + test: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [macos-15, ubuntu-latest] + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Get swift version + run: swift --version + + - name: Build + run: swift build -v + + - name: Run tests + run: swift test -v --parallel diff --git a/Package.swift b/Package.swift index dc2a5fe..8149129 100644 --- a/Package.swift +++ b/Package.swift @@ -4,30 +4,31 @@ import PackageDescription let package = Package( - name: "Fishee", - dependencies: [ - .package(url: "https://github.com/apple/swift-argument-parser.git", .upToNextMajor(from: "1.5.0")), - .package(url: "https://github.com/duckdb/duckdb-swift", .upToNextMinor(from: .init(1, 1, 0))), - ], - 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: "Fishee", - dependencies: [ - .product(name: "ArgumentParser", package: "swift-argument-parser"), - .product(name: "DuckDB", package: "duckdb-swift"), - ], - path: "Sources" - ), - .testTarget( - name: "FisheeTests", - dependencies: ["Fishee"], - path: "Tests", - resources: [ - .copy("Resources/fish_history_test.txt"), - .copy("Resources/fish_history_test_2.txt"), - ] - ) - ] + name: "Fishee", + products: [ + .executable(name: "fishee", targets: ["Fishee"]) + ], + dependencies: [ + .package( + url: "https://github.com/apple/swift-argument-parser.git", .upToNextMajor(from: "1.5.0")) + ], + targets: [ + .executableTarget( + name: "Fishee", + dependencies: [ + .product(name: "ArgumentParser", package: "swift-argument-parser") + ], + path: "Sources" + ), + .testTarget( + name: "FisheeTests", + dependencies: ["Fishee"], + path: "Tests", + resources: [ + .copy("Resources/fish_history_test.txt"), + .copy("Resources/fish_history_test_2.txt"), + .process("TestPlan.xctestplan"), + ] + ), + ] ) diff --git a/Sources/Algebra.swift b/Sources/Algebra.swift index 8eb89c8..e669faf 100644 --- a/Sources/Algebra.swift +++ b/Sources/Algebra.swift @@ -1,12 +1,12 @@ // // Copyright © 2024 Salar Rahmanian. -// +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -15,30 +15,30 @@ // import Foundation - + /// Data Structure/Schema representing an entry in a fish history file. struct FishHistoryEntry: Equatable { - let cmd: String - let when: Int - let paths: [String] - - /// Converts time to Date object - func getDate() -> Date { - Date(timeIntervalSince1970: TimeInterval(when)) - } - - /// Converts structure back to list of strings as expected in a fish history file. - func writeEntry() -> [String] { - var output: [String] = [] - - output.append("- cmd: \(cmd)") - output.append(" when: \(when)") - - if !paths.isEmpty { - output.append(" paths:") - paths.forEach { output.append(" - \(String($0))") } - } - - return output + let cmd: String + let when: Int + let paths: [String] + + /// Converts time to Date object + func getDate() -> Date { + Date(timeIntervalSince1970: TimeInterval(when)) + } + + /// Converts structure back to list of strings as expected in a fish history file. + func writeEntry() -> [String] { + var output: [String] = [] + + output.append("- cmd: \(cmd)") + output.append(" when: \(when)") + + if !paths.isEmpty { + output.append(" paths:") + paths.forEach { output.append(" - \(String($0))") } } + + return output + } } diff --git a/Sources/FileHelpers.swift b/Sources/FileHelpers.swift index b00adfc..7967e6a 100644 --- a/Sources/FileHelpers.swift +++ b/Sources/FileHelpers.swift @@ -1,23 +1,21 @@ // // Copyright © 2024 Salar Rahmanian. -// +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // - import Foundation - /// Gets the full file path to a given file. /// /// - Parameters @@ -25,20 +23,20 @@ import Foundation /// /// - Returns: A Valid file URL or None if invalid. func getPath(_ pathStr: String) -> URL? { - let userHomeDirectory = FileManager.default.homeDirectoryForCurrentUser.path - var filePath: String = pathStr - - if pathStr.hasPrefix("~") { - filePath = (pathStr as NSString).expandingTildeInPath - } - - if pathStr.hasPrefix("$HOME") { - filePath = filePath.replacingOccurrences(of: "$HOME", with: userHomeDirectory) - } - - if !FileManager.default.fileExists(atPath: filePath) { - return nil - } - - return URL(fileURLWithPath: filePath) + let userHomeDirectory = FileManager.default.homeDirectoryForCurrentUser.path + var filePath: String = pathStr + + if pathStr.hasPrefix("~") { + filePath = (pathStr as NSString).expandingTildeInPath + } + + if pathStr.hasPrefix("$HOME") { + filePath = filePath.replacingOccurrences(of: "$HOME", with: userHomeDirectory) + } + + if !FileManager.default.fileExists(atPath: filePath) { + return nil + } + + return URL(fileURLWithPath: filePath) } diff --git a/Sources/Fishee.swift b/Sources/Fishee.swift index d120d80..93a9f38 100644 --- a/Sources/Fishee.swift +++ b/Sources/Fishee.swift @@ -14,7 +14,6 @@ // limitations under the License. // - import ArgumentParser import Foundation @@ -22,81 +21,83 @@ let DEFAULT_FISH_HISTORY_LOCATION: String = "~/.local/share/fish/fish_history" @main struct Fishee: ParsableCommand { - @Option(name: [.short, .customLong("history-file")], help: "Location of your fish history file. Will default to ~/.local/share/fish/fish_history") - var fishHistoryLocationStr: String? - - @Option(name: .shortAndLong, help: "File path to file to merge with history file.") - var mergeFile: String? - - @Option( - name: [.short, .customLong("output-file")], - help: "File to write to. Default: same as current history file." - ) - var writeFileStr: String? - - @Flag( - name: .shortAndLong, - help: "Dry run. Will only print to the console without actually modifying the history file." - ) - var dryRun: Bool = false - - @Flag( - name: .shortAndLong, - help: "Remove duplicates from combined history. Default: false" - ) - var removeDuplicates: Bool = false - - @Flag( - name: .shortAndLong, - inversion: .prefixedNo, - help: "Backup fish history file given before writing." - ) - var backup: Bool = true - - var fishHistoryLocation: URL? { - let pathStr = fishHistoryLocationStr ?? DEFAULT_FISH_HISTORY_LOCATION - return getPath(pathStr) - } - - var writeFileLocation: URL? { - let pathStr = writeFileStr ?? DEFAULT_FISH_HISTORY_LOCATION - return getPath(pathStr) - } - - public func run() throws { - let mergeFileLocation = mergeFile.flatMap { getPath($0) } - let finalHistory: [FishHistoryEntry] = switch (fishHistoryLocation, mergeFileLocation) { - case let (fishHistoryLocation?, mergeFileLocation?): - { - let currentHistory = parseFishHistory(from: fishHistoryLocation.path) ?? [] - let toMergeHistory = parseFishHistory(from: mergeFileLocation.path) ?? [] - return mergeFishHistory(currentHistory, toMergeHistory, removeDuplicates: removeDuplicates) - }() - case let (fishHistoryLocation?, nil): - parseFishHistory(from: fishHistoryLocation.path) ?? [] - default: - [] + @Option( + name: [.short, .customLong("history-file")], + help: "Location of your fish history file. Will default to ~/.local/share/fish/fish_history") + var fishHistoryLocationStr: String? + + @Option(name: .shortAndLong, help: "File path to file to merge with history file.") + var mergeFile: String? + + @Option( + name: [.short, .customLong("output-file")], + help: "File to write to. Default: same as current history file." + ) + var writeFileStr: String? + + @Flag( + name: .shortAndLong, + help: "Dry run. Will only print to the console without actually modifying the history file." + ) + var dryRun: Bool = false + + @Flag( + name: .shortAndLong, + help: "Remove duplicates from combined history. Default: false" + ) + var removeDuplicates: Bool = false + + @Flag( + name: .shortAndLong, + inversion: .prefixedNo, + help: "Backup fish history file given before writing." + ) + var backup: Bool = true + + var fishHistoryLocation: URL? { + let pathStr = fishHistoryLocationStr ?? DEFAULT_FISH_HISTORY_LOCATION + return getPath(pathStr) + } + + var writeFileLocation: URL? { + let pathStr = writeFileStr ?? DEFAULT_FISH_HISTORY_LOCATION + return getPath(pathStr) + } + + public func run() throws { + let mergeFileLocation = mergeFile.flatMap { getPath($0) } + let finalHistory: [FishHistoryEntry] = + switch (fishHistoryLocation, mergeFileLocation) { + case let (fishHistoryLocation?, mergeFileLocation?): + { + let currentHistory = parseFishHistory(from: fishHistoryLocation.path) ?? [] + let toMergeHistory = parseFishHistory(from: mergeFileLocation.path) ?? [] + return mergeFishHistory( + currentHistory, toMergeHistory, removeDuplicates: removeDuplicates) + }() + case let (fishHistoryLocation?, nil): + parseFishHistory(from: fishHistoryLocation.path) ?? [] + default: + [] + } + + if dryRun { + finalHistory.forEach { print("\($0.writeEntry().joined(separator: "\n"))") } + } else { + if let writePath = writeFileLocation?.path { + let result = writeFishHistory( + to: writePath, + history: finalHistory, + backup: backup + ) + if result { + print("Succussfully updated \(writePath)") + } else { + print("Failed to update \(writePath)") } - - if dryRun { - finalHistory.forEach { print("\($0.writeEntry().joined(separator: "\n"))") } - } - else { - if let writePath = writeFileLocation?.path { - let result = writeFishHistory( - to: writePath, - history: finalHistory, - backup: backup - ) - if result { - print("Succussfully updated \(writePath)") - } - else { - print("Failed to update \(writePath)") - } - } - } - - return + } } + + return + } } diff --git a/Sources/Parser.swift b/Sources/Parser.swift index 2c04349..2f7cfe1 100644 --- a/Sources/Parser.swift +++ b/Sources/Parser.swift @@ -1,23 +1,21 @@ // // Copyright © 2024 Salar Rahmanian. -// +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // - import Foundation - /// Make a backup of the fish history. /// /// ``` @@ -31,32 +29,32 @@ import Foundation /// /// - Returns: true if backup copy successful, false if not. func backupHistory(_ path: String) -> Bool { - let fileManager = FileManager.default - - guard fileManager.fileExists(atPath: path) else { - print("File does not exist at path: \(path)") - return false - } - - let fileURL = URL(fileURLWithPath: path) - let fileExtension = fileURL.pathExtension - let fileNameWithoutExtension = fileURL.deletingPathExtension().lastPathComponent - let directory = fileURL.deletingLastPathComponent() - - let newFileName = "\(fileNameWithoutExtension)_copy" - let newFileURL = directory.appendingPathComponent(newFileName).appendingPathExtension(fileExtension) - - do { - try? fileManager.removeItem(at: newFileURL) - try fileManager.copyItem(at: fileURL, to: newFileURL) - print("File duplicated successfully to: \(newFileURL.path)") - return true - } catch { - print("error making a backup of \(path), got error: \(error)") - return false - } -} + let fileManager = FileManager.default + guard fileManager.fileExists(atPath: path) else { + print("File does not exist at path: \(path)") + return false + } + + let fileURL = URL(fileURLWithPath: path) + let fileExtension = fileURL.pathExtension + let fileNameWithoutExtension = fileURL.deletingPathExtension().lastPathComponent + let directory = fileURL.deletingLastPathComponent() + + let newFileName = "\(fileNameWithoutExtension)_copy" + let newFileURL = directory.appendingPathComponent(newFileName).appendingPathExtension( + fileExtension) + + do { + try? fileManager.removeItem(at: newFileURL) + try fileManager.copyItem(at: fileURL, to: newFileURL) + print("File duplicated successfully to: \(newFileURL.path)") + return true + } catch { + print("error making a backup of \(path), got error: \(error)") + return false + } +} /// Write fish history to file. /// @@ -67,32 +65,31 @@ func backupHistory(_ path: String) -> Bool { /// /// - Returns: true if writing to file copy successful, false if not. func writeFishHistory(to path: String, history: [FishHistoryEntry], backup: Bool = true) -> Bool { - var output = "" - - if backup { - let result = backupHistory(path) - if !result { - print("Failed to backup \(path) so aborting!") - return false - } + var output = "" + + if backup { + let result = backupHistory(path) + if !result { + print("Failed to backup \(path) so aborting!") + return false } - - history.forEach { output += $0.writeEntry().joined(separator: "\n") + "\n" } - - if !output.isEmpty { - do { - try output.write(toFile: path, atomically: true, encoding: .utf8) - print("Successfully wrote merged history to \(path)") - return true - } catch { - print("Error writing merged history: \(error)") - return false - } - } - else { - print("Nothing to write to \(path)") - return false + } + + history.forEach { output += $0.writeEntry().joined(separator: "\n") + "\n" } + + if !output.isEmpty { + do { + try output.write(toFile: path, atomically: true, encoding: .utf8) + print("Successfully wrote merged history to \(path)") + return true + } catch { + print("Error writing merged history: \(error)") + return false } + } else { + print("Nothing to write to \(path)") + return false + } } /// Parse the fish history file. @@ -102,41 +99,45 @@ func writeFishHistory(to path: String, history: [FishHistoryEntry], backup: Bool /// /// - Returns: List of ``FishHistoryEntry`` entries from history file. func parseFishHistory(from filePath: String) -> [FishHistoryEntry]? { - guard let fileContents = try? String(contentsOfFile: filePath) else { - print("Failed to open file.") - return nil - } - - let lines = fileContents.split(separator: "\n").map { String($0).trimmingCharacters(in: .whitespaces) } + guard let fileContents = try? String(contentsOfFile: filePath, encoding: .utf8) else { + print("Failed to open file.") + return nil + } - let initialState: (entries: [FishHistoryEntry], currentCmd: String?, currentWhen: Int?, currentPaths: [String]) = ([], nil, nil, []) - - let result = lines.reduce(into: initialState) { state, line in - if line.starts(with: "- cmd:") { - if let cmd = state.currentCmd, let when = state.currentWhen { - let entry = FishHistoryEntry(cmd: cmd, when: when, paths: state.currentPaths) - state.entries.append(entry) - state.currentPaths = [] - } - state.currentCmd = String(line.dropFirst("- cmd:".count).trimmingCharacters(in: .whitespaces)) - } else if line.starts(with: "when:") { - if let whenValue = Int(line.dropFirst("when:".count).trimmingCharacters(in: .whitespaces)) { - state.currentWhen = whenValue - } - } else if line.starts(with: "paths:") { - return - } else if line.starts(with: "- ") { - let path = String(line.dropFirst("- ".count).trimmingCharacters(in: .whitespaces)) - state.currentPaths.append(path) - } + let lines = fileContents.split(separator: "\n").map { + String($0).trimmingCharacters(in: .whitespaces) + } + + let initialState: + (entries: [FishHistoryEntry], currentCmd: String?, currentWhen: Int?, currentPaths: [String]) = + ([], nil, nil, []) + + let result = lines.reduce(into: initialState) { state, line in + if line.starts(with: "- cmd:") { + if let cmd = state.currentCmd, let when = state.currentWhen { + let entry = FishHistoryEntry(cmd: cmd, when: when, paths: state.currentPaths) + state.entries.append(entry) + state.currentPaths = [] + } + state.currentCmd = String(line.dropFirst("- cmd:".count).trimmingCharacters(in: .whitespaces)) + } else if line.starts(with: "when:") { + if let whenValue = Int(line.dropFirst("when:".count).trimmingCharacters(in: .whitespaces)) { + state.currentWhen = whenValue + } + } else if line.starts(with: "paths:") { + return + } else if line.starts(with: "- ") { + let path = String(line.dropFirst("- ".count).trimmingCharacters(in: .whitespaces)) + state.currentPaths.append(path) } - - if let cmd = result.currentCmd, let when = result.currentWhen { - let entry = FishHistoryEntry(cmd: cmd, when: when, paths: result.currentPaths) - return result.entries + [entry] - } - - return result.entries + } + + if let cmd = result.currentCmd, let when = result.currentWhen { + let entry = FishHistoryEntry(cmd: cmd, when: when, paths: result.currentPaths) + return result.entries + [entry] + } + + return result.entries } /// Merge two given ``FishHistoryEntry`` lists into one list. @@ -147,18 +148,20 @@ func parseFishHistory(from filePath: String) -> [FishHistoryEntry]? { /// - removeDuplicates: if true, remove any duplicates found after merging the two lists. /// /// - Returns: Single list of ``FishHistoryEntry`` entries. -func mergeFishHistory(_ left: [FishHistoryEntry], _ right: [FishHistoryEntry], removeDuplicates: Bool = false) -> [FishHistoryEntry] { - - let merged = left + right - - if removeDuplicates { - let finalList = merged.reduce(into: [String:FishHistoryEntry]()) { result, entry in - if result[entry.cmd] == nil { - result[entry.cmd] = entry - } - } - return Array(finalList.values) - } else { - return merged +func mergeFishHistory( + _ left: [FishHistoryEntry], _ right: [FishHistoryEntry], removeDuplicates: Bool = false +) -> [FishHistoryEntry] { + + let merged = left + right + + if removeDuplicates { + let finalList = merged.reduce(into: [String: FishHistoryEntry]()) { result, entry in + if result[entry.cmd] == nil { + result[entry.cmd] = entry + } } + return Array(finalList.values) + } else { + return merged + } } diff --git a/Tests/AlgebraTest.swift b/Tests/AlgebraTest.swift index 1b9a0ac..b689671 100644 --- a/Tests/AlgebraTest.swift +++ b/Tests/AlgebraTest.swift @@ -1,42 +1,44 @@ // // Copyright © 2024 Salar Rahmanian. -// +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // - + import Foundation import Testing + @testable import Fishee @Suite final class AlgebraTests { - let historyItem = FishHistoryEntry(cmd: "cd Projects/Fishee/", when: 1727545693, paths: ["Projects/Fishee/"]) + let historyItem = FishHistoryEntry( + cmd: "cd Projects/Fishee/", when: 1_727_545_693, paths: ["Projects/Fishee/"]) + + @Test func dateFromHistoryTest() { + let gotDate = historyItem.getDate() + #expect(gotDate == Date(timeIntervalSince1970: 1_727_545_693)) + } + + @Test func writeEntryTest() { + let entry = historyItem.writeEntry() + #expect(entry.count > 0) + let expectedEntry = """ + - cmd: cd Projects/Fishee/ + when: 1727545693 + paths: + - Projects/Fishee/ + """ + #expect(entry.joined(separator: "\n") == expectedEntry) + } - @Test func dateFromHistoryTest() { - let gotDate = historyItem.getDate() - #expect(gotDate == Date(timeIntervalSince1970: 1727545693)) - } - - @Test func writeEntryTest() { - let entry = historyItem.writeEntry() - #expect(entry.count > 0) - let expectedEntry = """ - - cmd: cd Projects/Fishee/ - when: 1727545693 - paths: - - Projects/Fishee/ - """ - #expect(entry.joined(separator: "\n") == expectedEntry) - } - } diff --git a/Tests/FileHelpersTests.swift b/Tests/FileHelpersTests.swift index 8abd3df..a49f0a7 100644 --- a/Tests/FileHelpersTests.swift +++ b/Tests/FileHelpersTests.swift @@ -1,48 +1,50 @@ // // Copyright © 2024 Salar Rahmanian. -// +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // - + import Foundation import Testing -@testable import Fishee +@testable import Fishee @Suite(.serialized) final class FileHelpersTests { - let filePath = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent("myfile.txt") - - init() { - try? "this is a test".write( - to: filePath, - atomically: true, - encoding: .utf8 - ) - } - - deinit { - try? FileManager.default.removeItem(at: filePath) - } - - @Test(arguments: [ - "$HOME/myfile.txt", - "~/myfile.txt", - "\(FileManager.default.homeDirectoryForCurrentUser.path)/myfile.txt" - ]) - func getPathTest(testPath: String) { - let path = getPath(testPath) - let expected = URL(fileURLWithPath: "\(FileManager.default.homeDirectoryForCurrentUser.path)/myfile.txt") - #expect(path == expected) - } + let filePath = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent( + "myfile.txt") + + init() { + try? "this is a test".write( + to: filePath, + atomically: true, + encoding: .utf8 + ) + } + + deinit { + try? FileManager.default.removeItem(at: filePath) + } + + @Test(arguments: [ + "$HOME/myfile.txt", + "~/myfile.txt", + "\(FileManager.default.homeDirectoryForCurrentUser.path)/myfile.txt", + ]) + func getPathTest(testPath: String) { + let path = getPath(testPath) + let expected = URL( + fileURLWithPath: "\(FileManager.default.homeDirectoryForCurrentUser.path)/myfile.txt") + #expect(path == expected) + } } diff --git a/Tests/FisheeTests.swift b/Tests/FisheeTests.swift index b01fae5..b4db340 100644 --- a/Tests/FisheeTests.swift +++ b/Tests/FisheeTests.swift @@ -1,77 +1,77 @@ // // Copyright © 2024 Salar Rahmanian. -// +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // - import Foundation import Testing + @testable import Fishee @Suite final class FisheeTests { - @Test func DryRunTest() { - do { - let help = try #require(Fishee.parse(["--dry-run"]) as Fishee) - #expect(help.dryRun) - } catch { - Issue.record("Test failed! \(error)") - } - } - - @Test func HistoryFileTest() { - do { - let help = try #require(Fishee.parse(["--history-file", "/tmp/fishtest.txt"]) as Fishee) - #expect(help.fishHistoryLocationStr == "/tmp/fishtest.txt") - } catch { - Issue.record("Test failed! \(error)") - } + @Test func DryRunTest() { + do { + let help = try #require(Fishee.parse(["--dry-run"]) as Fishee) + #expect(help.dryRun) + } catch { + Issue.record("Test failed! \(error)") } + } - @Test func OutputFileTest() { - do { - let help = try #require(Fishee.parse(["--output-file", "/tmp/fishtest.txt"]) as Fishee) - #expect(help.writeFileStr == "/tmp/fishtest.txt") - } catch { - Issue.record("Test failed! \(error)") - } + @Test func HistoryFileTest() { + do { + let help = try #require(Fishee.parse(["--history-file", "/tmp/fishtest.txt"]) as Fishee) + #expect(help.fishHistoryLocationStr == "/tmp/fishtest.txt") + } catch { + Issue.record("Test failed! \(error)") } + } - @Test func RemoveDuplicatesTest() { - do { - let help = try #require(Fishee.parse(["--remove-duplicates"]) as Fishee) - #expect(help.removeDuplicates) - } catch { - Issue.record("Test failed! \(error)") - } + @Test func OutputFileTest() { + do { + let help = try #require(Fishee.parse(["--output-file", "/tmp/fishtest.txt"]) as Fishee) + #expect(help.writeFileStr == "/tmp/fishtest.txt") + } catch { + Issue.record("Test failed! \(error)") } + } - @Test func BackupTest() { - do { - let help = try #require(Fishee.parse(["--backup"]) as Fishee) - #expect(help.backup) - } catch { - Issue.record("Test failed! \(error)") - } + @Test func RemoveDuplicatesTest() { + do { + let help = try #require(Fishee.parse(["--remove-duplicates"]) as Fishee) + #expect(help.removeDuplicates) + } catch { + Issue.record("Test failed! \(error)") } + } - @Test func NoBackupTest() { - do { - let help = try #require(Fishee.parse(["--no-backup"]) as Fishee) - #expect(!help.backup) - } catch { - Issue.record("Test failed! \(error)") - } + @Test func BackupTest() { + do { + let help = try #require(Fishee.parse(["--backup"]) as Fishee) + #expect(help.backup) + } catch { + Issue.record("Test failed! \(error)") } + } + + @Test func NoBackupTest() { + do { + let help = try #require(Fishee.parse(["--no-backup"]) as Fishee) + #expect(!help.backup) + } catch { + Issue.record("Test failed! \(error)") + } + } } diff --git a/Tests/ParserTests.swift b/Tests/ParserTests.swift index a6d54e9..dc99626 100644 --- a/Tests/ParserTests.swift +++ b/Tests/ParserTests.swift @@ -1,98 +1,102 @@ // // Copyright © 2024 Salar Rahmanian. -// +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // - + import Foundation import Testing + @testable import Fishee @Suite(.serialized) final class ParserTests { - let fishHistoryFile = Bundle.module.path(forResource: "fish_history_test", ofType: "txt") - let historyItem = FishHistoryEntry(cmd: "cd Projects/Fishee/", when: 1727545693, paths: ["Projects/Fishee/"]) - let historyItem2 = FishHistoryEntry(cmd: "swift package tools-version", when: 1727545709, paths: []) - let filePathforWriteTest = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask)[0].appendingPathComponent( - "myfile.txt" - ) - let filePathforFileBackupTest = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask)[0].appendingPathComponent( - "myfile_copy.txt" - ) - - deinit { - if FileManager.default.fileExists(atPath: filePathforWriteTest.path) { - _ = try? FileManager.default.removeItem(at: filePathforWriteTest) - } - if FileManager.default.fileExists(atPath: filePathforFileBackupTest.path) { - _ = try? FileManager.default.removeItem(at: filePathforFileBackupTest) - } - } + let fishHistoryFile = Bundle.module.path(forResource: "fish_history_test", ofType: "txt") + let historyItem = FishHistoryEntry( + cmd: "cd Projects/Fishee/", when: 1_727_545_693, paths: ["Projects/Fishee/"]) + let historyItem2 = FishHistoryEntry( + cmd: "swift package tools-version", when: 1_727_545_709, paths: []) + let filePathforWriteTest = FileManager.default.temporaryDirectory.appendingPathComponent( + "myfile.txt") + let filePathforFileBackupTest = FileManager.default.temporaryDirectory + .appendingPathComponent("myfile_copy.txt") - @Test func parseFishHistoryTest() { - #expect(fishHistoryFile != nil) - let fishHistory = parseFishHistory(from: fishHistoryFile!) - #expect(fishHistory!.count > 0) - let expectedHistory = [historyItem, historyItem2] - #expect(fishHistory == expectedHistory) + deinit { + if FileManager.default.fileExists(atPath: filePathforWriteTest.path) { + _ = try? FileManager.default.removeItem(at: filePathforWriteTest) } - - @Test func writeFishHistoryTest() { - let written = writeFishHistory( - to: filePathforWriteTest.path, - history: [historyItem], - backup: false - ) - #expect(written) - - let fileContent = try? String(contentsOf: filePathforWriteTest, encoding: .utf8) - let expectedEntry = """ - - cmd: cd Projects/Fishee/ - when: 1727545693 - paths: - - Projects/Fishee/ - - """ - #expect(fileContent == expectedEntry) - - // confirm backup functionality is working - let write_again = writeFishHistory( - to: filePathforWriteTest.path, - history: [historyItem], - backup: true - ) - #expect(write_again) - #expect(FileManager.default.fileExists(atPath: filePathforFileBackupTest.path)) - } - - @Test func mergeFishHistoryTest() { - let merged = mergeFishHistory([historyItem], [historyItem2]) - #expect(merged.count == 2) - #expect(merged.contains(historyItem)) - #expect(merged.contains(historyItem2)) - } - - @Test func mergeFishHistoryWithDuplicateTest() { - let merged = mergeFishHistory([historyItem], [historyItem, historyItem2]) - #expect(merged.count == 3) - #expect(merged.contains(historyItem)) - #expect(merged.contains(historyItem2)) + if FileManager.default.fileExists(atPath: filePathforFileBackupTest.path) { + _ = try? FileManager.default.removeItem(at: filePathforFileBackupTest) } + } - @Test func mergeFishHistoryRemoveDuplicateTest() { - let merged = mergeFishHistory([historyItem], [historyItem, historyItem2], removeDuplicates: true) - #expect(merged.count == 2) - #expect(merged.contains(historyItem)) - #expect(merged.contains(historyItem2)) - } + @Test func parseFishHistoryTest() { + #expect(fishHistoryFile != nil) + let fishHistory = parseFishHistory(from: fishHistoryFile!) + #expect(fishHistory!.count > 0) + let expectedHistory = [historyItem, historyItem2] + #expect(fishHistory == expectedHistory) + } + + @Test func writeFishHistoryTest() { + let written = writeFishHistory( + to: filePathforWriteTest.path, + history: [historyItem], + backup: false + ) + #expect(written) + + let fileContent = try? String(contentsOf: filePathforWriteTest, encoding: .utf8) + let expectedEntry = """ + - cmd: cd Projects/Fishee/ + when: 1727545693 + paths: + - Projects/Fishee/ + + """ + #expect(fileContent == expectedEntry) + + // confirm backup functionality is working + #expect(FileManager.default.fileExists(atPath: filePathforWriteTest.path)) + + let write_again = writeFishHistory( + to: filePathforWriteTest.path, + history: [historyItem], + backup: true + ) + #expect(write_again) + #expect(FileManager.default.fileExists(atPath: filePathforFileBackupTest.path)) + } + + @Test func mergeFishHistoryTest() { + let merged = mergeFishHistory([historyItem], [historyItem2]) + #expect(merged.count == 2) + #expect(merged.contains(historyItem)) + #expect(merged.contains(historyItem2)) + } + + @Test func mergeFishHistoryWithDuplicateTest() { + let merged = mergeFishHistory([historyItem], [historyItem, historyItem2]) + #expect(merged.count == 3) + #expect(merged.contains(historyItem)) + #expect(merged.contains(historyItem2)) + } + + @Test func mergeFishHistoryRemoveDuplicateTest() { + let merged = mergeFishHistory( + [historyItem], [historyItem, historyItem2], removeDuplicates: true) + #expect(merged.count == 2) + #expect(merged.contains(historyItem)) + #expect(merged.contains(historyItem2)) + } }