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.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +30 -0
  3. data/Example/.ruby-version +1 -0
  4. data/Example/Gemfile +5 -0
  5. data/Example/Gemfile.lock +71 -0
  6. data/Example/Podfile +6 -0
  7. data/Example/Podfile.lock +14 -0
  8. data/Example/Pods/Headers/Private/WillowTreeSegueHandler/SegueHandler.h +19 -0
  9. data/Example/Pods/Local Podspecs/WillowTreeSegueHandler.podspec.json +25 -0
  10. data/Example/Pods/Manifest.lock +14 -0
  11. data/Example/Pods/Pods.xcodeproj/project.pbxproj +523 -0
  12. data/Example/Pods/Pods.xcodeproj/xcshareddata/xcschemes/WillowTreeSegueHandler.xcscheme +60 -0
  13. data/Example/Pods/Target Support Files/Pods/Info.plist +26 -0
  14. data/Example/Pods/Target Support Files/Pods/Pods-acknowledgements.markdown +26 -0
  15. data/Example/Pods/Target Support Files/Pods/Pods-acknowledgements.plist +56 -0
  16. data/Example/Pods/Target Support Files/Pods/Pods-dummy.m +5 -0
  17. data/Example/Pods/Target Support Files/Pods/Pods-frameworks.sh +91 -0
  18. data/Example/Pods/Target Support Files/Pods/Pods-resources.sh +95 -0
  19. data/Example/Pods/Target Support Files/Pods/Pods-umbrella.h +6 -0
  20. data/Example/Pods/Target Support Files/Pods/Pods.debug.xcconfig +8 -0
  21. data/Example/Pods/Target Support Files/Pods/Pods.modulemap +6 -0
  22. data/Example/Pods/Target Support Files/Pods/Pods.release.xcconfig +8 -0
  23. data/Example/Pods/Target Support Files/WillowTreeSegueHandler/Info.plist +26 -0
  24. data/Example/Pods/Target Support Files/WillowTreeSegueHandler/WillowTreeSegueHandler-dummy.m +5 -0
  25. data/Example/Pods/Target Support Files/WillowTreeSegueHandler/WillowTreeSegueHandler-prefix.pch +4 -0
  26. data/Example/Pods/Target Support Files/WillowTreeSegueHandler/WillowTreeSegueHandler-umbrella.h +7 -0
  27. data/Example/Pods/Target Support Files/WillowTreeSegueHandler/WillowTreeSegueHandler.modulemap +6 -0
  28. data/Example/Pods/Target Support Files/WillowTreeSegueHandler/WillowTreeSegueHandler.xcconfig +5 -0
  29. data/Example/SegueBuilder.xcodeproj/project.pbxproj +390 -0
  30. data/Example/SegueBuilder.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
  31. data/Example/SegueBuilder.xcworkspace/contents.xcworkspacedata +10 -0
  32. data/Example/SegueBuilder/AppDelegate.swift +49 -0
  33. data/Example/SegueBuilder/Assets.xcassets/AppIcon.appiconset/Contents.json +73 -0
  34. data/Example/SegueBuilder/Base.lproj/LaunchScreen.storyboard +27 -0
  35. data/Example/SegueBuilder/Base.lproj/Main.storyboard +151 -0
  36. data/Example/SegueBuilder/Info.plist +45 -0
  37. data/Example/SegueBuilder/SegueExtensions.swift +57 -0
  38. data/Example/SegueBuilder/ViewControllers.swift +77 -0
  39. data/Example/Storyboards.swift +0 -0
  40. data/Example/test.sh +20 -0
  41. data/LICENSE +19 -0
  42. data/README.md +75 -0
  43. data/WillowTreeSegueHandler.podspec +23 -0
  44. data/exe/segue-handler-generator +1107 -0
  45. data/exe/segue-handler-generator-build +30 -0
  46. data/lib/cocoapods_plugin.rb +1 -0
  47. data/lib/pod/command/generate_segue_handlers.rb +31 -0
  48. data/lib/segue_handler_plugin.rb +4 -0
  49. data/lib/segue_handler_plugin/version.rb +3 -0
  50. data/segue_handler_plugin.gemspec +20 -0
  51. 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]}`