Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
name: "CI"

on:
on:
push:
branches:
branches:
- main
pull_request:
branches:
branches:
- '*'

jobs:
test:
name: Unit Tests
runs-on: macOS-13
env:
DEVELOPER_DIR: /Applications/Xcode_15.0.app/Contents/Developer
runs-on: macOS-26
env:
DEVELOPER_DIR: /Applications/Xcode_26.2.app/Contents/Developer
steps:
- uses: actions/checkout@v2
- name: Run Tests
run: swift test

24 changes: 24 additions & 0 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 13 additions & 8 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// swift-tools-version: 5.7
// The swift-tools-version declares the minimum version of Swift required to build this package.
// swift-tools-version: 6.0

import PackageDescription

Expand All @@ -14,20 +13,26 @@ let package = Package(
products: [
.library(
name: "Papyrus",
targets: ["Papyrus"]),
targets: ["Papyrus"]
)
],
dependencies: [],
dependencies: [.package(url: "https://github.com/apple/swift-async-algorithms", from: "1.1.1")],
targets: [
.target(name: "Papyrus"),
.target(
name: "Papyrus",
dependencies: [
.product(name: "AsyncAlgorithms", package: "swift-async-algorithms")
]
),
.testTarget(
name: "Unit",
dependencies: ["Papyrus"],
exclude: ["Performance/Supporting Files/Unit.xctestplan"]
exclude: ["Supporting Files/Unit.xctestplan"]
),
.testTarget(
name: "Performance",
dependencies: ["Papyrus"],
exclude: ["Performance/Supporting Files/Performance.xctestplan"]
),
exclude: ["Supporting Files/Performance.xctestplan"]
)
]
)
10 changes: 0 additions & 10 deletions Sources/Papyrus/Extensions/AsyncSequence.swift

This file was deleted.

30 changes: 23 additions & 7 deletions Sources/Papyrus/Extensions/AsyncStream.swift
Original file line number Diff line number Diff line change
@@ -1,12 +1,28 @@
// Thanks - https://github.com/pointfreeco/swift-dependencies
extension AsyncStream {
init<S: AsyncSequence>(_ sequence: S) where S.Element == Element {
var iterator: S.AsyncIterator?
self.init {
if iterator == nil {
iterator = sequence.makeAsyncIterator()
extension AsyncStream where Element: Sendable {
init<S: AsyncSequence & Sendable>(
_ sequence: S
) where S.Element == Element, S.Element: Sendable {
self.init { continuation in
let task = Task {
do {
for try await element in sequence {
continuation.yield(element)
}
continuation.finish()
} catch {
continuation.finish()
}
}
continuation.onTermination = { _ in
task.cancel()
}
return try? await iterator?.next()
}
}
}

extension AsyncSequence where Self: Sendable, Element: Sendable {
func eraseToStream() -> AsyncStream<Element> {
AsyncStream(self)
}
}
30 changes: 23 additions & 7 deletions Sources/Papyrus/Extensions/AsyncThrowingStream.swift
Original file line number Diff line number Diff line change
@@ -1,12 +1,28 @@
// Thanks - https://github.com/pointfreeco/swift-dependencies
extension AsyncThrowingStream where Failure == Error {
init<S: AsyncSequence>(_ sequence: S) where S.Element == Element {
var iterator: S.AsyncIterator?
self.init {
if iterator == nil {
iterator = sequence.makeAsyncIterator()
extension AsyncThrowingStream where Element: Sendable, Failure == Error {
init<S: AsyncSequence & Sendable>(
_ sequence: S
) where S.Element == Element, S.Element: Sendable {
self.init { continuation in
let task = Task {
do {
for try await element in sequence {
continuation.yield(element)
}
continuation.finish()
} catch {
continuation.finish(throwing: error)
}
}
continuation.onTermination = { _ in
task.cancel()
}
return try await iterator?.next()
}
}
}

extension AsyncSequence where Self: Sendable, Element: Sendable {
func eraseToThrowingStream() -> AsyncThrowingStream<Element, Error> {
AsyncThrowingStream(self)
}
}
8 changes: 4 additions & 4 deletions Sources/Papyrus/Fail.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ struct Fail<Element, Failure>: AsyncSequence, Sendable where Failure: Error {
/// Creates an async iterator that emits elements of this async sequence.
/// - Returns: An instance that conforms to `AsyncIteratorProtocol`.
func makeAsyncIterator() -> Self {
.init(error: self.error)
.init(error: error)
}
}

Expand All @@ -44,9 +44,9 @@ extension Fail: AsyncIteratorProtocol {
/// Produces the next element in the sequence.
/// - Returns: The next element or `nil` if the end of the sequence is reached.
mutating func next() async throws -> Element? {
defer { self.hasThownError = true }
guard !self.hasThownError else { return nil }
defer { hasThownError = true }
guard !hasThownError else { return nil }

throw self.error
throw error
}
}
48 changes: 48 additions & 0 deletions Sources/Papyrus/Just.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/// An asynchronous sequence that only emits the provided value once.
///
/// ```swift
/// let stream = Just(1)
///
/// for await value in stream {
/// print(value)
/// }
///
/// // Prints:
/// // 1
/// ```
struct Just<Element>: AsyncSequence {
private let element: Element
private var emittedElement = false

// MARK: Initialization

/// Creates an async sequence that emits an element once.
/// - Parameters:
/// - element: The element to emit.
init(_ element: Element) {
self.element = element
}

// MARK: AsyncSequence

/// Creates an async iterator that emits elements of this async sequence.
/// - Returns: An instance that conforms to `AsyncIteratorProtocol`.
func makeAsyncIterator() -> Self {
.init(element)
}
}

extension Just: Sendable where Element: Sendable {}

// MARK: AsyncIteratorProtocol

extension Just: AsyncIteratorProtocol {
/// Produces the next element in the sequence.
/// - Returns: The next element or `nil` if the end of the sequence is reached.
mutating func next() async -> Element? {
guard !emittedElement else { return nil }
defer { emittedElement = true }

return element
}
}
74 changes: 0 additions & 74 deletions Sources/Papyrus/Logger.swift

This file was deleted.

6 changes: 3 additions & 3 deletions Sources/Papyrus/Observers/DirectoryObserver.swift
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import Foundation

struct DirectoryObserver: Sendable {
struct DirectoryObserver : Sendable {
private let url: URL

// MARK: Initialization

init(url: URL) throws {
self.url = url

if !FileManager.default.fileExists(atPath: self.url.path) {
try FileManager.default.createDirectory(at: self.url, withIntermediateDirectories: true)
if !FileManager.default.fileExists(atPath: url.path) {
try FileManager.default.createDirectory(at: url, withIntermediateDirectories: true)
}
}

Expand Down
4 changes: 3 additions & 1 deletion Sources/Papyrus/Observers/ObjectChange.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
public enum ObjectChange<T: Papyrus>: Equatable {
case deleted
case changed(T)
case created(T)
case deleted
}

extension ObjectChange: Sendable where T: Sendable {}
5 changes: 3 additions & 2 deletions Sources/Papyrus/Papyrus.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ import Foundation
/// - `Codable`
/// - `Equatable`
/// - `Identifiable where ID: LosslessStringConvertible & Sendable`
public protocol Papyrus: Codable, Equatable, Identifiable where ID: LosslessStringConvertible & Sendable { }
public protocol Papyrus: Codable, Equatable, Identifiable
where ID: LosslessStringConvertible & Sendable { }

// MARK: Helpers

extension Papyrus {
var filename: String { String(self.id) }
var filename: String { String(id) }
var typeDescription: String { String(describing: type(of: self)) }
}
Loading