Skip to content
This repository was archived by the owner on Mar 1, 2020. It is now read-only.

Commit 444c14d

Browse files
committed
Initial version
0 parents  commit 444c14d

File tree

7 files changed

+327
-0
lines changed

7 files changed

+327
-0
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
bin/querykit
2+
Rome/
3+
querykit-cli.tar.gz
4+

Makefile

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
DESTDIR := /usr/local
2+
3+
all: dependencies build
4+
5+
build:
6+
xcrun -sdk macosx swiftc -O -o bin/querykit -F Rome -framework CoreData -framework PathKit -framework Stencil bin/querykit.swift
7+
8+
dependencies:
9+
pod install --no-integrate
10+
11+
install:
12+
mkdir -p "$(DESTDIR)/bin/"
13+
mkdir -p "$(DESTDIR)/share/querykit/"
14+
mkdir -p "$(DESTDIR)/Frameworks/"
15+
cp -f "bin/querykit" "$(DESTDIR)/bin/"
16+
cp -f "share/querykit/template.swift" "$(DESTDIR)/share/querykit/"
17+
cp -fr "Rome/" "$(DESTDIR)/Frameworks/"
18+
install_name_tool -add_rpath "@executable_path/../Frameworks/" "$(DESTDIR)/bin/querykit"
19+

Podfile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
platform :osx, '10.10'
2+
3+
use_frameworks!
4+
plugin 'cocoapods-rome'
5+
6+
pod 'PathKit', '0.4.0-beta.1'
7+
pod 'Stencil', :git => 'https://github.com/kylef/Stencil', :branch => 'swift-2.0'
8+

Podfile.lock

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
PODS:
2+
- PathKit (0.4.0-beta.1)
3+
- Stencil (0.2.0):
4+
- PathKit (~> 0.4.0-beta.1)
5+
6+
DEPENDENCIES:
7+
- PathKit (= 0.4.0-beta.1)
8+
- Stencil (from `https://github.com/kylef/Stencil`, branch `swift-2.0`)
9+
10+
EXTERNAL SOURCES:
11+
Stencil:
12+
:branch: swift-2.0
13+
:git: https://github.com/kylef/Stencil
14+
15+
CHECKOUT OPTIONS:
16+
Stencil:
17+
:commit: dcf2611ac24829ffe80cf46894ebc98fdda62e0c
18+
:git: https://github.com/kylef/Stencil
19+
20+
SPEC CHECKSUMS:
21+
PathKit: dd424f40892d4f60f279c8f2cd82503fc86e4dad
22+
Stencil: 4214b68cf5a5f24c74c8c6fbbb573e225c490f88
23+
24+
COCOAPODS: 0.39.0.beta.4

README.md

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# QueryKit CLI
2+
3+
A command line tool to generate extensions for your Core Data models
4+
including QueryKit attributes for type-safe Core Data querying.
5+
Allowing you to use Xcode generated managed object classes with QueryKit.
6+
7+
## Installation
8+
9+
TODO?
10+
11+
## Usage
12+
13+
You can run QueryKit against your model providing the directory to save the
14+
extensions.
15+
16+
```bash
17+
$ querykit <modelfile> <directory-to-output>
18+
```
19+
20+
### Example
21+
22+
We've provided an [example application](http://github.com/QueryKit/TodoExample) using QueryKit's CLI tool.
23+
24+
```
25+
$ querykit Todo/Model.xcdatamodeld Todo/Model
26+
-> Generated 'Task' 'Todo/Model/Task+QueryKit.swift'
27+
-> Generated 'User' 'Todo/Model/User+QueryKit.swift'
28+
```
29+
30+
QueryKit CLI will generate a `QueryKit` extension for each of your managed
31+
object subclasses that you can add to Xcode, in this case `Task` and `User`.
32+
33+
These extensions provide you with properties on your model for type-safe filtering and ordering.
34+
35+
#### Task+QueryKit.swift
36+
37+
```swift
38+
import QueryKit
39+
40+
extension Task {
41+
static var createdAt:Attribute<NSDate> { return Attribute("createdAt") }
42+
static var creator:Attribute<User> { return Attribute("creator") }
43+
static var name:Attribute<String> { return Attribute("name") }
44+
static var complete:Attribute<Bool> { return Attribute("complete") }
45+
}
46+
47+
extension Attribute where AttributeType: Task {
48+
static var creator:Attribute<User> { return attribute(AttributeType.creator) }
49+
static var createdAt:Attribute<NSDate> { return attribute(AttributeType.createdAt) }
50+
static var name:Attribute<String> { return attribute(AttributeType.name) }
51+
static var complete:Attribute<Bool> { return attribute(AttributeType.complete) }
52+
}
53+
```
54+
55+
#### User+QueryKit.swift
56+
57+
```swift
58+
import QueryKit
59+
60+
extension User {
61+
static var name:Attribute<String> { return Attribute("name") }
62+
}
63+
64+
extension Attribute where AttributeType: User {
65+
static var name:Attribute<String> { return attribute(AttributeType.name) }
66+
}
67+
```
68+
69+
We can use these properties in conjunction with QueryKit to build type-safe
70+
queries. For example, here we are querying for all the Tasks which are
71+
created by a user with the name `Kyle` which are completed, ordered
72+
by their created date.
73+
74+
```swift
75+
Task.queryset(context)
76+
.filter { $0.user.name == "Kyle" }
77+
.exclude { $0.completed == true }
78+
.orderBy { $0.createdAt.ascending }
79+
```
80+

bin/querykit.swift

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
#!/usr/bin/env xcrun swift -F Rome
2+
3+
import CoreData
4+
import PathKit
5+
import Stencil
6+
7+
8+
extension Path {
9+
static var processPath:Path {
10+
if Process.arguments[0].componentsSeparatedByString(Path.separator).count > 1 {
11+
return Path.current + Process.arguments[0]
12+
}
13+
14+
let PATH = NSProcessInfo.processInfo().environment["PATH"]!
15+
let paths = PATH.componentsSeparatedByString(":").map {
16+
Path($0) + Process.arguments[0]
17+
}.filter { $0.exists }
18+
19+
return paths.first!
20+
}
21+
22+
static var defaultTemplatePath:Path {
23+
return processPath + "../../share/querykit/template.swift"
24+
}
25+
}
26+
27+
28+
func compileCoreDataModel(source:Path) -> Path {
29+
let destinationExtension = source.`extension`!.hasSuffix("d") ? ".momd" : ".mom"
30+
let destination = try! Path.uniqueTemporary() + (source.lastComponentWithoutExtension + destinationExtension)
31+
system("xcrun momc \(source.absolute()) \(destination.absolute())")
32+
return destination
33+
}
34+
35+
class AttributeDescription : NSObject {
36+
let name:String
37+
let type:String
38+
39+
init(name:String, type:String) {
40+
self.name = name
41+
self.type = type
42+
}
43+
}
44+
45+
extension NSAttributeDescription {
46+
var qkAttributeDescription:AttributeDescription? {
47+
if let className = attributeValueClassName {
48+
return AttributeDescription(name: name, type: className)
49+
}
50+
51+
return nil
52+
}
53+
}
54+
55+
extension NSRelationshipDescription {
56+
var qkAttributeDescription:AttributeDescription? {
57+
if let destinationEntity = destinationEntity {
58+
var type = destinationEntity.managedObjectClassName
59+
60+
if toMany {
61+
type = "Set<\(type)>"
62+
63+
if ordered {
64+
type = "Ordered\(type)"
65+
}
66+
}
67+
68+
return AttributeDescription(name: name, type: type)
69+
}
70+
71+
return nil
72+
}
73+
}
74+
75+
class CommandError : ErrorType {
76+
let description:String
77+
78+
init(description:String) {
79+
self.description = description
80+
}
81+
}
82+
83+
func render(entity:NSEntityDescription, destination:Path, template:Template) throws {
84+
let attributes = entity.properties.flatMap { property -> AttributeDescription? in
85+
if let attribute = property as? NSAttributeDescription {
86+
return attribute.qkAttributeDescription
87+
} else if let relationship = property as? NSRelationshipDescription {
88+
return relationship.qkAttributeDescription
89+
}
90+
91+
return nil
92+
}
93+
94+
let context = Context(dictionary: [
95+
"className": entity.managedObjectClassName,
96+
"attributes": attributes,
97+
"entityName": entity.name ?? "Unknown",
98+
])
99+
100+
switch template.render(context) {
101+
case .Success(let string):
102+
destination.write(string)
103+
case .Error(let error):
104+
throw CommandError(description: "Failed to render '\(entity.name): \(error).'")
105+
}
106+
}
107+
108+
func render(model:NSManagedObjectModel, destination:Path, templatePath:Path) {
109+
if !destination.exists {
110+
do {
111+
try destination.mkdir()
112+
} catch {
113+
print("Failed to create directory: '\(destination)'.")
114+
return
115+
}
116+
}
117+
118+
for entity in model.entities {
119+
let template = try! Template(path: templatePath)!
120+
let className = entity.managedObjectClassName
121+
122+
if className == "NSManagedObject" {
123+
let name = entity.name ?? "Unknown"
124+
print("-> Skipping entity '\(name)', doesn't use a custom class.")
125+
continue
126+
}
127+
128+
let destinationFile = destination + (className + "+QueryKit.swift")
129+
130+
do {
131+
try render(entity, destination: destinationFile, template: template)
132+
print("-> Generated '\(className)' '\(destinationFile)'")
133+
} catch {
134+
print(error)
135+
}
136+
137+
}
138+
}
139+
140+
func run() {
141+
let arguments = Process.arguments
142+
143+
if arguments.count == 3 {
144+
let modelPath = Path(arguments[1])
145+
let outputPath = Path(arguments[2])
146+
let modelExtension = modelPath.`extension`
147+
let isDataModel = modelExtension == "xcdatamodel"
148+
let isDataModeld = modelExtension == "xcdatamodeld"
149+
150+
if isDataModel || isDataModeld {
151+
if modelPath.isReadable {
152+
let templatePath = Path.defaultTemplatePath
153+
if !templatePath.isReadable {
154+
print("Template '\(templatePath)' is not readable.")
155+
} else {
156+
let compiledModel = compileCoreDataModel(modelPath)
157+
let modelURL = NSURL(fileURLWithPath: compiledModel.description)
158+
let model = NSManagedObjectModel(contentsOfURL: modelURL)!
159+
render(model, destination: outputPath, templatePath: templatePath)
160+
}
161+
} else {
162+
print("'\(modelPath)' does not exist or is not readable.")
163+
}
164+
} else {
165+
print("'\(modelPath)' is not a Core Data model.")
166+
}
167+
} else {
168+
let processName = arguments[0]
169+
print("Usage: \(processName) <model> <output-directory>")
170+
}
171+
}
172+
173+
run()
174+

share/querykit/template.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// This is a generated file from QueryKit.
2+
// https://github.com/QueryKit/querykit-cli
3+
4+
import QueryKit
5+
6+
/// Extension to {{ className }} providing an QueryKit attribute descriptions.
7+
extension {{ className }} {{% for attribute in attributes %}
8+
static var {{ attribute.name }}:Attribute<{{ attribute.type }}> { return Attribute("{{ attribute.name }}") }{% endfor %}
9+
10+
class func queryset(context:NSManagedObjectContext) -> QuerySet<{{ className }}> {
11+
return QuerySet(context, "{{ entityName }}")
12+
}
13+
}
14+
15+
extension Attribute where AttributeType: {{ className }} {{% for attribute in attributes %}
16+
var {{ attribute.name }}:Attribute<{{ attribute.type }}> { return attribute(AttributeType.{{ attribute.name }}) }{% endfor %}
17+
}
18+

0 commit comments

Comments
 (0)