Merge pull request #1 from softinio/push-suonroksrrkt

Add GitHub actions workflows for running tests and for doing releases
This commit is contained in:
Salar Rahmanian 2025-02-09 11:02:29 -08:00 committed by GitHub
commit 1bab9a3dea
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 513 additions and 423 deletions

51
.github/workflows/build-and-release.yml vendored Normal file
View file

@ -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

28
.github/workflows/test.yml vendored Normal file
View file

@ -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

View file

@ -5,18 +5,18 @@ import PackageDescription
let package = Package( let package = Package(
name: "Fishee", name: "Fishee",
products: [
.executable(name: "fishee", targets: ["Fishee"])
],
dependencies: [ dependencies: [
.package(url: "https://github.com/apple/swift-argument-parser.git", .upToNextMajor(from: "1.5.0")), .package(
.package(url: "https://github.com/duckdb/duckdb-swift", .upToNextMinor(from: .init(1, 1, 0))), url: "https://github.com/apple/swift-argument-parser.git", .upToNextMajor(from: "1.5.0"))
], ],
targets: [ 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( .executableTarget(
name: "Fishee", name: "Fishee",
dependencies: [ dependencies: [
.product(name: "ArgumentParser", package: "swift-argument-parser"), .product(name: "ArgumentParser", package: "swift-argument-parser")
.product(name: "DuckDB", package: "duckdb-swift"),
], ],
path: "Sources" path: "Sources"
), ),
@ -27,7 +27,8 @@ let package = Package(
resources: [ resources: [
.copy("Resources/fish_history_test.txt"), .copy("Resources/fish_history_test.txt"),
.copy("Resources/fish_history_test_2.txt"), .copy("Resources/fish_history_test_2.txt"),
] .process("TestPlan.xctestplan"),
) ]
),
] ]
) )

View file

@ -14,10 +14,8 @@
// limitations under the License. // limitations under the License.
// //
import Foundation import Foundation
/// Gets the full file path to a given file. /// Gets the full file path to a given file.
/// ///
/// - Parameters /// - Parameters

View file

@ -14,7 +14,6 @@
// limitations under the License. // limitations under the License.
// //
import ArgumentParser import ArgumentParser
import Foundation import Foundation
@ -22,7 +21,9 @@ let DEFAULT_FISH_HISTORY_LOCATION: String = "~/.local/share/fish/fish_history"
@main @main
struct Fishee: ParsableCommand { struct Fishee: ParsableCommand {
@Option(name: [.short, .customLong("history-file")], help: "Location of your fish history file. Will default to ~/.local/share/fish/fish_history") @Option(
name: [.short, .customLong("history-file")],
help: "Location of your fish history file. Will default to ~/.local/share/fish/fish_history")
var fishHistoryLocationStr: String? var fishHistoryLocationStr: String?
@Option(name: .shortAndLong, help: "File path to file to merge with history file.") @Option(name: .shortAndLong, help: "File path to file to merge with history file.")
@ -65,12 +66,14 @@ struct Fishee: ParsableCommand {
public func run() throws { public func run() throws {
let mergeFileLocation = mergeFile.flatMap { getPath($0) } let mergeFileLocation = mergeFile.flatMap { getPath($0) }
let finalHistory: [FishHistoryEntry] = switch (fishHistoryLocation, mergeFileLocation) { let finalHistory: [FishHistoryEntry] =
switch (fishHistoryLocation, mergeFileLocation) {
case let (fishHistoryLocation?, mergeFileLocation?): case let (fishHistoryLocation?, mergeFileLocation?):
{ {
let currentHistory = parseFishHistory(from: fishHistoryLocation.path) ?? [] let currentHistory = parseFishHistory(from: fishHistoryLocation.path) ?? []
let toMergeHistory = parseFishHistory(from: mergeFileLocation.path) ?? [] let toMergeHistory = parseFishHistory(from: mergeFileLocation.path) ?? []
return mergeFishHistory(currentHistory, toMergeHistory, removeDuplicates: removeDuplicates) return mergeFishHistory(
currentHistory, toMergeHistory, removeDuplicates: removeDuplicates)
}() }()
case let (fishHistoryLocation?, nil): case let (fishHistoryLocation?, nil):
parseFishHistory(from: fishHistoryLocation.path) ?? [] parseFishHistory(from: fishHistoryLocation.path) ?? []
@ -80,8 +83,7 @@ struct Fishee: ParsableCommand {
if dryRun { if dryRun {
finalHistory.forEach { print("\($0.writeEntry().joined(separator: "\n"))") } finalHistory.forEach { print("\($0.writeEntry().joined(separator: "\n"))") }
} } else {
else {
if let writePath = writeFileLocation?.path { if let writePath = writeFileLocation?.path {
let result = writeFishHistory( let result = writeFishHistory(
to: writePath, to: writePath,
@ -90,8 +92,7 @@ struct Fishee: ParsableCommand {
) )
if result { if result {
print("Succussfully updated \(writePath)") print("Succussfully updated \(writePath)")
} } else {
else {
print("Failed to update \(writePath)") print("Failed to update \(writePath)")
} }
} }

View file

@ -14,10 +14,8 @@
// limitations under the License. // limitations under the License.
// //
import Foundation import Foundation
/// Make a backup of the fish history. /// Make a backup of the fish history.
/// ///
/// ``` /// ```
@ -44,7 +42,8 @@ func backupHistory(_ path: String) -> Bool {
let directory = fileURL.deletingLastPathComponent() let directory = fileURL.deletingLastPathComponent()
let newFileName = "\(fileNameWithoutExtension)_copy" let newFileName = "\(fileNameWithoutExtension)_copy"
let newFileURL = directory.appendingPathComponent(newFileName).appendingPathExtension(fileExtension) let newFileURL = directory.appendingPathComponent(newFileName).appendingPathExtension(
fileExtension)
do { do {
try? fileManager.removeItem(at: newFileURL) try? fileManager.removeItem(at: newFileURL)
@ -57,7 +56,6 @@ func backupHistory(_ path: String) -> Bool {
} }
} }
/// Write fish history to file. /// Write fish history to file.
/// ///
/// - Parameters /// - Parameters
@ -88,8 +86,7 @@ func writeFishHistory(to path: String, history: [FishHistoryEntry], backup: Bool
print("Error writing merged history: \(error)") print("Error writing merged history: \(error)")
return false return false
} }
} } else {
else {
print("Nothing to write to \(path)") print("Nothing to write to \(path)")
return false return false
} }
@ -102,14 +99,18 @@ func writeFishHistory(to path: String, history: [FishHistoryEntry], backup: Bool
/// ///
/// - Returns: List of ``FishHistoryEntry`` entries from history file. /// - Returns: List of ``FishHistoryEntry`` entries from history file.
func parseFishHistory(from filePath: String) -> [FishHistoryEntry]? { func parseFishHistory(from filePath: String) -> [FishHistoryEntry]? {
guard let fileContents = try? String(contentsOfFile: filePath) else { guard let fileContents = try? String(contentsOfFile: filePath, encoding: .utf8) else {
print("Failed to open file.") print("Failed to open file.")
return nil return nil
} }
let lines = fileContents.split(separator: "\n").map { String($0).trimmingCharacters(in: .whitespaces) } 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 initialState:
(entries: [FishHistoryEntry], currentCmd: String?, currentWhen: Int?, currentPaths: [String]) =
([], nil, nil, [])
let result = lines.reduce(into: initialState) { state, line in let result = lines.reduce(into: initialState) { state, line in
if line.starts(with: "- cmd:") { if line.starts(with: "- cmd:") {
@ -147,7 +148,9 @@ func parseFishHistory(from filePath: String) -> [FishHistoryEntry]? {
/// - removeDuplicates: if true, remove any duplicates found after merging the two lists. /// - removeDuplicates: if true, remove any duplicates found after merging the two lists.
/// ///
/// - Returns: Single list of ``FishHistoryEntry`` entries. /// - Returns: Single list of ``FishHistoryEntry`` entries.
func mergeFishHistory(_ left: [FishHistoryEntry], _ right: [FishHistoryEntry], removeDuplicates: Bool = false) -> [FishHistoryEntry] { func mergeFishHistory(
_ left: [FishHistoryEntry], _ right: [FishHistoryEntry], removeDuplicates: Bool = false
) -> [FishHistoryEntry] {
let merged = left + right let merged = left + right

View file

@ -16,15 +16,17 @@
import Foundation import Foundation
import Testing import Testing
@testable import Fishee @testable import Fishee
@Suite @Suite
final class AlgebraTests { 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() { @Test func dateFromHistoryTest() {
let gotDate = historyItem.getDate() let gotDate = historyItem.getDate()
#expect(gotDate == Date(timeIntervalSince1970: 1727545693)) #expect(gotDate == Date(timeIntervalSince1970: 1_727_545_693))
} }
@Test func writeEntryTest() { @Test func writeEntryTest() {

View file

@ -16,12 +16,13 @@
import Foundation import Foundation
import Testing import Testing
@testable import Fishee
@testable import Fishee
@Suite(.serialized) @Suite(.serialized)
final class FileHelpersTests { final class FileHelpersTests {
let filePath = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent("myfile.txt") let filePath = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent(
"myfile.txt")
init() { init() {
try? "this is a test".write( try? "this is a test".write(
@ -38,11 +39,12 @@ final class FileHelpersTests {
@Test(arguments: [ @Test(arguments: [
"$HOME/myfile.txt", "$HOME/myfile.txt",
"~/myfile.txt", "~/myfile.txt",
"\(FileManager.default.homeDirectoryForCurrentUser.path)/myfile.txt" "\(FileManager.default.homeDirectoryForCurrentUser.path)/myfile.txt",
]) ])
func getPathTest(testPath: String) { func getPathTest(testPath: String) {
let path = getPath(testPath) let path = getPath(testPath)
let expected = URL(fileURLWithPath: "\(FileManager.default.homeDirectoryForCurrentUser.path)/myfile.txt") let expected = URL(
fileURLWithPath: "\(FileManager.default.homeDirectoryForCurrentUser.path)/myfile.txt")
#expect(path == expected) #expect(path == expected)
} }
} }

View file

@ -14,9 +14,9 @@
// limitations under the License. // limitations under the License.
// //
import Foundation import Foundation
import Testing import Testing
@testable import Fishee @testable import Fishee
@Suite @Suite

View file

@ -16,19 +16,20 @@
import Foundation import Foundation
import Testing import Testing
@testable import Fishee @testable import Fishee
@Suite(.serialized) @Suite(.serialized)
final class ParserTests { final class ParserTests {
let fishHistoryFile = Bundle.module.path(forResource: "fish_history_test", ofType: "txt") let fishHistoryFile = Bundle.module.path(forResource: "fish_history_test", ofType: "txt")
let historyItem = FishHistoryEntry(cmd: "cd Projects/Fishee/", when: 1727545693, paths: ["Projects/Fishee/"]) let historyItem = FishHistoryEntry(
let historyItem2 = FishHistoryEntry(cmd: "swift package tools-version", when: 1727545709, paths: []) cmd: "cd Projects/Fishee/", when: 1_727_545_693, paths: ["Projects/Fishee/"])
let filePathforWriteTest = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask)[0].appendingPathComponent( let historyItem2 = FishHistoryEntry(
"myfile.txt" cmd: "swift package tools-version", when: 1_727_545_709, paths: [])
) let filePathforWriteTest = FileManager.default.temporaryDirectory.appendingPathComponent(
let filePathforFileBackupTest = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask)[0].appendingPathComponent( "myfile.txt")
"myfile_copy.txt" let filePathforFileBackupTest = FileManager.default.temporaryDirectory
) .appendingPathComponent("myfile_copy.txt")
deinit { deinit {
if FileManager.default.fileExists(atPath: filePathforWriteTest.path) { if FileManager.default.fileExists(atPath: filePathforWriteTest.path) {
@ -66,6 +67,8 @@ final class ParserTests {
#expect(fileContent == expectedEntry) #expect(fileContent == expectedEntry)
// confirm backup functionality is working // confirm backup functionality is working
#expect(FileManager.default.fileExists(atPath: filePathforWriteTest.path))
let write_again = writeFishHistory( let write_again = writeFishHistory(
to: filePathforWriteTest.path, to: filePathforWriteTest.path,
history: [historyItem], history: [historyItem],
@ -90,7 +93,8 @@ final class ParserTests {
} }
@Test func mergeFishHistoryRemoveDuplicateTest() { @Test func mergeFishHistoryRemoveDuplicateTest() {
let merged = mergeFishHistory([historyItem], [historyItem, historyItem2], removeDuplicates: true) let merged = mergeFishHistory(
[historyItem], [historyItem, historyItem2], removeDuplicates: true)
#expect(merged.count == 2) #expect(merged.count == 2)
#expect(merged.contains(historyItem)) #expect(merged.contains(historyItem))
#expect(merged.contains(historyItem2)) #expect(merged.contains(historyItem2))