Last active
April 30, 2022 13:01
-
-
Save harrytwright/ebfa20c1277eed2d2ffaccb73c446c65 to your computer and use it in GitHub Desktop.
Swift4 KeyPath Predicate
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
/// Predicatable is the protocol that all predicting objects conform. | |
public protocol Predicatable: CustomStringConvertible { | |
/// Returns a Boolean value indicating whether the specified object matches the conditions specified by the predicate. | |
/// | |
/// - Parameter object: The object against which to evaluate the predicate. | |
/// - Returns: `true` if object matches the conditions specified by the predicate, otherwise `false`. | |
func evaluate(with object: Any?) -> Bool | |
/// The predicate's format string. | |
var predicateFormat: String { get } | |
} | |
extension Predicatable { | |
public var description: String { | |
return self.predicateFormat | |
} | |
} | |
extension NSPredicate: Predicatable { | |
public convenience init(with predicatable: Predicatable) { | |
self.init(format: predicatable.predicateFormat) | |
} | |
} | |
/// Structure to create a logical test for dates. | |
/// | |
/// Simply used by passing the keyPath to a date and choosing an event, | |
/// then using the `evaluate(with:)` the logic will be tested | |
public struct DatePredicate<Key: Codable>: Predicatable { | |
/// The date events | |
public enum Event { | |
case today, tomorrow, yesterday, sameDay(as: Date) | |
} | |
/// The key path for the date | |
public var keyPath: KeyPath<Key, Date> | |
// The event to check | |
public var event: Event | |
public init(keyPath: KeyPath<Key, Date>, is event: Event) { | |
self.keyPath = keyPath | |
self.event = event | |
} | |
public func evaluate(with object: Any?) -> Bool { | |
guard let value = object as? Key else { return false } | |
switch event { | |
case .today: return Calendar.current.isDateInToday(value[keyPath: keyPath]) | |
case .tomorrow: return Calendar.current.isDateInTomorrow(value[keyPath: keyPath]) | |
case .yesterday: return Calendar.current.isDateInTomorrow(value[keyPath: keyPath]) | |
case .sameDay(let date): return Calendar.current.isDate(date, inSameDayAs: value[keyPath: keyPath]) | |
} | |
} | |
public var predicateFormat: String { | |
if let kvoString = keyPath._kvcKeyPathString { | |
return kvoString + " is \(event)" | |
} | |
return "\(keyPath) is \(event)" | |
} | |
} | |
/// Structure to create a logical test to see if an ebject exists inside a sequence. | |
/// | |
/// Simply used by passing the keyPath to a sequence, then | |
/// using the `evaluate(with:)` the logic will be tested | |
public struct ContainPredicate<Key: Codable, Value: Sequence>: Predicatable where Value.Element: Equatable { | |
/// KeyPath for the sequence | |
public var keyPath: KeyPath<Key, Value> | |
// The value you wish to check exists | |
public var value: Value.Element | |
public init(keyPath: KeyPath<Key, Value>, contains value: Value.Element) { | |
self.value = value | |
self.keyPath = keyPath | |
} | |
public func evaluate(with object: Any?) -> Bool { | |
guard let value = object as? Key else { return false } | |
return value[keyPath: keyPath].contains { $0 == self.value } | |
} | |
public var predicateFormat: String { | |
if let kvoString = keyPath._kvcKeyPathString { | |
return kvoString + " contains \(value)" | |
} | |
return "\(keyPath) contains \(value)" | |
} | |
} | |
/// Structure to create a logical test to see if an ebject exists inside a range of objects. | |
/// | |
/// Simply used by passing the keyPath to a value, then | |
/// using the `evaluate(with:)` the logic will be tested | |
public struct RangePredicate<Key: Codable, Value: Comparable>: Predicatable { | |
/// KeyPath for the object | |
public var keyPath: KeyPath<Key, Value> | |
/// The range the object should be in | |
public var range: ClosedRange<Value> | |
public init(keyPath: KeyPath<Key, Value>, in range: ClosedRange<Value>) { | |
self.range = range | |
self.keyPath = keyPath | |
} | |
public func evaluate(with object: Any?) -> Bool { | |
guard let value = object as? Key else { return false } | |
return range.contains(value[keyPath: keyPath]) | |
} | |
public var predicateFormat: String { | |
if let kvoString = keyPath._kvcKeyPathString { | |
return kvoString + " in \(range)" | |
} | |
return "\(keyPath) in \(range)" | |
} | |
} | |
/// <#Description#> | |
public struct ComparablePredicate<Key: Codable, Value: Comparable> : Predicatable { | |
public enum Event: String { | |
case eq = "==" | |
case lt = "<" | |
case gt = ">" | |
} | |
public var keyPath: KeyPath<Key, Value> | |
public var event: Event | |
public var object: Value | |
public init(where keyPath: KeyPath<Key, Value>, is event: String, to object: Value) { | |
self.init(where: keyPath, is: Event(rawValue: event) ?? .eq, to: object) | |
} | |
public init(where keyPath: KeyPath<Key, Value>, is event: Event, to object: Value) { | |
self.keyPath = keyPath | |
self.object = object | |
self.event = event | |
} | |
public func evaluate(with object: Any?) -> Bool { | |
guard let object = object as? Key else { return false } | |
return Bool(value: object, predicate: self) | |
} | |
public var predicateFormat: String { | |
if let kvoString = keyPath._kvcKeyPathString { | |
return kvoString + " \(event.rawValue) \"\(object)\"" | |
} | |
return "\(keyPath) \(event.rawValue) \"\(object)\"" | |
} | |
} | |
public func == <Key: Codable, Value: Comparable>(lhs: KeyPath<Key, Value>, rhs: Value) -> ComparablePredicate<Key, Value> { | |
return ComparablePredicate<Key, Value>(where: lhs, is: .eq, to: rhs) | |
} | |
public func >>= <Key: Codable, Value>(lhs: KeyPath<Key, Value>, rhs: ClosedRange<Value>) -> RangePredicate<Key, Value> { | |
return RangePredicate<Key, Value>(keyPath: lhs, in: rhs) | |
} | |
extension Bool { | |
init<Element, Value>(value: Element, predicate: (ComparablePredicate<Element, Value>)) { | |
switch predicate.event { | |
case .eq: | |
self = value[keyPath: predicate.keyPath] == predicate.object | |
case .gt: | |
self = value[keyPath: predicate.keyPath] > predicate.object | |
case .lt: | |
self = value[keyPath: predicate.keyPath] < predicate.object | |
} | |
} | |
} | |
func check<Key>(_ value: Key, where predicate: [Predicatable]) -> Bool { | |
var passes: Bool = true | |
for check in predicate { | |
if passes == false { return false } | |
passes = check.evaluate(with: value) | |
} | |
return passes | |
} | |
public extension Sequence { | |
public func filter(where predicate: Predicatable...) -> [Element] { | |
return self.filter(where: predicate) | |
} | |
public func filter(where predicate: [Predicatable]) -> [Element] { | |
return self.filter ({ check($0, where: predicate) }) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment