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: Predicatable { /// The date events public enum Event { case today, tomorrow, yesterday, sameDay(as: Date) } /// The key path for the date public var keyPath: KeyPath // The event to check public var event: Event public init(keyPath: KeyPath, 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: Predicatable where Value.Element: Equatable { /// KeyPath for the sequence public var keyPath: KeyPath // The value you wish to check exists public var value: Value.Element public init(keyPath: KeyPath, 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: Predicatable { /// KeyPath for the object public var keyPath: KeyPath /// The range the object should be in public var range: ClosedRange public init(keyPath: KeyPath, in range: ClosedRange) { 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 : Predicatable { public enum Event: String { case eq = "==" case lt = "<" case gt = ">" } public var keyPath: KeyPath public var event: Event public var object: Value public init(where keyPath: KeyPath, is event: String, to object: Value) { self.init(where: keyPath, is: Event(rawValue: event) ?? .eq, to: object) } public init(where keyPath: KeyPath, 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 == (lhs: KeyPath, rhs: Value) -> ComparablePredicate { return ComparablePredicate(where: lhs, is: .eq, to: rhs) } public func >>= (lhs: KeyPath, rhs: ClosedRange) -> RangePredicate { return RangePredicate(keyPath: lhs, in: rhs) } extension Bool { init(value: Element, predicate: (ComparablePredicate)) { 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(_ 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) }) } }