Skip to content

Commit

Permalink
adds NSArray CollectionType conformance and splits up PrediKit file i…
Browse files Browse the repository at this point in the history
…nto multiple easy to digest files and better organizes files in the project for potential contributors.
  • Loading branch information
hectormatos2011 committed May 30, 2016
1 parent cc9dcfe commit b7907cc
Show file tree
Hide file tree
Showing 31 changed files with 1,444 additions and 1,089 deletions.
3 changes: 1 addition & 2 deletions .slather.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,4 @@ scheme: PrediKit-iOS
source_directory: Sources
ignore:
- Tests/*
- PrediKit-iOS/*
- PrediKit-OSX/*
- Supporting Files/**
7 changes: 0 additions & 7 deletions CHANGELOG.md

This file was deleted.

2 changes: 1 addition & 1 deletion PrediKit.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'PrediKit'
s.version = '2.0.6'
s.version = '3.0'
s.license = 'MIT'
s.summary = 'A Swift NSPredicate DSL for iOS & OS X inspired by SnapKit, lovingly written in Swift, and created by the awesome peeps at KrakenDev.io!'
s.homepage = 'https://github.com/KrakenDev/PrediKit'
Expand Down
235 changes: 193 additions & 42 deletions PrediKit.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
![Pretty Banner](https://github.com/KrakenDev/PrediKit/wiki/Sources/Banner.jpg)
[![Travis Build Status](http://img.shields.io/travis/KrakenDev/PrediKit.svg?style=flat-square)](https://travis-ci.org/KrakenDev/PrediKit)
[![Coveralls](https://img.shields.io/coveralls/KrakenDev/PrediKit/master.svg?style=flat-square)](https://coveralls.io/github/KrakenDev/PrediKit?branch=master)
[![Supported Platforms](https://img.shields.io/cocoapods/p/PrediKit.svg)]()
[![Swift Version Compatibility](https://img.shields.io/badge/swift2-compatible-4BC51D.svg?style=flat-square)](https://developer.apple.com/swift)
[![Cocoapods Version](https://img.shields.io/badge/pod-2.0.6-blue.svg?style=flat-square)](https://cocoapods.org/pods/PrediKit)
[![Cocoapods Version](https://img.shields.io/badge/pod-3.0-blue.svg?style=flat-square)](https://cocoapods.org/pods/PrediKit)
[![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat-square)](https://github.com/Carthage/Carthage)
[![LICENSE](http://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](https://raw.githubusercontent.com/KrakenDev/PrediKit/master/LICENSE)

Expand Down Expand Up @@ -142,11 +143,11 @@ PrediKit also overloads the `&&`, `||`, and `!` operators. This allows you compo

```swift
let predicate = NSPredicate(ManagedLegend.self) { includeIf in
//Include any ManagedLegend instance if the property named "string" is NOT nil and does NOT equal "The Almighty Kraken"
//Include any ManagedLegend instance if the property named "string" is NOT nil and does NOT equal "The Almighty Kraken"
!includeIf.string("title").equalsNil &&
!includeIf.string("title").equals("The Almighty Kraken") &&

//Also include any ManagedLegend instance if the date property named "birthdate" is in the past or if the bool property "isAwesome" is true.
//Also include any ManagedLegend instance if the date property named "birthdate" is in the past or if the bool property "isAwesome" is true.
includeIf.date("birthdate").isEarlierThan(NSDate()) ||
includeIf.bool("isAwesome").isTrue
}
Expand Down
101 changes: 101 additions & 0 deletions Sources/Builders/FinalizedIncluder.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
//
// FinalizedIncluder.swift
// PrediKit
//
// Created by Hector Matos on 5/30/16.
//
//

import Foundation

/**
A class that finalizes an includer created within the builder closure of PrediKit's `NSPredicate` convenience initializer. All includers (basically any line of code within that closure) must end in a call to a function that returns an instance or subclassed instance of this class to create a valid `NSPredicate`.
*/
public final class FinalizedIncluder<T: Reflectable> {
private let builder: PredicateBuilder<T>
private var finalArguments: [AnyObject?]

private(set) var finalPredicateString: String
private(set) var ANDPredicatesToCombine: [String] = []
private(set) var ORPredicatesToCombine: [String] = []

init(builder: PredicateBuilder<T>, arguments: [AnyObject?] = []) {
self.builder = builder
self.finalPredicateString = builder.predicateString
self.finalArguments = arguments
}

private static func combine<T>(lhs: FinalizedIncluder<T>, rhs: FinalizedIncluder<T>, logicalAND: Bool) -> FinalizedIncluder<T> {
let lhsPredicate = lhs.finalPredicateString
let rhsPredicate = rhs.finalPredicateString
let predicateFormat: String

var lhsPredicatesToCombine = logicalAND ? lhs.ANDPredicatesToCombine : lhs.ORPredicatesToCombine
var rhsPredicatesToCombine = logicalAND ? rhs.ANDPredicatesToCombine : rhs.ORPredicatesToCombine

if lhsPredicatesToCombine.isEmpty && rhsPredicatesToCombine.isEmpty {
lhsPredicatesToCombine = [lhsPredicate, rhsPredicate]
rhsPredicatesToCombine = lhsPredicatesToCombine
} else if rhsPredicatesToCombine.isEmpty {
//Operators associate to the left so there's no need to check if the lhs combination predicate array is empty since it will always have something if it gets here by failing the first check.
lhsPredicatesToCombine.append(rhsPredicate)
rhsPredicatesToCombine = lhsPredicatesToCombine
} else {
lhsPredicatesToCombine.appendContentsOf(rhsPredicatesToCombine)
rhsPredicatesToCombine = lhsPredicatesToCombine
}

if logicalAND {
lhs.ANDPredicatesToCombine = lhsPredicatesToCombine
rhs.ANDPredicatesToCombine = lhsPredicatesToCombine

predicateFormat = "(\(lhsPredicatesToCombine.joinWithSeparator(" && ")))"
} else {
lhs.ORPredicatesToCombine = lhsPredicatesToCombine
rhs.ORPredicatesToCombine = lhsPredicatesToCombine

predicateFormat = "(\(lhsPredicatesToCombine.joinWithSeparator(" || ")))"
}

lhs.finalPredicateString = predicateFormat

lhs.builder.predicateString = lhs.finalPredicateString
rhs.builder.predicateString = lhs.builder.predicateString

lhs.finalArguments = lhs.finalArguments + rhs.finalArguments
rhs.finalArguments = lhs.finalArguments
lhs.builder.arguments = lhs.finalArguments
rhs.builder.arguments = lhs.finalArguments

return lhs
}
}

/**
Convenience infix `&&` operator that combines two `FinalizedIncluder<T>` instances into one `FinalizedIncluder<T>` that represents the ANDed compound of the `finalizedPredicate` properties in each instance.
Essentially, you use this operator to join together two includers.
*/
public func && <T>(lhs: FinalizedIncluder<T>, rhs: FinalizedIncluder<T>) -> FinalizedIncluder<T> {
return .combine(lhs, rhs: rhs, logicalAND: true)
}

/**
Convenience infix `||` operator that combines two `FinalizedIncluder<T>` instances into one `FinalizedIncluder<T>` that represents the ORed compound of the `finalizedPredicate` properties in each instance.
Essentially, you use this operator to join together two includers.
*/
public func || <T>(lhs: FinalizedIncluder<T>, rhs: FinalizedIncluder<T>) -> FinalizedIncluder<T> {
return .combine(lhs, rhs: rhs, logicalAND: false)
}

/**
Convenience prefix `!` operator that turns `FinalizedIncluder<T>` into its NOT version.
Essentially, you use this operator to indicate the opposite of an includer.
*/
public prefix func ! <T>(rhs: FinalizedIncluder<T>) -> FinalizedIncluder<T> {
rhs.finalPredicateString = "!(\(rhs.finalPredicateString))"
rhs.builder.predicateString = rhs.finalPredicateString
return rhs
}
156 changes: 156 additions & 0 deletions Sources/Builders/PredicateBuilder.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
//
// PredicateBuilder.swift
// PrediKit
//
// Created by Hector Matos on 5/30/16.
//
//

import Foundation

/**
The class that gets passed into the builder closure of PrediKit's `NSPredicate` convenience initializer.
*/
public class PredicateBuilder<T: Reflectable> {
let type: T.Type
var predicateString: String = ""
var arguments: [AnyObject?] = []

/**
Used to indicate that you want to query the actual object checked when the predicate is run. Behaves like the `SELF` in the SQL-like query:
NSPredicate(format: "SELF in names")
*/
public var SELF: BasicQuery<T> {
return BasicQuery(builder: self, property: "SELF")
}

init(type: T.Type) {
self.type = type
}

/**
Describes the key of class `T`'s `String` property you want to query. For example, when creating a predicate that compares a class' string property to a given string:
class Kraken: NSObject {
var theyCallMe: String
}
NSPredicate(format: "theyCallMe == 'Chief Supreme'")
The `property` parameter would be the "theyCallMe" in the example predicate format.
- Parameters:
- property: The name of the property in the class of type `T`
- file: Name of the file the function is being called from. Defaults to `#file`
- line: Number of the line the function is being called from. Defaults to `#line`
*/
public func string(property: Selector, file: String = #file, line: Int = #line) -> StringQuery<T> {
return StringQuery(builder: self, property: validatedProperty(property, file: file, line: line))
}

/**
Describes the key of class `T`'s number type property you want to query. For example, when creating a predicate that compares a number property to a given value:
class Kraken: NSObject {
var numberOfHumansEaten: Int
}
NSPredicate(format: "numberOfHumansEaten >= 6")
The `property` parameter would be the "numberOfHumansEaten" in the example predicate format.
- Parameters:
- property: The name of the property in the class of type `T`
- file: Name of the file the function is being called from. Defaults to `#file`
- line: Number of the line the function is being called from. Defaults to `#line`
*/
public func number(property: Selector, file: String = #file, line: Int = #line) -> NumberQuery<T> {
return NumberQuery(builder: self, property: validatedProperty(property, file: file, line: line))
}

/**
Describes the key of class `T`'s `NSDate` property you want to query. For example, when creating a predicate compares a class' date property to another given date:
class Kraken: NSObject {
var birthdate: NSDate
}
NSPredicate(format: "birthdate == %@", NSDate())
The `property` parameter would be the "birthdate" in the example predicate format.
- Parameters:
- property: The name of the property in the class of type `T`
- file: Name of the file the function is being called from. Defaults to `#file`
- line: Number of the line the function is being called from. Defaults to `#line`
*/
public func date(property: Selector, file: String = #file, line: Int = #line) -> DateQuery<T> {
return DateQuery(builder: self, property: validatedProperty(property, file: file, line: line))
}

/**
Describes the key of class `T`'s `Bool` property you want to query. For example, when creating a predicate that checks against a given `Bool` flag in a class:
class Kraken: NSObject {
var isHungry: Bool
}
NSPredicate(format: "isHungry == true")
The `property` parameter would be the "isHungry" in the example predicate format.
- Parameters:
- property: The name of the property in the class of type `T`
- file: Name of the file the function is being called from. Defaults to `#file`
- line: Number of the line the function is being called from. Defaults to `#line`
*/
public func bool(property: Selector, file: String = #file, line: Int = #line) -> BooleanQuery<T> {
return BooleanQuery(builder: self, property: validatedProperty(property, file: file, line: line))
}

/**
Describes the key of class `T`'s `CollectionType` property you want to query. This is also the starting point for subqueries on list properties. For example, when creating a predicate that checks if a class' array property has 5 objects:
class Kraken: NSObject {
var friends: [LegendaryCreatures]
}
NSPredicate(format: "friends.@count == 5")
The `property` parameter would be the "friends" in the example predicate format.
- Parameters:
- property: The name of the property in the class of type `T`
- file: Name of the file the function is being called from. Defaults to `#file`
- line: Number of the line the function is being called from. Defaults to `#line`
*/
public func collection(property: Selector, file: String = #file, line: Int = #line) -> SequenceQuery<T> {
return SequenceQuery(builder: self, property: validatedProperty(property, file: file, line: line))
}

/**
Describes a member with a custom type that belongs to class `T` that you want to query. Recursive since it returns an instance of PredicateMemberQuery that is a subclass of PredicateBuilder. For example, when creating a predicate for a specific custom type member:
class Kraken: NSObject {
var friend: LegendaryCreature
}
NSPredicate(format: "friend == %@", legendaryCreature)
The `property` parameter would be the "friend" in the example predicate format.
- Parameters:
- property: The name of the property member in the class of type `T`
- memberType: The Reflectable type of the property member
- file: Name of the file the function is being called from. Defaults to `__FILE__`
- line: Number of the line the function is being called from. Defaults to `__LINE__`
*/
public func member<U: protocol<Reflectable, AnyObject>>(property: Selector, ofType memberType: U.Type, file: String = #file, line: Int = #line) -> MemberQuery<T, U> {
return MemberQuery(builder: self, property: validatedProperty(property), memberType: memberType)
}

internal func validatedProperty(property: Selector, file: String = #file, line: Int = #line) -> String {
if !type.properties().contains(property) && self.type != NSObject.self {
#if DEBUG
print("\(String(type)) does not seem to contain property \"\(property)\". This could be due to the optionality of a value type. Possible property key values:\n\(type.properties()).\nWarning in file:\(file) at line \(line)")
#endif
}

return String(property)
}
}
23 changes: 23 additions & 0 deletions Sources/Builders/PredicateSubqueryBuilder.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//
// PredicateSubqueryBuilder.swift
// PrediKit
//
// Created by Hector Matos on 5/30/16.
//
//

import Foundation

/**
A class that facilitates the creation of subqueries against `T`'s `CollectionType` properties. Used in tandem with the `PredicateSequenceQuery<T>` class.
*/
public final class PredicateSubqueryBuilder<T: Reflectable>: PredicateBuilder<T> {
override init(type: T.Type) {
super.init(type: type)
}

override func validatedProperty(property: Selector, file: String, line: Int) -> String {
let subqueryProperty = super.validatedProperty(property, file: file, line: line)
return "$\(String(type))Item.\(subqueryProperty)"
}
}
20 changes: 20 additions & 0 deletions Sources/Extensions/NSArray+PrediKit.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// NSArray+PrediKit.swift
// PrediKit
//
// Created by Hector Matos on 5/30/16.
//
//

import Foundation

extension NSArray: CollectionType {
public typealias SubSequence = [AnyObject]

public var startIndex: Int { return 0 }
public var endIndex: Int { return count }

public subscript (bounds: Range<Int>) -> [AnyObject] {
return subarrayWithRange(NSRange(location: bounds.startIndex, length: bounds.endIndex - bounds.startIndex))
}
}
47 changes: 47 additions & 0 deletions Sources/Extensions/NSPredicate+PrediKit.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//
// PrediKit.swift
// KrakenDev
//
// Created by Hector Matos on 5/13/16.
// Copyright © 2016 KrakenDev. All rights reserved.
//

import Foundation

/**
This is where the magic happens. This extension allows anyone to construct an `NSPredicate` from a closure.
*/
public extension NSPredicate {
/**
A generic convenience initializer that accepts a `Reflectable` type and a builder closure that allows you to construct includers that describe the resulting `NSPredicate` instance.
- Parameters:
- type: The `Reflectable` class type that you'll be querying against. The type you supply here is what PrediKit will inspect to ensure the property names you specify in your includers are contained in that class' property list.
- builder: A closure that you use to generate includers that construct each subpredicate in the created `NSPredicate`
*/
convenience init<T: Reflectable>(_ type: T.Type, @noescape builder: ((includeIf: PredicateBuilder<T>) -> Void)) {
let predicateBuilder = PredicateBuilder(type: type)
builder(includeIf: predicateBuilder)

if predicateBuilder.predicateString.isEmpty {
self.init(value: false)
} else {
self.init(format: predicateBuilder.predicateString, argumentArray: predicateBuilder.arguments.flatMap({$0}))
}
}
}

/**
Convenience infix `&&` operator that combines two `NSPredicate` instances into one ANDed `NSCompoundPredicate`
*/
public func && (lhs: NSPredicate, rhs: NSPredicate) -> NSPredicate {
return NSCompoundPredicate(andPredicateWithSubpredicates: [lhs, rhs])
}

/**
Convenience infix `||` operator that combines two `NSPredicate` instances into one ORed `NSCompoundPredicate`
*/
public func || (lhs: NSPredicate, rhs: NSPredicate) -> NSPredicate {
return NSCompoundPredicate(orPredicateWithSubpredicates: [lhs, rhs])
}

0 comments on commit b7907cc

Please sign in to comment.