Skip to content

Commit 66c455b

Browse files
committed
Added callback trigger instead of notification based one
1 parent 4ec960a commit 66c455b

File tree

6 files changed

+120
-22
lines changed

6 files changed

+120
-22
lines changed

Sources/RuleKit/Center.swift

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public final class RuleKit {
4444
}
4545
}
4646

47-
var rules: [Notification.Name: any Rule] = [:]
47+
var rules: [(rule: any Rule, trigger: any Trigger)] = []
4848

4949
private init() {}
5050

@@ -56,24 +56,24 @@ public final class RuleKit {
5656
}
5757

5858
func triggerFulfilledRules() async throws {
59-
for (notification, rule) in rules {
59+
for (rule, trigger) in rules {
6060
guard await rule.isFulfilled else {
6161
continue
6262
}
6363
let queue = rule.firstOption(ofType: DispatchQueueOption.self)?.queue ?? .main
6464
queue.async {
65-
NotificationCenter.default.post(name: notification, object: nil)
65+
trigger.execute()
6666
}
67-
try await store.persist(triggerOf: notification)
67+
try await store.persist(triggerOf: trigger)
6868
}
6969
}
7070

7171
func donations(for event: Event) async -> Event.Donations {
7272
(try? await store.donations(for: event)) ?? .empty
7373
}
7474

75-
func lastTrigger(for notification: Notification.Name) async -> Date? {
76-
try? await store.lastTrigger(of: notification)
75+
func lastTrigger(for trigger: any Trigger) async -> Date? {
76+
try? await store.lastTrigger(of: trigger)
7777
}
7878

7979
func donate(_ event: Event) async {
@@ -113,6 +113,14 @@ extension RuleKit {
113113
}
114114

115115
public static func setRule(triggering notification: Notification.Name, options: [any RuleKitOption] = [], _ rule: Rule) {
116-
RuleKit.internal.rules[notification] = options.isEmpty ? rule : RuleWithOptions(options: options, notification: notification, rule: rule)
116+
let trigger = NotificationCenterTrigger(notification: notification)
117+
let rule = options.isEmpty ? rule : RuleWithOptions(options: options, trigger: trigger, rule: rule)
118+
RuleKit.internal.rules.append((rule, trigger))
119+
}
120+
121+
public static func setRule(triggering callback: @escaping @Sendable () -> Void, rawValue: String, options: [any RuleKitOption] = [], _ rule: Rule) {
122+
let trigger = CallbackTrigger(rawValue: rawValue, callback: callback)
123+
let rule = options.isEmpty ? rule : RuleWithOptions(options: options, trigger: trigger, rule: rule)
124+
RuleKit.internal.rules.append((rule, trigger))
117125
}
118126
}

Sources/RuleKit/Option.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@ import Foundation
3030
public protocol RuleKitOption {
3131
/// If this returns true, the rule will never be fulfilled, and the notification prevented
3232
/// Defaults to false
33-
func preventRuleFulfillment(for notification: Notification.Name) async -> Bool
33+
func preventRuleFulfillment(for trigger: any Trigger) async -> Bool
3434
}
3535
extension RuleKitOption {
36-
public func preventRuleFulfillment(for notification: Notification.Name) async -> Bool {
36+
public func preventRuleFulfillment(for trigger: any Trigger) async -> Bool {
3737
false
3838
}
3939
}
@@ -70,8 +70,8 @@ public struct TriggerFrequencyOption: RuleKitOption {
7070
let frequency: Frequency
7171

7272
// Thank you, Dave Delong for your thoughtful advices on handling dates at NSSpain XI
73-
public func preventRuleFulfillment(for notification: Notification.Name) async -> Bool {
74-
guard let lastTrigger = await RuleKit.internal.lastTrigger(for: notification) else {
73+
public func preventRuleFulfillment(for trigger: any Trigger) async -> Bool {
74+
guard let lastTrigger = await RuleKit.internal.lastTrigger(for: trigger) else {
7575
return false
7676
}
7777
guard let earliestDateForNextRuleTrigger = Calendar.current.date(byAdding: frequency.component, value: 1, to: lastTrigger) else {

Sources/RuleKit/Rule.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,12 +117,12 @@ extension Rule where Self == AllOfRule {
117117

118118
struct RuleWithOptions: Rule {
119119
let options: [RuleKitOption]
120-
let notification: Notification.Name
120+
let trigger: any Trigger
121121
let rule: Rule
122122

123123
var isFulfilled: Bool {
124124
get async {
125-
for option in options where await option.preventRuleFulfillment(for: notification) {
125+
for option in options where await option.preventRuleFulfillment(for: trigger) {
126126
return false
127127
}
128128
return await rule.isFulfilled

Sources/RuleKit/Store.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,18 +83,18 @@ extension RuleKit {
8383
self.url = url
8484
}
8585

86-
func lastTrigger(of notification: Notification.Name) throws -> Date? {
86+
func lastTrigger(of trigger: any Trigger) throws -> Date? {
8787
if data == nil {
8888
try loadData()
8989
}
90-
return data?.lastTrigger[notification.rawValue]
90+
return data?.lastTrigger[trigger.rawValue]
9191
}
9292

93-
func persist(triggerOf notification: Notification.Name) throws {
93+
func persist(triggerOf trigger: any Trigger) throws {
9494
if data == nil {
9595
try loadData()
9696
}
97-
data?.lastTrigger[notification.rawValue] = Date()
97+
data?.lastTrigger[trigger.rawValue] = Date()
9898
try saveData()
9999
}
100100

Sources/RuleKit/Trigger.swift

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
//
2+
// Trigger.swift
3+
// RuleKit
4+
//
5+
// MIT License
6+
//
7+
// Copyright (c) 2023 Thomas Durand
8+
//
9+
// Permission is hereby granted, free of charge, to any person obtaining a copy
10+
// of this software and associated documentation files (the "Software"), to deal
11+
// in the Software without restriction, including without limitation the rights
12+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13+
// copies of the Software, and to permit persons to whom the Software is
14+
// furnished to do so, subject to the following conditions:
15+
//
16+
// The above copyright notice and this permission notice shall be included in all
17+
// copies or substantial portions of the Software.
18+
//
19+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25+
// SOFTWARE.
26+
//
27+
28+
import Foundation
29+
30+
public protocol Trigger: Sendable {
31+
var rawValue: String { get }
32+
func execute()
33+
}
34+
35+
struct NotificationCenterTrigger: Trigger {
36+
let notification: Notification.Name
37+
38+
var rawValue: String {
39+
notification.rawValue
40+
}
41+
42+
func execute() {
43+
NotificationCenter.default.post(name: notification, object: nil)
44+
}
45+
}
46+
47+
struct CallbackTrigger: Trigger {
48+
let rawValue: String
49+
let callback: @Sendable () -> Void
50+
51+
func execute() {
52+
callback()
53+
}
54+
}

Tests/RuleKitTests/RuleKitTests.swift

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,48 @@
2828
import XCTest
2929
@testable import RuleKit
3030

31+
extension RuleKit.Event {
32+
static let testEvent: Self = "test.event"
33+
}
34+
3135
final class RuleKitTests: XCTestCase {
32-
func testExample() throws {
33-
// XCTest Documentation
34-
// https://developer.apple.com/documentation/xctest
36+
static let testNotification = Notification.Name("test.notification")
37+
static let testCallback = "test.callback"
38+
39+
@MainActor
40+
override class func setUp() {
41+
do {
42+
try RuleKit.configure(storeLocation: .applicationDefault)
43+
} catch {}
44+
}
45+
46+
func testNotificationRuleTriggering() async throws {
47+
await RuleKit.Event.testEvent.reset()
48+
await RuleKit.setRule(triggering: Self.testNotification, .anyOf {
49+
EventRule(event: .testEvent) {
50+
$0.donations.count > 0
51+
}
52+
})
53+
let expectation = expectation(forNotification: Self.testNotification, object: nil)
54+
await RuleKit.Event.testEvent.donate()
55+
await fulfillment(of: [expectation])
56+
let count = await RuleKit.Event.testEvent.donations.count
57+
XCTAssertEqual(1, count)
58+
}
3559

36-
// Defining Test Cases and Test Methods
37-
// https://developer.apple.com/documentation/xctest/defining_test_cases_and_test_methods
60+
func testCallbackRuleTriggering() async throws {
61+
await RuleKit.Event.testEvent.reset()
62+
let expectation = XCTestExpectation()
63+
await RuleKit.setRule(triggering: {
64+
expectation.fulfill()
65+
}, rawValue: Self.testCallback, .anyOf {
66+
EventRule(event: .testEvent) {
67+
$0.donations.count > 0
68+
}
69+
})
70+
await RuleKit.Event.testEvent.donate()
71+
await fulfillment(of: [expectation])
72+
let count = await RuleKit.Event.testEvent.donations.count
73+
XCTAssertEqual(1, count)
3874
}
3975
}

0 commit comments

Comments
 (0)