mirror of
https://github.com/softinio/Fishee.git
synced 2025-02-22 21:46:05 -08:00
Merge pull request #1 from softinio/push-suonroksrrkt
Add GitHub actions workflows for running tests and for doing releases
This commit is contained in:
commit
1bab9a3dea
11 changed files with 513 additions and 423 deletions
51
.github/workflows/build-and-release.yml
vendored
Normal file
51
.github/workflows/build-and-release.yml
vendored
Normal 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
28
.github/workflows/test.yml
vendored
Normal 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
|
|
@ -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"),
|
||||||
)
|
]
|
||||||
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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))
|
||||||
|
|
Loading…
Reference in a new issue