segue_handler_plugin 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +30 -0
- data/Example/.ruby-version +1 -0
- data/Example/Gemfile +5 -0
- data/Example/Gemfile.lock +71 -0
- data/Example/Podfile +6 -0
- data/Example/Podfile.lock +14 -0
- data/Example/Pods/Headers/Private/WillowTreeSegueHandler/SegueHandler.h +19 -0
- data/Example/Pods/Local Podspecs/WillowTreeSegueHandler.podspec.json +25 -0
- data/Example/Pods/Manifest.lock +14 -0
- data/Example/Pods/Pods.xcodeproj/project.pbxproj +523 -0
- data/Example/Pods/Pods.xcodeproj/xcshareddata/xcschemes/WillowTreeSegueHandler.xcscheme +60 -0
- data/Example/Pods/Target Support Files/Pods/Info.plist +26 -0
- data/Example/Pods/Target Support Files/Pods/Pods-acknowledgements.markdown +26 -0
- data/Example/Pods/Target Support Files/Pods/Pods-acknowledgements.plist +56 -0
- data/Example/Pods/Target Support Files/Pods/Pods-dummy.m +5 -0
- data/Example/Pods/Target Support Files/Pods/Pods-frameworks.sh +91 -0
- data/Example/Pods/Target Support Files/Pods/Pods-resources.sh +95 -0
- data/Example/Pods/Target Support Files/Pods/Pods-umbrella.h +6 -0
- data/Example/Pods/Target Support Files/Pods/Pods.debug.xcconfig +8 -0
- data/Example/Pods/Target Support Files/Pods/Pods.modulemap +6 -0
- data/Example/Pods/Target Support Files/Pods/Pods.release.xcconfig +8 -0
- data/Example/Pods/Target Support Files/WillowTreeSegueHandler/Info.plist +26 -0
- data/Example/Pods/Target Support Files/WillowTreeSegueHandler/WillowTreeSegueHandler-dummy.m +5 -0
- data/Example/Pods/Target Support Files/WillowTreeSegueHandler/WillowTreeSegueHandler-prefix.pch +4 -0
- data/Example/Pods/Target Support Files/WillowTreeSegueHandler/WillowTreeSegueHandler-umbrella.h +7 -0
- data/Example/Pods/Target Support Files/WillowTreeSegueHandler/WillowTreeSegueHandler.modulemap +6 -0
- data/Example/Pods/Target Support Files/WillowTreeSegueHandler/WillowTreeSegueHandler.xcconfig +5 -0
- data/Example/SegueBuilder.xcodeproj/project.pbxproj +390 -0
- data/Example/SegueBuilder.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- data/Example/SegueBuilder.xcworkspace/contents.xcworkspacedata +10 -0
- data/Example/SegueBuilder/AppDelegate.swift +49 -0
- data/Example/SegueBuilder/Assets.xcassets/AppIcon.appiconset/Contents.json +73 -0
- data/Example/SegueBuilder/Base.lproj/LaunchScreen.storyboard +27 -0
- data/Example/SegueBuilder/Base.lproj/Main.storyboard +151 -0
- data/Example/SegueBuilder/Info.plist +45 -0
- data/Example/SegueBuilder/SegueExtensions.swift +57 -0
- data/Example/SegueBuilder/ViewControllers.swift +77 -0
- data/Example/Storyboards.swift +0 -0
- data/Example/test.sh +20 -0
- data/LICENSE +19 -0
- data/README.md +75 -0
- data/WillowTreeSegueHandler.podspec +23 -0
- data/exe/segue-handler-generator +1107 -0
- data/exe/segue-handler-generator-build +30 -0
- data/lib/cocoapods_plugin.rb +1 -0
- data/lib/pod/command/generate_segue_handlers.rb +31 -0
- data/lib/segue_handler_plugin.rb +4 -0
- data/lib/segue_handler_plugin/version.rb +3 -0
- data/segue_handler_plugin.gemspec +20 -0
- metadata +95 -0
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2015 WillowTree, Inc.
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
# WillowTreeSegueHandler
|
2
|
+
|
3
|
+
### *Better segue handling in Swift*
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
**Gemfile**
|
8
|
+
```ruby
|
9
|
+
gem "segue_handler_plugin"
|
10
|
+
```
|
11
|
+
|
12
|
+
**Podfile**
|
13
|
+
```ruby
|
14
|
+
plugin 'segue_handler_plugin'
|
15
|
+
pod 'WillowTreeSegueHandler'
|
16
|
+
```
|
17
|
+
|
18
|
+
**Build Phases**
|
19
|
+
Add a run script phase before the "Compile Sources" phase, with the following:
|
20
|
+
```bash
|
21
|
+
bundle exec pod --no-ansi --silent generate-segue-handlers $PROJECT_DIR $PROJECT_NAME
|
22
|
+
```
|
23
|
+
|
24
|
+
Build it once, then add the `SegueExtensions.swift` file to your project and
|
25
|
+
build again.
|
26
|
+
|
27
|
+
## Usage
|
28
|
+
|
29
|
+
Segues with identifiers will be pulled out into nested enums in extensions on
|
30
|
+
your view controllers, written to `SegueExtensions.swift`.
|
31
|
+
|
32
|
+
Example generated code, assuming `A` and `B` are view controllers connected
|
33
|
+
with from `A` to `B` via the segue with identifier `"ToB"`.
|
34
|
+
|
35
|
+
```swift
|
36
|
+
extension A: SegueHandler {
|
37
|
+
enum SegueIdentifier: String {
|
38
|
+
case ToB
|
39
|
+
}
|
40
|
+
|
41
|
+
enum ToSegueDestination: SegueDestination {
|
42
|
+
case ToB(B)
|
43
|
+
|
44
|
+
init?(identifier: SegueIdentifier, destination: UIViewController) {
|
45
|
+
switch identifier {
|
46
|
+
case .ToB:
|
47
|
+
guard let vc = destination as? B else { return nil }
|
48
|
+
self = ToB(vc)
|
49
|
+
}
|
50
|
+
}
|
51
|
+
}
|
52
|
+
}
|
53
|
+
```
|
54
|
+
|
55
|
+
These can be used in your view controller like so:
|
56
|
+
|
57
|
+
```swift
|
58
|
+
func pushManually() {
|
59
|
+
performSegue(.ToB, sender: self)
|
60
|
+
}
|
61
|
+
|
62
|
+
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
|
63
|
+
switch segueFromStoryboardSegue(segue) {
|
64
|
+
case .ToB(let b):
|
65
|
+
b.prepare(dependency: "injected from a")
|
66
|
+
}
|
67
|
+
}
|
68
|
+
```
|
69
|
+
|
70
|
+
See the enclosed `Example` application for a working demo.
|
71
|
+
|
72
|
+
### WillowTree is Hiring!
|
73
|
+
|
74
|
+
Want to write amazing tvOS apps? Want to write amazing iOS apps?
|
75
|
+
[Check out our openings!](http://willowtreeapps.com/careers/)
|
@@ -0,0 +1,23 @@
|
|
1
|
+
Pod::Spec.new do |s|
|
2
|
+
s.name = "WillowTreeSegueHandler"
|
3
|
+
s.version = "0.0.1"
|
4
|
+
s.summary = "Better segue handling in Swift"
|
5
|
+
s.description = <<-DESC
|
6
|
+
|
7
|
+
DESC
|
8
|
+
|
9
|
+
s.homepage = "https://github.com/willowtreeapps/segue_handler"
|
10
|
+
s.license = "MIT"
|
11
|
+
s.authors = { "Ian Terrell" => "ian.terrell@gmail.com" }
|
12
|
+
s.source = { :git => "https://github.com/willowtreeapps/segue_handler.git",
|
13
|
+
:tag => "0.0.1" }
|
14
|
+
|
15
|
+
s.platform = :ios, :tvos
|
16
|
+
s.ios.deployment_target = "8.3"
|
17
|
+
s.tvos.deployment_target = "9.1"
|
18
|
+
|
19
|
+
s.source_files = "SegueHandler/**/*.swift",
|
20
|
+
"SegueHandler/**/*.h",
|
21
|
+
"SegueHandler/**/*.m"
|
22
|
+
s.public_header_files = "SegueHandler/**/*.h"
|
23
|
+
end
|
@@ -0,0 +1,1107 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
t = Tempfile.new("generate-segue-handlers.swift")
|
3
|
+
t << <<-'SWIFTDOC'
|
4
|
+
//
|
5
|
+
// Segue Handler Generator Script
|
6
|
+
//
|
7
|
+
// Generate swift file based on storyboard files
|
8
|
+
//
|
9
|
+
// Usage:
|
10
|
+
// natalie.swift Main.storyboard > Storyboards.swift
|
11
|
+
// natalie.swift path/toproject/with/storyboards > Storyboards.swift
|
12
|
+
//
|
13
|
+
// Licence: MIT
|
14
|
+
// Author: Marcin Krzyżanowski http://blog.krzyzanowskim.com
|
15
|
+
//
|
16
|
+
|
17
|
+
//MARK: SWXMLHash
|
18
|
+
//
|
19
|
+
// SWXMLHash.swift
|
20
|
+
//
|
21
|
+
// Copyright (c) 2014 David Mohundro
|
22
|
+
//
|
23
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
24
|
+
// of this software and associated documentation files (the "Software"), to deal
|
25
|
+
// in the Software without restriction, including without limitation the rights
|
26
|
+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
27
|
+
// copies of the Software, and to permit persons to whom the Software is
|
28
|
+
// furnished to do so, subject to the following conditions:
|
29
|
+
//
|
30
|
+
// The above copyright notice and this permission notice shall be included in
|
31
|
+
// all copies or substantial portions of the Software.
|
32
|
+
//
|
33
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
34
|
+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
35
|
+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
36
|
+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
37
|
+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
38
|
+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
39
|
+
// THE SOFTWARE.
|
40
|
+
|
41
|
+
import Foundation
|
42
|
+
|
43
|
+
//MARK: Extensions
|
44
|
+
|
45
|
+
private extension String {
|
46
|
+
func trimAllWhitespacesAndSpecialCharacters() -> String {
|
47
|
+
let invalidCharacters = NSCharacterSet.alphanumericCharacterSet().invertedSet
|
48
|
+
let x = self.componentsSeparatedByCharactersInSet(invalidCharacters)
|
49
|
+
return x.joinWithSeparator("")
|
50
|
+
}
|
51
|
+
}
|
52
|
+
|
53
|
+
private func SwiftRepresentationForString(string: String, capitalizeFirstLetter: Bool = false, doNotShadow: String? = nil) -> String {
|
54
|
+
var str = string.trimAllWhitespacesAndSpecialCharacters()
|
55
|
+
if capitalizeFirstLetter {
|
56
|
+
str = String(str.uppercaseString.unicodeScalars.prefix(1) + str.unicodeScalars.suffix(str.unicodeScalars.count - 1))
|
57
|
+
}
|
58
|
+
if str == doNotShadow {
|
59
|
+
str = str + "_"
|
60
|
+
}
|
61
|
+
return str
|
62
|
+
}
|
63
|
+
|
64
|
+
//MARK: Parser
|
65
|
+
|
66
|
+
let rootElementName = "SWXMLHash_Root_Element"
|
67
|
+
|
68
|
+
/// Simple XML parser.
|
69
|
+
public class SWXMLHash {
|
70
|
+
/**
|
71
|
+
Method to parse XML passed in as a string.
|
72
|
+
|
73
|
+
- parameter xml: The XML to be parsed
|
74
|
+
|
75
|
+
- returns: An XMLIndexer instance that is used to look up elements in the XML
|
76
|
+
*/
|
77
|
+
class public func parse(xml: String) -> XMLIndexer {
|
78
|
+
return parse((xml as NSString).dataUsingEncoding(NSUTF8StringEncoding)!)
|
79
|
+
}
|
80
|
+
|
81
|
+
/**
|
82
|
+
Method to parse XML passed in as an NSData instance.
|
83
|
+
|
84
|
+
- parameter xml: The XML to be parsed
|
85
|
+
|
86
|
+
- returns: An XMLIndexer instance that is used to look up elements in the XML
|
87
|
+
*/
|
88
|
+
class public func parse(data: NSData) -> XMLIndexer {
|
89
|
+
let parser = XMLParser()
|
90
|
+
return parser.parse(data)
|
91
|
+
}
|
92
|
+
|
93
|
+
class public func lazy(xml: String) -> XMLIndexer {
|
94
|
+
return lazy((xml as NSString).dataUsingEncoding(NSUTF8StringEncoding)!)
|
95
|
+
}
|
96
|
+
|
97
|
+
class public func lazy(data: NSData) -> XMLIndexer {
|
98
|
+
let parser = LazyXMLParser()
|
99
|
+
return parser.parse(data)
|
100
|
+
}
|
101
|
+
}
|
102
|
+
|
103
|
+
struct Stack<T> {
|
104
|
+
var items = [T]()
|
105
|
+
mutating func push(item: T) {
|
106
|
+
items.append(item)
|
107
|
+
}
|
108
|
+
mutating func pop() -> T {
|
109
|
+
return items.removeLast()
|
110
|
+
}
|
111
|
+
mutating func removeAll() {
|
112
|
+
items.removeAll(keepCapacity: false)
|
113
|
+
}
|
114
|
+
func top() -> T {
|
115
|
+
return items[items.count - 1]
|
116
|
+
}
|
117
|
+
}
|
118
|
+
|
119
|
+
class LazyXMLParser: NSObject, NSXMLParserDelegate {
|
120
|
+
override init() {
|
121
|
+
super.init()
|
122
|
+
}
|
123
|
+
|
124
|
+
var root = XMLElement(name: rootElementName)
|
125
|
+
var parentStack = Stack<XMLElement>()
|
126
|
+
var elementStack = Stack<String>()
|
127
|
+
|
128
|
+
var data: NSData?
|
129
|
+
var ops: [IndexOp] = []
|
130
|
+
|
131
|
+
func parse(data: NSData) -> XMLIndexer {
|
132
|
+
self.data = data
|
133
|
+
return XMLIndexer(self)
|
134
|
+
}
|
135
|
+
|
136
|
+
func startParsing(ops: [IndexOp]) {
|
137
|
+
// clear any prior runs of parse... expected that this won't be necessary, but you never know
|
138
|
+
parentStack.removeAll()
|
139
|
+
root = XMLElement(name: rootElementName)
|
140
|
+
parentStack.push(root)
|
141
|
+
|
142
|
+
self.ops = ops
|
143
|
+
let parser = NSXMLParser(data: data!)
|
144
|
+
parser.delegate = self
|
145
|
+
parser.parse()
|
146
|
+
}
|
147
|
+
|
148
|
+
func parser(parser: NSXMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String: String]) {
|
149
|
+
|
150
|
+
elementStack.push(elementName)
|
151
|
+
|
152
|
+
if !onMatch() {
|
153
|
+
return
|
154
|
+
}
|
155
|
+
let currentNode = parentStack.top().addElement(elementName, withAttributes: attributeDict)
|
156
|
+
parentStack.push(currentNode)
|
157
|
+
}
|
158
|
+
|
159
|
+
func parser(parser: NSXMLParser, foundCharacters string: String) {
|
160
|
+
if !onMatch() {
|
161
|
+
return
|
162
|
+
}
|
163
|
+
|
164
|
+
let current = parentStack.top()
|
165
|
+
if current.text == nil {
|
166
|
+
current.text = ""
|
167
|
+
}
|
168
|
+
|
169
|
+
parentStack.top().text! += string
|
170
|
+
}
|
171
|
+
|
172
|
+
func parser(parser: NSXMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
|
173
|
+
let match = onMatch()
|
174
|
+
|
175
|
+
elementStack.pop()
|
176
|
+
|
177
|
+
if match {
|
178
|
+
parentStack.pop()
|
179
|
+
}
|
180
|
+
}
|
181
|
+
|
182
|
+
func onMatch() -> Bool {
|
183
|
+
// we typically want to compare against the elementStack to see if it matches ops, *but*
|
184
|
+
// if we're on the first element, we'll instead compare the other direction.
|
185
|
+
if elementStack.items.count > ops.count {
|
186
|
+
return elementStack.items.startsWith(ops.map { $0.key })
|
187
|
+
}
|
188
|
+
else {
|
189
|
+
return ops.map { $0.key }.startsWith(elementStack.items)
|
190
|
+
}
|
191
|
+
}
|
192
|
+
}
|
193
|
+
|
194
|
+
/// The implementation of NSXMLParserDelegate and where the parsing actually happens.
|
195
|
+
class XMLParser: NSObject, NSXMLParserDelegate {
|
196
|
+
override init() {
|
197
|
+
super.init()
|
198
|
+
}
|
199
|
+
|
200
|
+
var root = XMLElement(name: rootElementName)
|
201
|
+
var parentStack = Stack<XMLElement>()
|
202
|
+
|
203
|
+
func parse(data: NSData) -> XMLIndexer {
|
204
|
+
// clear any prior runs of parse... expected that this won't be necessary, but you never know
|
205
|
+
parentStack.removeAll()
|
206
|
+
|
207
|
+
parentStack.push(root)
|
208
|
+
|
209
|
+
let parser = NSXMLParser(data: data)
|
210
|
+
parser.delegate = self
|
211
|
+
parser.parse()
|
212
|
+
|
213
|
+
return XMLIndexer(root)
|
214
|
+
}
|
215
|
+
|
216
|
+
func parser(parser: NSXMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String: String]) {
|
217
|
+
|
218
|
+
let currentNode = parentStack.top().addElement(elementName, withAttributes: attributeDict)
|
219
|
+
parentStack.push(currentNode)
|
220
|
+
}
|
221
|
+
|
222
|
+
func parser(parser: NSXMLParser, foundCharacters string: String) {
|
223
|
+
let current = parentStack.top()
|
224
|
+
if current.text == nil {
|
225
|
+
current.text = ""
|
226
|
+
}
|
227
|
+
|
228
|
+
parentStack.top().text! += string
|
229
|
+
}
|
230
|
+
|
231
|
+
func parser(parser: NSXMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
|
232
|
+
parentStack.pop()
|
233
|
+
}
|
234
|
+
}
|
235
|
+
|
236
|
+
public class IndexOp {
|
237
|
+
var index: Int
|
238
|
+
let key: String
|
239
|
+
|
240
|
+
init(_ key: String) {
|
241
|
+
self.key = key
|
242
|
+
self.index = -1
|
243
|
+
}
|
244
|
+
|
245
|
+
func toString() -> String {
|
246
|
+
if index >= 0 {
|
247
|
+
return key + " " + index.description
|
248
|
+
}
|
249
|
+
|
250
|
+
return key
|
251
|
+
}
|
252
|
+
}
|
253
|
+
|
254
|
+
public class IndexOps {
|
255
|
+
var ops: [IndexOp] = []
|
256
|
+
|
257
|
+
let parser: LazyXMLParser
|
258
|
+
|
259
|
+
init(parser: LazyXMLParser) {
|
260
|
+
self.parser = parser
|
261
|
+
}
|
262
|
+
|
263
|
+
func findElements() -> XMLIndexer {
|
264
|
+
parser.startParsing(ops)
|
265
|
+
let indexer = XMLIndexer(parser.root)
|
266
|
+
var childIndex = indexer
|
267
|
+
for op in ops {
|
268
|
+
childIndex = childIndex[op.key]
|
269
|
+
if op.index >= 0 {
|
270
|
+
childIndex = childIndex[op.index]
|
271
|
+
}
|
272
|
+
}
|
273
|
+
ops.removeAll(keepCapacity: false)
|
274
|
+
return childIndex
|
275
|
+
}
|
276
|
+
|
277
|
+
func stringify() -> String {
|
278
|
+
var s = ""
|
279
|
+
for op in ops {
|
280
|
+
s += "[" + op.toString() + "]"
|
281
|
+
}
|
282
|
+
return s
|
283
|
+
}
|
284
|
+
}
|
285
|
+
|
286
|
+
/// Returned from SWXMLHash, allows easy element lookup into XML data.
|
287
|
+
public enum XMLIndexer: SequenceType {
|
288
|
+
case Element(XMLElement)
|
289
|
+
case List([XMLElement])
|
290
|
+
case Stream(IndexOps)
|
291
|
+
case Error(NSError)
|
292
|
+
|
293
|
+
/// The underlying XMLElement at the currently indexed level of XML.
|
294
|
+
public var element: XMLElement? {
|
295
|
+
get {
|
296
|
+
switch self {
|
297
|
+
case .Element(let elem):
|
298
|
+
return elem
|
299
|
+
case .Stream(let ops):
|
300
|
+
let list = ops.findElements()
|
301
|
+
return list.element
|
302
|
+
default:
|
303
|
+
return nil
|
304
|
+
}
|
305
|
+
}
|
306
|
+
}
|
307
|
+
|
308
|
+
/// All elements at the currently indexed level
|
309
|
+
public var all: [XMLIndexer] {
|
310
|
+
get {
|
311
|
+
switch self {
|
312
|
+
case .List(let list):
|
313
|
+
var xmlList = [XMLIndexer]()
|
314
|
+
for elem in list {
|
315
|
+
xmlList.append(XMLIndexer(elem))
|
316
|
+
}
|
317
|
+
return xmlList
|
318
|
+
case .Element(let elem):
|
319
|
+
return [XMLIndexer(elem)]
|
320
|
+
case .Stream(let ops):
|
321
|
+
let list = ops.findElements()
|
322
|
+
return list.all
|
323
|
+
default:
|
324
|
+
return []
|
325
|
+
}
|
326
|
+
}
|
327
|
+
}
|
328
|
+
|
329
|
+
/// All child elements from the currently indexed level
|
330
|
+
public var children: [XMLIndexer] {
|
331
|
+
get {
|
332
|
+
var list = [XMLIndexer]()
|
333
|
+
for elem in all.map({ $0.element! }) {
|
334
|
+
for elem in elem.children {
|
335
|
+
list.append(XMLIndexer(elem))
|
336
|
+
}
|
337
|
+
}
|
338
|
+
return list
|
339
|
+
}
|
340
|
+
}
|
341
|
+
|
342
|
+
/**
|
343
|
+
Allows for element lookup by matching attribute values.
|
344
|
+
|
345
|
+
- parameter attr: should the name of the attribute to match on
|
346
|
+
- parameter _: should be the value of the attribute to match on
|
347
|
+
|
348
|
+
- returns: instance of XMLIndexer
|
349
|
+
*/
|
350
|
+
public func withAttr(attr: String, _ value: String) -> XMLIndexer {
|
351
|
+
let attrUserInfo = [NSLocalizedDescriptionKey: "XML Attribute Error: Missing attribute [\"\(attr)\"]"]
|
352
|
+
let valueUserInfo = [NSLocalizedDescriptionKey: "XML Attribute Error: Missing attribute [\"\(attr)\"] with value [\"\(value)\"]"]
|
353
|
+
switch self {
|
354
|
+
case .Stream(let opStream):
|
355
|
+
opStream.stringify()
|
356
|
+
let match = opStream.findElements()
|
357
|
+
return match.withAttr(attr, value)
|
358
|
+
case .List(let list):
|
359
|
+
if let elem = list.filter({ $0.attributes[attr] == value }).first {
|
360
|
+
return .Element(elem)
|
361
|
+
}
|
362
|
+
return .Error(NSError(domain: "SWXMLDomain", code: 1000, userInfo: valueUserInfo))
|
363
|
+
case .Element(let elem):
|
364
|
+
if let attr = elem.attributes[attr] {
|
365
|
+
if attr == value {
|
366
|
+
return .Element(elem)
|
367
|
+
}
|
368
|
+
return .Error(NSError(domain: "SWXMLDomain", code: 1000, userInfo: valueUserInfo))
|
369
|
+
}
|
370
|
+
return .Error(NSError(domain: "SWXMLDomain", code: 1000, userInfo: attrUserInfo))
|
371
|
+
default:
|
372
|
+
return .Error(NSError(domain: "SWXMLDomain", code: 1000, userInfo: attrUserInfo))
|
373
|
+
}
|
374
|
+
}
|
375
|
+
|
376
|
+
/**
|
377
|
+
Initializes the XMLIndexer
|
378
|
+
|
379
|
+
- parameter _: should be an instance of XMLElement, but supports other values for error handling
|
380
|
+
|
381
|
+
- returns: instance of XMLIndexer
|
382
|
+
*/
|
383
|
+
public init(_ rawObject: AnyObject) {
|
384
|
+
switch rawObject {
|
385
|
+
case let value as XMLElement:
|
386
|
+
self = .Element(value)
|
387
|
+
case let value as LazyXMLParser:
|
388
|
+
self = .Stream(IndexOps(parser: value))
|
389
|
+
default:
|
390
|
+
self = .Error(NSError(domain: "SWXMLDomain", code: 1000, userInfo: nil))
|
391
|
+
}
|
392
|
+
}
|
393
|
+
|
394
|
+
/**
|
395
|
+
Find an XML element at the current level by element name
|
396
|
+
|
397
|
+
- parameter key: The element name to index by
|
398
|
+
|
399
|
+
- returns: instance of XMLIndexer to match the element (or elements) found by key
|
400
|
+
*/
|
401
|
+
public subscript(key: String) -> XMLIndexer {
|
402
|
+
get {
|
403
|
+
let userInfo = [NSLocalizedDescriptionKey: "XML Element Error: Incorrect key [\"\(key)\"]"]
|
404
|
+
switch self {
|
405
|
+
case .Stream(let opStream):
|
406
|
+
let op = IndexOp(key)
|
407
|
+
opStream.ops.append(op)
|
408
|
+
return .Stream(opStream)
|
409
|
+
case .Element(let elem):
|
410
|
+
let match = elem.children.filter({ $0.name == key })
|
411
|
+
if match.count > 0 {
|
412
|
+
if match.count == 1 {
|
413
|
+
return .Element(match[0])
|
414
|
+
}
|
415
|
+
else {
|
416
|
+
return .List(match)
|
417
|
+
}
|
418
|
+
}
|
419
|
+
return .Error(NSError(domain: "SWXMLDomain", code: 1000, userInfo: userInfo))
|
420
|
+
default:
|
421
|
+
return .Error(NSError(domain: "SWXMLDomain", code: 1000, userInfo: userInfo))
|
422
|
+
}
|
423
|
+
}
|
424
|
+
}
|
425
|
+
|
426
|
+
/**
|
427
|
+
Find an XML element by index within a list of XML Elements at the current level
|
428
|
+
|
429
|
+
- parameter index: The 0-based index to index by
|
430
|
+
|
431
|
+
- returns: instance of XMLIndexer to match the element (or elements) found by key
|
432
|
+
*/
|
433
|
+
public subscript(index: Int) -> XMLIndexer {
|
434
|
+
get {
|
435
|
+
let userInfo = [NSLocalizedDescriptionKey: "XML Element Error: Incorrect index [\"\(index)\"]"]
|
436
|
+
switch self {
|
437
|
+
case .Stream(let opStream):
|
438
|
+
opStream.ops[opStream.ops.count - 1].index = index
|
439
|
+
return .Stream(opStream)
|
440
|
+
case .List(let list):
|
441
|
+
if index <= list.count {
|
442
|
+
return .Element(list[index])
|
443
|
+
}
|
444
|
+
return .Error(NSError(domain: "SWXMLDomain", code: 1000, userInfo: userInfo))
|
445
|
+
case .Element(let elem):
|
446
|
+
if index == 0 {
|
447
|
+
return .Element(elem)
|
448
|
+
}
|
449
|
+
else {
|
450
|
+
return .Error(NSError(domain: "SWXMLDomain", code: 1000, userInfo: userInfo))
|
451
|
+
}
|
452
|
+
default:
|
453
|
+
return .Error(NSError(domain: "SWXMLDomain", code: 1000, userInfo: userInfo))
|
454
|
+
}
|
455
|
+
}
|
456
|
+
}
|
457
|
+
|
458
|
+
typealias GeneratorType = XMLIndexer
|
459
|
+
|
460
|
+
public func generate() -> IndexingGenerator<[XMLIndexer]> {
|
461
|
+
return all.generate()
|
462
|
+
}
|
463
|
+
}
|
464
|
+
|
465
|
+
/// XMLIndexer extensions
|
466
|
+
extension XMLIndexer: BooleanType {
|
467
|
+
/// True if a valid XMLIndexer, false if an error type
|
468
|
+
public var boolValue: Bool {
|
469
|
+
get {
|
470
|
+
switch self {
|
471
|
+
case .Error:
|
472
|
+
return false
|
473
|
+
default:
|
474
|
+
return true
|
475
|
+
}
|
476
|
+
}
|
477
|
+
}
|
478
|
+
}
|
479
|
+
|
480
|
+
extension XMLIndexer: CustomStringConvertible {
|
481
|
+
public var description: String {
|
482
|
+
get {
|
483
|
+
switch self {
|
484
|
+
case .List(let list):
|
485
|
+
return list.map { $0.description }.joinWithSeparator("\n")
|
486
|
+
case .Element(let elem):
|
487
|
+
if elem.name == rootElementName {
|
488
|
+
return elem.children.map { $0.description }.joinWithSeparator("\n")
|
489
|
+
}
|
490
|
+
|
491
|
+
return elem.description
|
492
|
+
default:
|
493
|
+
return ""
|
494
|
+
}
|
495
|
+
}
|
496
|
+
}
|
497
|
+
}
|
498
|
+
|
499
|
+
/// Models an XML element, including name, text and attributes
|
500
|
+
public class XMLElement {
|
501
|
+
/// The name of the element
|
502
|
+
public let name: String
|
503
|
+
/// The inner text of the element, if it exists
|
504
|
+
public var text: String?
|
505
|
+
/// The attributes of the element
|
506
|
+
public var attributes = [String:String]()
|
507
|
+
|
508
|
+
var children = [XMLElement]()
|
509
|
+
var count: Int = 0
|
510
|
+
var index: Int
|
511
|
+
|
512
|
+
/**
|
513
|
+
Initialize an XMLElement instance
|
514
|
+
|
515
|
+
- parameter name: The name of the element to be initialized
|
516
|
+
|
517
|
+
- returns: a new instance of XMLElement
|
518
|
+
*/
|
519
|
+
init(name: String, index: Int = 0) {
|
520
|
+
self.name = name
|
521
|
+
self.index = index
|
522
|
+
}
|
523
|
+
|
524
|
+
/**
|
525
|
+
Adds a new XMLElement underneath this instance of XMLElement
|
526
|
+
|
527
|
+
- parameter name: The name of the new element to be added
|
528
|
+
- parameter withAttributes: The attributes dictionary for the element being added
|
529
|
+
|
530
|
+
- returns: The XMLElement that has now been added
|
531
|
+
*/
|
532
|
+
func addElement(name: String, withAttributes attributes: NSDictionary) -> XMLElement {
|
533
|
+
let element = XMLElement(name: name, index: count)
|
534
|
+
count++
|
535
|
+
|
536
|
+
children.append(element)
|
537
|
+
|
538
|
+
for (keyAny,valueAny) in attributes {
|
539
|
+
let key = keyAny as! String
|
540
|
+
let value = valueAny as! String
|
541
|
+
element.attributes[key] = value
|
542
|
+
}
|
543
|
+
|
544
|
+
return element
|
545
|
+
}
|
546
|
+
}
|
547
|
+
|
548
|
+
extension XMLElement: CustomStringConvertible {
|
549
|
+
public var description:String {
|
550
|
+
get {
|
551
|
+
var attributesStringList = [String]()
|
552
|
+
if !attributes.isEmpty {
|
553
|
+
for (key, val) in attributes {
|
554
|
+
attributesStringList.append("\(key)=\"\(val)\"")
|
555
|
+
}
|
556
|
+
}
|
557
|
+
|
558
|
+
var attributesString = attributesStringList.joinWithSeparator(" ")
|
559
|
+
if (!attributesString.isEmpty) {
|
560
|
+
attributesString = " " + attributesString
|
561
|
+
}
|
562
|
+
|
563
|
+
if children.count > 0 {
|
564
|
+
var xmlReturn = [String]()
|
565
|
+
xmlReturn.append("<\(name)\(attributesString)>")
|
566
|
+
for child in children {
|
567
|
+
xmlReturn.append(child.description)
|
568
|
+
}
|
569
|
+
xmlReturn.append("</\(name)>")
|
570
|
+
return xmlReturn.joinWithSeparator("\n")
|
571
|
+
}
|
572
|
+
|
573
|
+
if text != nil {
|
574
|
+
return "<\(name)\(attributesString)>\(text!)</\(name)>"
|
575
|
+
} else {
|
576
|
+
return "<\(name)\(attributesString)/>"
|
577
|
+
}
|
578
|
+
}
|
579
|
+
}
|
580
|
+
}
|
581
|
+
|
582
|
+
//MARK: - Natalie
|
583
|
+
|
584
|
+
//MARK: Objects
|
585
|
+
enum OS: String, CustomStringConvertible {
|
586
|
+
case iOS = "iOS"
|
587
|
+
case OSX = "OSX"
|
588
|
+
|
589
|
+
static let allValues = [iOS, OSX]
|
590
|
+
|
591
|
+
enum Runtime: String {
|
592
|
+
case iOSCocoaTouch = "iOS.CocoaTouch"
|
593
|
+
case MacOSXCocoa = "MacOSX.Cocoa"
|
594
|
+
|
595
|
+
init(os: OS) {
|
596
|
+
switch os {
|
597
|
+
case iOS:
|
598
|
+
self = .iOSCocoaTouch
|
599
|
+
case OSX:
|
600
|
+
self = .MacOSXCocoa
|
601
|
+
}
|
602
|
+
}
|
603
|
+
}
|
604
|
+
|
605
|
+
enum Framework: String {
|
606
|
+
case UIKit = "UIKit"
|
607
|
+
case Cocoa = "Cocoa"
|
608
|
+
|
609
|
+
init(os: OS) {
|
610
|
+
switch os {
|
611
|
+
case iOS:
|
612
|
+
self = .UIKit
|
613
|
+
case OSX:
|
614
|
+
self = .Cocoa
|
615
|
+
}
|
616
|
+
}
|
617
|
+
}
|
618
|
+
|
619
|
+
init(targetRuntime: String) {
|
620
|
+
switch (targetRuntime) {
|
621
|
+
case Runtime.iOSCocoaTouch.rawValue:
|
622
|
+
self = .iOS
|
623
|
+
case Runtime.MacOSXCocoa.rawValue:
|
624
|
+
self = .OSX
|
625
|
+
case "iOS.CocoaTouch.iPad":
|
626
|
+
self = iOS
|
627
|
+
default:
|
628
|
+
fatalError("Unsupported")
|
629
|
+
}
|
630
|
+
}
|
631
|
+
|
632
|
+
var description: String {
|
633
|
+
return self.rawValue
|
634
|
+
}
|
635
|
+
|
636
|
+
var framework: String {
|
637
|
+
return Framework(os: self).rawValue
|
638
|
+
}
|
639
|
+
|
640
|
+
var targetRuntime: String {
|
641
|
+
return Runtime(os: self).rawValue
|
642
|
+
}
|
643
|
+
|
644
|
+
var storyboardType: String {
|
645
|
+
switch self {
|
646
|
+
case iOS:
|
647
|
+
return "UIStoryboard"
|
648
|
+
case OSX:
|
649
|
+
return "NSStoryboard"
|
650
|
+
}
|
651
|
+
}
|
652
|
+
|
653
|
+
var storyboardSegueType: String {
|
654
|
+
switch self {
|
655
|
+
case iOS:
|
656
|
+
return "UIStoryboardSegue"
|
657
|
+
case OSX:
|
658
|
+
return "NSStoryboardSegue"
|
659
|
+
}
|
660
|
+
}
|
661
|
+
|
662
|
+
var storyboardControllerTypes: [String] {
|
663
|
+
switch self {
|
664
|
+
case iOS:
|
665
|
+
return ["UIViewController"]
|
666
|
+
case OSX:
|
667
|
+
return ["NSViewController", "NSWindowController"]
|
668
|
+
}
|
669
|
+
}
|
670
|
+
|
671
|
+
var storyboardControllerReturnType: String {
|
672
|
+
switch self {
|
673
|
+
case iOS:
|
674
|
+
return "UIViewController"
|
675
|
+
case OSX:
|
676
|
+
return "AnyObject" // NSViewController or NSWindowController
|
677
|
+
}
|
678
|
+
}
|
679
|
+
|
680
|
+
var storyboardControllerSignatureType: String {
|
681
|
+
switch self {
|
682
|
+
case iOS:
|
683
|
+
return "ViewController"
|
684
|
+
case OSX:
|
685
|
+
return "Controller" // NSViewController or NSWindowController
|
686
|
+
}
|
687
|
+
}
|
688
|
+
|
689
|
+
var storyboardInstantiationInfo: [(String /* Signature type */, String /* Return type */)] {
|
690
|
+
switch self {
|
691
|
+
case iOS:
|
692
|
+
return [("ViewController", "UIViewController")]
|
693
|
+
case OSX:
|
694
|
+
return [("Controller", "NSWindowController"), ("Controller", "NSViewController")]
|
695
|
+
}
|
696
|
+
}
|
697
|
+
|
698
|
+
var viewType: String {
|
699
|
+
switch self {
|
700
|
+
case iOS:
|
701
|
+
return "UIView"
|
702
|
+
case OSX:
|
703
|
+
return "NSView"
|
704
|
+
}
|
705
|
+
}
|
706
|
+
|
707
|
+
var resuableViews: [String]? {
|
708
|
+
switch self {
|
709
|
+
case iOS:
|
710
|
+
return ["UICollectionReusableView", "UITableViewCell"]
|
711
|
+
case OSX:
|
712
|
+
return nil
|
713
|
+
}
|
714
|
+
}
|
715
|
+
|
716
|
+
func controllerTypeForElementName(name: String) -> String? {
|
717
|
+
switch self {
|
718
|
+
case iOS:
|
719
|
+
switch name {
|
720
|
+
case "viewController":
|
721
|
+
return "UIViewController"
|
722
|
+
case "navigationController":
|
723
|
+
return "UINavigationController"
|
724
|
+
case "tableViewController":
|
725
|
+
return "UITableViewController"
|
726
|
+
case "tabBarController":
|
727
|
+
return "UITabBarController"
|
728
|
+
case "splitViewController":
|
729
|
+
return "UISplitViewController"
|
730
|
+
case "pageViewController":
|
731
|
+
return "UIPageViewController"
|
732
|
+
case "collectionViewController":
|
733
|
+
return "UICollectionViewController"
|
734
|
+
case "exit", "viewControllerPlaceholder":
|
735
|
+
return nil
|
736
|
+
default:
|
737
|
+
assertionFailure("Unknown controller element: \(name)")
|
738
|
+
return nil
|
739
|
+
}
|
740
|
+
case OSX:
|
741
|
+
switch name {
|
742
|
+
case "viewController":
|
743
|
+
return "NSViewController"
|
744
|
+
case "windowController":
|
745
|
+
return "NSWindowController"
|
746
|
+
case "pagecontroller":
|
747
|
+
return "NSPageController"
|
748
|
+
case "tabViewController":
|
749
|
+
return "NSTabViewController"
|
750
|
+
case "splitViewController":
|
751
|
+
return "NSSplitViewController"
|
752
|
+
case "exit", "viewControllerPlaceholder":
|
753
|
+
return nil
|
754
|
+
default:
|
755
|
+
assertionFailure("Unknown controller element: \(name)")
|
756
|
+
return nil
|
757
|
+
}
|
758
|
+
}
|
759
|
+
}
|
760
|
+
|
761
|
+
}
|
762
|
+
|
763
|
+
class XMLObject {
|
764
|
+
|
765
|
+
let xml: XMLIndexer
|
766
|
+
let name: String
|
767
|
+
|
768
|
+
init(xml: XMLIndexer) {
|
769
|
+
self.xml = xml
|
770
|
+
self.name = xml.element!.name
|
771
|
+
}
|
772
|
+
|
773
|
+
func searchAll(attributeKey: String, attributeValue: String? = nil) -> [XMLIndexer]? {
|
774
|
+
return searchAll(self.xml, attributeKey: attributeKey, attributeValue: attributeValue)
|
775
|
+
}
|
776
|
+
|
777
|
+
func searchAll(root: XMLIndexer, attributeKey: String, attributeValue: String? = nil) -> [XMLIndexer]? {
|
778
|
+
var result = Array<XMLIndexer>()
|
779
|
+
for child in root.children {
|
780
|
+
|
781
|
+
for childAtLevel in child.all {
|
782
|
+
if let attributeValue = attributeValue {
|
783
|
+
if let element = childAtLevel.element where element.attributes[attributeKey] == attributeValue {
|
784
|
+
result += [childAtLevel]
|
785
|
+
}
|
786
|
+
} else if let element = childAtLevel.element where element.attributes[attributeKey] != nil {
|
787
|
+
result += [childAtLevel]
|
788
|
+
}
|
789
|
+
|
790
|
+
if let found = searchAll(childAtLevel, attributeKey: attributeKey, attributeValue: attributeValue) {
|
791
|
+
result += found
|
792
|
+
}
|
793
|
+
}
|
794
|
+
}
|
795
|
+
return result.count > 0 ? result : nil
|
796
|
+
}
|
797
|
+
|
798
|
+
func searchNamed(name: String) -> [XMLIndexer]? {
|
799
|
+
return self.searchNamed(self.xml, name: name)
|
800
|
+
}
|
801
|
+
|
802
|
+
func searchNamed(root: XMLIndexer, name: String) -> [XMLIndexer]? {
|
803
|
+
var result = Array<XMLIndexer>()
|
804
|
+
for child in root.children {
|
805
|
+
|
806
|
+
for childAtLevel in child.all {
|
807
|
+
if let elementName = childAtLevel.element?.name where elementName == name {
|
808
|
+
result += [child]
|
809
|
+
}
|
810
|
+
if let found = searchNamed(childAtLevel, name: name) {
|
811
|
+
result += found
|
812
|
+
}
|
813
|
+
}
|
814
|
+
}
|
815
|
+
return result.count > 0 ? result : nil
|
816
|
+
}
|
817
|
+
|
818
|
+
func searchById(id: String) -> XMLIndexer? {
|
819
|
+
return searchAll("id", attributeValue: id)?.first
|
820
|
+
}
|
821
|
+
}
|
822
|
+
|
823
|
+
class Scene: XMLObject {
|
824
|
+
|
825
|
+
lazy var viewController: ViewController? = {
|
826
|
+
if let vcs = self.searchAll("sceneMemberID", attributeValue: "viewController"), vc = vcs.first {
|
827
|
+
return ViewController(xml: vc)
|
828
|
+
}
|
829
|
+
return nil
|
830
|
+
}()
|
831
|
+
|
832
|
+
lazy var segues: [Segue]? = {
|
833
|
+
return self.searchNamed("segue")?.map { Segue(xml: $0) }
|
834
|
+
}()
|
835
|
+
|
836
|
+
lazy var customModule: String? = self.viewController?.customModule
|
837
|
+
lazy var customModuleProvider: String? = self.viewController?.customModuleProvider
|
838
|
+
}
|
839
|
+
|
840
|
+
class ViewController: XMLObject {
|
841
|
+
|
842
|
+
lazy var customClass: String? = self.xml.element?.attributes["customClass"]
|
843
|
+
lazy var customModuleProvider: String? = self.xml.element?.attributes["customModuleProvider"]
|
844
|
+
lazy var storyboardIdentifier: String? = self.xml.element?.attributes["storyboardIdentifier"]
|
845
|
+
lazy var customModule: String? = self.xml.element?.attributes["customModule"]
|
846
|
+
|
847
|
+
lazy var reusables: [Reusable]? = {
|
848
|
+
if let reusables = self.searchAll(self.xml, attributeKey: "reuseIdentifier"){
|
849
|
+
return reusables.map { Reusable(xml: $0) }
|
850
|
+
}
|
851
|
+
return nil
|
852
|
+
}()
|
853
|
+
}
|
854
|
+
|
855
|
+
class Segue: XMLObject {
|
856
|
+
let kind: String
|
857
|
+
let identifier: String?
|
858
|
+
lazy var destination: String? = self.xml.element?.attributes["destination"]
|
859
|
+
lazy var relationship: String? = self.xml.element?.attributes["relationship"]
|
860
|
+
|
861
|
+
override init(xml: XMLIndexer) {
|
862
|
+
self.kind = xml.element!.attributes["kind"]!
|
863
|
+
if let id = xml.element?.attributes["identifier"] where id.characters.count > 0 {self.identifier = id}
|
864
|
+
else {self.identifier = nil}
|
865
|
+
super.init(xml: xml)
|
866
|
+
}
|
867
|
+
|
868
|
+
}
|
869
|
+
|
870
|
+
class Reusable: XMLObject {
|
871
|
+
|
872
|
+
let kind: String
|
873
|
+
lazy var reuseIdentifier: String? = self.xml.element?.attributes["reuseIdentifier"]
|
874
|
+
lazy var customClass: String? = self.xml.element?.attributes["customClass"]
|
875
|
+
|
876
|
+
|
877
|
+
override init(xml: XMLIndexer) {
|
878
|
+
kind = xml.element!.name
|
879
|
+
super.init(xml: xml)
|
880
|
+
}
|
881
|
+
}
|
882
|
+
|
883
|
+
class Storyboard: XMLObject {
|
884
|
+
|
885
|
+
let version: String
|
886
|
+
lazy var os:OS = {
|
887
|
+
guard let targetRuntime = self.xml["document"].element?.attributes["targetRuntime"] else {
|
888
|
+
return OS.iOS
|
889
|
+
}
|
890
|
+
|
891
|
+
return OS(targetRuntime: targetRuntime)
|
892
|
+
}()
|
893
|
+
|
894
|
+
lazy var initialViewControllerClass: String? = {
|
895
|
+
if let initialViewControllerId = self.xml["document"].element?.attributes["initialViewController"],
|
896
|
+
let xmlVC = self.searchById(initialViewControllerId)
|
897
|
+
{
|
898
|
+
let vc = ViewController(xml: xmlVC)
|
899
|
+
if let customClassName = vc.customClass {
|
900
|
+
return customClassName
|
901
|
+
}
|
902
|
+
|
903
|
+
if let controllerType = self.os.controllerTypeForElementName(vc.name) {
|
904
|
+
return controllerType
|
905
|
+
}
|
906
|
+
}
|
907
|
+
return nil
|
908
|
+
}()
|
909
|
+
|
910
|
+
lazy var scenes: [Scene] = {
|
911
|
+
guard let scenes = self.searchAll(self.xml, attributeKey: "sceneID") else {
|
912
|
+
return []
|
913
|
+
}
|
914
|
+
|
915
|
+
return scenes.map { Scene(xml: $0) }
|
916
|
+
}()
|
917
|
+
|
918
|
+
lazy var customModules: [String] = self.scenes.filter{ $0.customModule != nil && $0.customModuleProvider == nil }.map{ $0.customModule! }
|
919
|
+
|
920
|
+
override init(xml: XMLIndexer) {
|
921
|
+
self.version = xml["document"].element!.attributes["version"]!
|
922
|
+
super.init(xml: xml)
|
923
|
+
}
|
924
|
+
|
925
|
+
func processStoryboard(storyboardName: String, os: OS) {
|
926
|
+
|
927
|
+
}
|
928
|
+
|
929
|
+
func processViewControllers() {
|
930
|
+
struct SegueInfo {
|
931
|
+
var identifier: String
|
932
|
+
var destination: String
|
933
|
+
var viaNav: Bool
|
934
|
+
}
|
935
|
+
|
936
|
+
var viewControllers = [String:[SegueInfo]]()
|
937
|
+
|
938
|
+
for scene in self.scenes {
|
939
|
+
if let viewController = scene.viewController {
|
940
|
+
if let customClass = viewController.customClass {
|
941
|
+
if viewControllers[customClass] == nil {
|
942
|
+
viewControllers[customClass] = []
|
943
|
+
}
|
944
|
+
|
945
|
+
if let segues = scene.segues?.filter({ return $0.identifier != nil }) where segues.count > 0 {
|
946
|
+
for segue in segues {
|
947
|
+
|
948
|
+
guard let destination = segue.destination,
|
949
|
+
let destinationXML = searchById(destination),
|
950
|
+
let destinationElement = destinationXML.element,
|
951
|
+
let destinationClass = (destinationElement.attributes["customClass"] ?? os.controllerTypeForElementName(destinationElement.name))
|
952
|
+
else {
|
953
|
+
continue
|
954
|
+
}
|
955
|
+
|
956
|
+
var finalDestination = destinationClass
|
957
|
+
var viaNav = false
|
958
|
+
if destinationClass == "UINavigationController" {
|
959
|
+
let scene = Scene(xml: destinationXML)
|
960
|
+
let segue = scene.segues?.first
|
961
|
+
guard let destination = segue?.destination,
|
962
|
+
let destinationXML = searchById(destination),
|
963
|
+
let destinationElement = destinationXML.element,
|
964
|
+
let destinationClass = (destinationElement.attributes["customClass"] ?? os.controllerTypeForElementName(destinationElement.name))
|
965
|
+
where segue?.relationship == "rootViewController"
|
966
|
+
else {
|
967
|
+
continue
|
968
|
+
}
|
969
|
+
finalDestination = destinationClass
|
970
|
+
viaNav = true
|
971
|
+
}
|
972
|
+
let info = SegueInfo(identifier: segue.identifier!, destination: finalDestination, viaNav: viaNav)
|
973
|
+
viewControllers[customClass]?.append(info)
|
974
|
+
}
|
975
|
+
}
|
976
|
+
}
|
977
|
+
}
|
978
|
+
}
|
979
|
+
|
980
|
+
viewControllers.filter({ $1.count > 0 }).forEach { customClass, segues in
|
981
|
+
print("")
|
982
|
+
print("// MARK: - \(customClass)")
|
983
|
+
print("")
|
984
|
+
print("extension \(customClass): SegueHandler {")
|
985
|
+
print(" enum SegueIdentifier: String {")
|
986
|
+
for segue in segues {
|
987
|
+
print(" case \(SwiftRepresentationForString(segue.identifier))")
|
988
|
+
}
|
989
|
+
print(" }")
|
990
|
+
print("")
|
991
|
+
print(" enum ToSegueDestination: SegueDestination {")
|
992
|
+
for segue in segues {
|
993
|
+
print(" case \(SwiftRepresentationForString(segue.identifier))(\(SwiftRepresentationForString(segue.destination)))")
|
994
|
+
}
|
995
|
+
print("")
|
996
|
+
print(" init?(identifier: SegueIdentifier, destination: UIViewController) {")
|
997
|
+
print(" switch identifier {")
|
998
|
+
for segue in segues {
|
999
|
+
print(" case .\(SwiftRepresentationForString(segue.identifier)):")
|
1000
|
+
var (to, from) = ("vc", "destination")
|
1001
|
+
if segue.viaNav {
|
1002
|
+
to = "nav"
|
1003
|
+
print(" guard let \(to) = \(from) as? UINavigationController else { return nil }")
|
1004
|
+
from = "\(to).viewControllers.first"
|
1005
|
+
to = "vc"
|
1006
|
+
}
|
1007
|
+
print(" guard let \(to) = \(from) as? \(SwiftRepresentationForString(segue.destination)) else { return nil }")
|
1008
|
+
print(" self = \(SwiftRepresentationForString(segue.identifier))(\(to))")
|
1009
|
+
}
|
1010
|
+
print(" }")
|
1011
|
+
print(" }")
|
1012
|
+
print(" }")
|
1013
|
+
print("}")
|
1014
|
+
}
|
1015
|
+
}
|
1016
|
+
}
|
1017
|
+
|
1018
|
+
class StoryboardFile {
|
1019
|
+
let data: NSData
|
1020
|
+
let storyboardName: String
|
1021
|
+
let storyboard: Storyboard
|
1022
|
+
|
1023
|
+
init(filePath: String) {
|
1024
|
+
self.data = NSData(contentsOfFile: filePath)!
|
1025
|
+
self.storyboardName = ((filePath as NSString).lastPathComponent as NSString).stringByDeletingPathExtension
|
1026
|
+
self.storyboard = Storyboard(xml:SWXMLHash.parse(self.data))
|
1027
|
+
}
|
1028
|
+
}
|
1029
|
+
|
1030
|
+
|
1031
|
+
//MARK: Functions
|
1032
|
+
|
1033
|
+
func findStoryboards(rootPath: String, suffix: String) -> [String]? {
|
1034
|
+
var result = Array<String>()
|
1035
|
+
let fm = NSFileManager.defaultManager()
|
1036
|
+
if let paths = fm.subpathsAtPath(rootPath) {
|
1037
|
+
let storyboardPaths = paths.filter({ return $0.hasSuffix(suffix)})
|
1038
|
+
// result = storyboardPaths
|
1039
|
+
for p in storyboardPaths {
|
1040
|
+
result.append((rootPath as NSString).stringByAppendingPathComponent(p))
|
1041
|
+
}
|
1042
|
+
}
|
1043
|
+
return result.count > 0 ? result : nil
|
1044
|
+
}
|
1045
|
+
|
1046
|
+
func processStoryboards(storyboards: [StoryboardFile], os: OS) {
|
1047
|
+
|
1048
|
+
print("//")
|
1049
|
+
print("// Generated Code. Do not modify.")
|
1050
|
+
print("//")
|
1051
|
+
print("// Generated by a fork of Natalie:")
|
1052
|
+
print("// - https://github.com/ianterrell/Natalie")
|
1053
|
+
print("// - https://github.com/krzyzanowskim/Natalie")
|
1054
|
+
print("// - http://blog.krzyzanowskim.com")
|
1055
|
+
print("//")
|
1056
|
+
print("")
|
1057
|
+
print("import \(os.framework)")
|
1058
|
+
print("import WillowTreeSegueHandler")
|
1059
|
+
let modules = storyboards.flatMap{ $0.storyboard.customModules }
|
1060
|
+
for module in Set<String>(modules) {
|
1061
|
+
print("import \(module)")
|
1062
|
+
}
|
1063
|
+
|
1064
|
+
for file in storyboards {
|
1065
|
+
file.storyboard.processViewControllers()
|
1066
|
+
}
|
1067
|
+
|
1068
|
+
}
|
1069
|
+
|
1070
|
+
//MARK: MAIN()
|
1071
|
+
|
1072
|
+
if Process.arguments.count == 1 {
|
1073
|
+
print("Invalid usage. Missing path to storyboard.")
|
1074
|
+
exit(1)
|
1075
|
+
}
|
1076
|
+
|
1077
|
+
let argument = Process.arguments[1]
|
1078
|
+
var filePaths:[String] = []
|
1079
|
+
let storyboardSuffix = ".storyboard"
|
1080
|
+
if argument.hasSuffix(storyboardSuffix) {
|
1081
|
+
filePaths = [argument]
|
1082
|
+
} else if let s = findStoryboards(argument, suffix: storyboardSuffix) {
|
1083
|
+
filePaths = s
|
1084
|
+
}
|
1085
|
+
let storyboardFiles = filePaths.map { StoryboardFile(filePath: $0) }
|
1086
|
+
|
1087
|
+
for os in OS.allValues {
|
1088
|
+
let storyboardsForOS = storyboardFiles.filter({ $0.storyboard.os == os })
|
1089
|
+
if !storyboardsForOS.isEmpty {
|
1090
|
+
|
1091
|
+
if storyboardsForOS.count != storyboardFiles.count {
|
1092
|
+
print("#if os(\(os.rawValue))")
|
1093
|
+
}
|
1094
|
+
|
1095
|
+
processStoryboards(storyboardsForOS, os: os)
|
1096
|
+
|
1097
|
+
if storyboardsForOS.count != storyboardFiles.count {
|
1098
|
+
print("#endif")
|
1099
|
+
}
|
1100
|
+
}
|
1101
|
+
}
|
1102
|
+
|
1103
|
+
exit(0)
|
1104
|
+
SWIFTDOC
|
1105
|
+
|
1106
|
+
t.close
|
1107
|
+
puts `/usr/bin/env xcrun -sdk macosx swift #{t.path} #{ARGV[0]}`
|