Skip to content

Commit 252a57d

Browse files
committed
Merge pull request #20 from mac-cain13/develop
Release of version 0.6
2 parents 5cd5ac4 + b2a8dd7 commit 252a57d

File tree

8 files changed

+528
-145
lines changed

8 files changed

+528
-145
lines changed

License

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
The MIT License (MIT)
22

3-
Copyright (c) 2014 Mathijs Kadijk
3+
Copyright (c) 2014-2015 Mathijs Kadijk
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

R.swift.xcodeproj/project.pbxproj

+8
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10+
D5032B691A7C1542007D1107 /* types.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5032B681A7C1542007D1107 /* types.swift */; };
11+
D5B53A171A7D2A9B00F64418 /* values.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5B53A161A7D2A9B00F64418 /* values.swift */; };
1012
D5EA0DF81A3DF45600FFEBC4 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5EA0DF71A3DF45600FFEBC4 /* main.swift */; };
1113
D5EA0DFF1A3DF4E300FFEBC4 /* util.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5EA0DFE1A3DF4E300FFEBC4 /* util.swift */; };
1214
D5F105FF1A3E2BC30077263A /* func.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F105FE1A3E2BC30077263A /* func.swift */; };
@@ -25,6 +27,8 @@
2527
/* End PBXCopyFilesBuildPhase section */
2628

2729
/* Begin PBXFileReference section */
30+
D5032B681A7C1542007D1107 /* types.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = types.swift; sourceTree = "<group>"; };
31+
D5B53A161A7D2A9B00F64418 /* values.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = values.swift; sourceTree = "<group>"; };
2832
D5EA0DF41A3DF45600FFEBC4 /* rswift */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = rswift; sourceTree = BUILT_PRODUCTS_DIR; };
2933
D5EA0DF71A3DF45600FFEBC4 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
3034
D5EA0DFE1A3DF4E300FFEBC4 /* util.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = util.swift; sourceTree = "<group>"; };
@@ -63,7 +67,9 @@
6367
children = (
6468
D5EA0DF71A3DF45600FFEBC4 /* main.swift */,
6569
D5F105FE1A3E2BC30077263A /* func.swift */,
70+
D5032B681A7C1542007D1107 /* types.swift */,
6671
D5EA0DFE1A3DF4E300FFEBC4 /* util.swift */,
72+
D5B53A161A7D2A9B00F64418 /* values.swift */,
6773
);
6874
path = R.swift;
6975
sourceTree = "<group>";
@@ -127,6 +133,8 @@
127133
D5EA0DF81A3DF45600FFEBC4 /* main.swift in Sources */,
128134
D5EA0DFF1A3DF4E300FFEBC4 /* util.swift in Sources */,
129135
D5F105FF1A3E2BC30077263A /* func.swift in Sources */,
136+
D5B53A171A7D2A9B00F64418 /* values.swift in Sources */,
137+
D5032B691A7C1542007D1107 /* types.swift in Sources */,
130138
);
131139
runOnlyForDeploymentPostprocessing = 0;
132140
};

R.swift/func.swift

+100-116
Original file line numberDiff line numberDiff line change
@@ -9,108 +9,37 @@
99

1010
import Foundation
1111

12-
// MARK: Types
13-
14-
let ResourceFilename = "R.generated.swift"
15-
16-
struct AssetFolder {
17-
let name: String
18-
let imageAssets: [String]
12+
// MARK: Helper functions
1913

20-
init(url: NSURL, fileManager: NSFileManager) {
21-
name = url.filename!
14+
let indent = indentWithString(IndentationString)
2215

23-
let contents = fileManager.contentsOfDirectoryAtURL(url, includingPropertiesForKeys: nil, options: NSDirectoryEnumerationOptions.SkipsHiddenFiles, error: nil) as [NSURL]
24-
imageAssets = contents.map { $0.filename! }
25-
}
16+
func warn(warning: String) {
17+
println("warning: \(warning)")
2618
}
2719

28-
struct Storyboard {
29-
let name: String
30-
let segues: [String]
31-
let viewControllers: [ViewController]
32-
let usedImageIdentifiers: [String]
33-
34-
init(url: NSURL) {
35-
name = url.filename!
36-
37-
let parserDelegate = StoryboardParserDelegate()
38-
39-
let parser = NSXMLParser(contentsOfURL: url)!
40-
parser.delegate = parserDelegate
41-
parser.parse()
42-
43-
segues = parserDelegate.segues
44-
viewControllers = parserDelegate.viewControllers
45-
usedImageIdentifiers = parserDelegate.usedImageIdentifiers
46-
}
47-
48-
struct ViewController {
49-
let storyboardIdentifier: String
50-
let customModule: String?
51-
let customClass: String
52-
53-
func fullyQualifiedClass() -> String {
54-
if let customModule = customModule {
55-
return customModule + "." + customClass
56-
}
57-
58-
return customClass
59-
}
60-
}
20+
func fail(error: String) {
21+
println("error: \(error)")
6122
}
6223

63-
class StoryboardParserDelegate: NSObject, NSXMLParserDelegate {
64-
var segues: [String] = []
65-
var viewControllers: [Storyboard.ViewController] = []
66-
var usedImageIdentifiers: [String] = []
67-
68-
func parser(parser: NSXMLParser!, didStartElement elementName: String!, namespaceURI: String!, qualifiedName qName: String!, attributes attributeDict: [NSObject : AnyObject]!) {
69-
switch elementName {
70-
case "segue":
71-
if let segueIdentifier = attributeDict["identifier"] as? String {
72-
segues.append(segueIdentifier)
73-
}
74-
75-
case "image":
76-
if let imageIdentifier = attributeDict["name"] as? String {
77-
usedImageIdentifiers.append(imageIdentifier)
78-
}
79-
80-
default:
81-
if let viewController = viewControllerFromAttributes(attributeDict) {
82-
viewControllers.append(viewController)
83-
}
84-
}
85-
}
86-
87-
func viewControllerFromAttributes(attributeDict: [NSObject : AnyObject]) -> Storyboard.ViewController? {
88-
if attributeDict["sceneMemberID"] as? String == "viewController" {
89-
if let storyboardIdentifier = attributeDict["storyboardIdentifier"] as? String {
90-
let customModule = attributeDict["customModule"] as? String
91-
let customClass = attributeDict["customClass"] as? String ?? "UIViewController"
92-
93-
return Storyboard.ViewController(storyboardIdentifier: storyboardIdentifier, customModule: customModule, customClass: customClass)
94-
}
95-
}
96-
97-
return nil
24+
func failOnError(error: NSError?) {
25+
if let error = error {
26+
fail("\(error)")
9827
}
9928
}
10029

101-
// MARK: Helper functions
102-
103-
let IndentationString = " "
104-
let indent = indentWithString(IndentationString)
105-
10630
func inputDirectories(processInfo: NSProcessInfo) -> [NSURL] {
10731
return processInfo.arguments.skip(1).map { NSURL(fileURLWithPath: $0 as String)! }
10832
}
10933

11034
func filterDirectoryContentsRecursively(fileManager: NSFileManager, filter: (NSURL) -> Bool)(url: NSURL) -> [NSURL] {
11135
var assetFolders = [NSURL]()
11236

113-
if let enumerator = fileManager.enumeratorAtURL(url, includingPropertiesForKeys: [NSURLIsDirectoryKey], options: NSDirectoryEnumerationOptions.SkipsHiddenFiles|NSDirectoryEnumerationOptions.SkipsPackageDescendants, errorHandler: nil) {
37+
let errorHandler: (NSURL!, NSError!) -> Bool = { url, error in
38+
failOnError(error)
39+
return true
40+
}
41+
42+
if let enumerator = fileManager.enumeratorAtURL(url, includingPropertiesForKeys: [NSURLIsDirectoryKey], options: NSDirectoryEnumerationOptions.SkipsHiddenFiles|NSDirectoryEnumerationOptions.SkipsPackageDescendants, errorHandler: errorHandler) {
11443

11544
while let enumeratorItem: AnyObject = enumerator.nextObject() {
11645
if let url = enumeratorItem as? NSURL {
@@ -126,55 +55,110 @@ func filterDirectoryContentsRecursively(fileManager: NSFileManager, filter: (NSU
12655
return assetFolders
12756
}
12857

129-
func sanitizedSwiftName(name: String) -> String {
58+
func sanitizedSwiftName(name: String, lowercaseFirstCharacter: Bool = true) -> String {
13059
var components = name.componentsSeparatedByString("-")
13160
let firstComponent = components.removeAtIndex(0)
132-
return components.reduce(firstComponent) { $0 + $1.capitalizedString }.lowercaseFirstCharacter
61+
let swiftName = components.reduce(firstComponent) { $0 + $1.capitalizedString }
62+
63+
return lowercaseFirstCharacter ? swiftName.lowercaseFirstCharacter : swiftName
13364
}
13465

13566
func writeResourceFile(code: String, toFolderURL folderURL: NSURL) {
13667
let outputURL = folderURL.URLByAppendingPathComponent(ResourceFilename)
137-
code.writeToURL(outputURL, atomically: true, encoding: NSUTF8StringEncoding, error: nil)
68+
69+
var error: NSError?
70+
code.writeToURL(outputURL, atomically: true, encoding: NSUTF8StringEncoding, error: &error)
71+
72+
failOnError(error)
13873
}
13974

140-
// MARK: Code generator functions
75+
func readResourceFile(folderURL: NSURL) -> String? {
76+
let inputURL = folderURL.URLByAppendingPathComponent(ResourceFilename)
77+
78+
if let resourceFileString = String(contentsOfURL: inputURL, encoding: NSUTF8StringEncoding, error: nil) {
79+
return resourceFileString
80+
}
14181

142-
func swiftImports() -> String {
143-
return "import UIKit"
82+
return nil
14483
}
14584

146-
func swiftImageStructWithAssetFolders(assetFolders: [AssetFolder]) -> String {
147-
return distinct(assetFolders.flatMap { $0.imageAssets })
148-
.reduce("struct image {\n") {
149-
$0 + " static var \(sanitizedSwiftName($1)): UIImage? { return UIImage(named: \"\($1)\") }\n"
150-
} + "}"
85+
// MARK: Struct/function generators
86+
87+
// Image
88+
89+
func imageStructFromAssetFolders(assetFolders: [AssetFolder]) -> Struct {
90+
let vars = distinct(assetFolders.flatMap { $0.imageAssets })
91+
.map { Var(name: $0, type: Type._UIImage.asOptional(), getter: "return UIImage(named: \"\($0)\")") }
92+
93+
return Struct(name: "image", vars: vars, functions: [], structs: [])
15194
}
15295

153-
func swiftSegueStructWithStoryboards(storyboards: [Storyboard]) -> String {
154-
return distinct(storyboards.flatMap { $0.segues })
155-
.reduce("struct segue {\n") {
156-
$0 + " static var \(sanitizedSwiftName($1)): String { return \"\($1)\" }\n"
157-
} + "}"
96+
// Segue
97+
98+
func segueStructFromStoryboards(storyboards: [Storyboard]) -> Struct {
99+
let vars = distinct(storyboards.flatMap { $0.segues })
100+
.map { Var(name: $0, type: Type._String, getter: "return \"\($0)\"") }
101+
102+
return Struct(name: "segue", vars: vars, functions: [], structs: [])
158103
}
159104

160-
func swiftStructForStoryboard(storyboard: Storyboard) -> String {
161-
let instanceVar = "static var instance: UIStoryboard { return UIStoryboard(name: \"\(storyboard.name)\", bundle: nil) }"
105+
// Storyboard
162106

163-
let viewControllers = storyboard.viewControllers.reduce("") {
164-
$0 + "static var \(sanitizedSwiftName($1.storyboardIdentifier)): \($1.fullyQualifiedClass())? { return instance.instantiateViewControllerWithIdentifier(\"\($1.storyboardIdentifier)\") as? \($1.fullyQualifiedClass()) }\n"
165-
}
107+
func storyboardStructFromStoryboards(storyboards: [Storyboard]) -> Struct {
108+
return Struct(name: "storyboard", vars: [], functions: [], structs: storyboards.map(storyboardStructForStoryboard))
109+
}
110+
111+
func storyboardStructForStoryboard(storyboard: Storyboard) -> Struct {
112+
let instanceVars = [Var(name: "instance", type: Type._UIStoryboard, getter: "return UIStoryboard(name: \"\(storyboard.name)\", bundle: nil)")]
166113

167-
let validateStoryboardImages = distinct(storyboard.usedImageIdentifiers)
168-
.reduce("static func validateImages() {\n") {
169-
$0 + " assert(UIImage(named: \"\($1)\") != nil, \"[R.swift] Image named '\($1)' is used in storyboard '\(storyboard.name)', but couldn't be loaded.\")\n"
170-
} + "}"
114+
let viewControllerVars = storyboard.viewControllers
115+
.map { Var(name: $0.storyboardIdentifier, type: $0.type.asOptional(), getter: "return instance.instantiateViewControllerWithIdentifier(\"\($0.storyboardIdentifier)\") as? \($0.type.asNonOptional())") }
116+
117+
let validateImagesLines = distinct(storyboard.usedImageIdentifiers)
118+
.map { "assert(UIImage(named: \"\($0)\") != nil, \"[R.swift] Image named '\($0)' is used in storyboard '\(storyboard.name)', but couldn't be loaded.\")" }
119+
let validateImagesFunc = Function(name: "validateImages", parameters: [], returnType: Type._Void, body: join("\n", validateImagesLines))
120+
121+
let validateViewControllersLines = storyboard.viewControllers
122+
.map { "assert(\(sanitizedSwiftName($0.storyboardIdentifier)) != nil, \"[R.swift] ViewController with identifier '\(sanitizedSwiftName($0.storyboardIdentifier))' could not be loaded from storyboard '\(storyboard.name)' as '\($0.type)'.\")" }
123+
let validateViewControllersFunc = Function(name: "validateViewControllers", parameters: [], returnType: Type._Void, body: join("\n", validateViewControllersLines))
124+
125+
return Struct(name: storyboard.name, vars: instanceVars + viewControllerVars, functions: [validateImagesFunc, validateViewControllersFunc], structs: [])
126+
}
127+
128+
// Nib
129+
130+
func nibStructFromNibs(nibs: [Nib]) -> Struct {
131+
return Struct(name: "nib", vars: [], functions: [], structs: nibs.map(nibStructForNib))
132+
}
133+
134+
func nibStructForNib(nib: Nib) -> Struct {
135+
let ownerOrNilParameter = Function.Parameter(name: "ownerOrNil", type: Type._AnyObject.asOptional())
136+
let optionsOrNilParameter = Function.Parameter(name: "options", localName: "optionsOrNil", type: Type(className: "[NSObject : AnyObject]", optional: true))
137+
138+
let instanceVars = [Var(name: "instance", type: Type._UINib, getter: "return UINib.init(nibName: \"\(nib.name)\", bundle: nil)")]
139+
let instantiateFunc = Function(name: "instantiateWithOwner", parameters: [ownerOrNilParameter, optionsOrNilParameter], returnType: Type(className: "[AnyObject]"), body: "return instance.instantiateWithOwner(ownerOrNil, options: optionsOrNil)")
140+
141+
let viewFuncs = zip(nib.rootViews, Ordinals)
142+
.map { (view: $0.0, ordinal: $0.1) }
143+
.map { Function(name: "\($0.ordinal.word)View", parameters: [ownerOrNilParameter, optionsOrNilParameter], returnType: $0.view.asOptional(), body: "return instantiateWithOwner(ownerOrNil, options: optionsOrNil)[\($0.ordinal.number - 1)] as? \($0.view)") }
144+
145+
return Struct(name: nib.name, vars: instanceVars, functions: [instantiateFunc] + viewFuncs, structs: [])
146+
}
147+
148+
// Reuse identifiers
149+
150+
func reuseIdentifierStructFromReuseIdentifierContainers(containers: [ReuseIdentifierContainer]) -> Struct {
151+
let reuseIdentifierVars = containers
152+
.flatMap { $0.reuseIdentifiers }
153+
.map { Var(name: $0, type: Type._String, getter: "return \"\($0)\"") }
154+
155+
return Struct(name: "reuseIdentifier", vars: reuseIdentifierVars, functions: [], structs: [])
156+
}
171157

172-
let validateStoryboardViewControllers = storyboard.viewControllers
173-
.reduce("static func validateViewControllers() {\n") {
174-
$0 + " assert(\(sanitizedSwiftName($1.storyboardIdentifier)) != nil, \"[R.swift] ViewController with identifier '\(sanitizedSwiftName($1.storyboardIdentifier))' could not be loaded from storyboard '\(storyboard.name)' as '\($1.fullyQualifiedClass())'.\")\n"
175-
} + "}"
158+
// Validation
176159

177-
return "struct \(sanitizedSwiftName(storyboard.name)) {\n" + indent(string: instanceVar) + "\n" + indent(string: viewControllers) + indent(string: validateStoryboardImages) + "\n" + indent(string: validateStoryboardViewControllers) + "}"
160+
func validateAllFunctionWithStoryboards(storyboards: [Storyboard]) -> Function {
161+
return Function(name: "validate", parameters: [], returnType: Type._Void, body: storyboards.map(swiftCallStoryboardValidators).reduce("", combine: +))
178162
}
179163

180164
func swiftCallStoryboardValidators(storyboard: Storyboard) -> String {

R.swift/main.swift

+34-20
Original file line numberDiff line numberDiff line change
@@ -11,35 +11,49 @@ import Foundation
1111

1212
let defaultFileManager = NSFileManager.defaultManager()
1313
let findAllAssetsFolderURLsInDirectory = filterDirectoryContentsRecursively(defaultFileManager) { $0.isDirectory && $0.absoluteString!.pathExtension == "xcassets" }
14+
let findAllNibURLsInDirectory = filterDirectoryContentsRecursively(defaultFileManager) { !$0.isDirectory && $0.absoluteString!.pathExtension == "xib" }
1415
let findAllStoryboardURLsInDirectory = filterDirectoryContentsRecursively(defaultFileManager) { !$0.isDirectory && $0.absoluteString!.pathExtension == "storyboard" }
1516

1617
inputDirectories(NSProcessInfo.processInfo())
1718
.each { directory in
18-
// Imports
19-
let imports = swiftImports()
20-
21-
// Storyboards
19+
20+
var error: NSError?
21+
if !directory.checkResourceIsReachableAndReturnError(&error) {
22+
failOnError(error)
23+
return
24+
}
25+
26+
// Get/parse all resources into our domain objects
27+
let assetFolders = findAllAssetsFolderURLsInDirectory(url: directory)
28+
.map { AssetFolder(url: $0, fileManager: defaultFileManager) }
29+
2230
let storyboards = findAllStoryboardURLsInDirectory(url: directory)
2331
.map { Storyboard(url: $0) }
2432

25-
let segueStruct = swiftSegueStructWithStoryboards(storyboards)
33+
let nibs = findAllNibURLsInDirectory(url: directory)
34+
.map { Nib(url: $0) }
2635

27-
let storyboardStructs = storyboards.map(swiftStructForStoryboard)
28-
.map(indent)
29-
.reduce("struct storyboard {\n", +) + "}"
30-
31-
let validateAllStoryboardsFunction = storyboards.map(swiftCallStoryboardValidators)
32-
.map(indent)
33-
.reduce("static func validate() {\n", +) + "}"
36+
let reuseIdentifierContainers = nibs.map { $0 as ReuseIdentifierContainer } + storyboards.map { $0 as ReuseIdentifierContainer }
3437

35-
// Asset folders
36-
let assetFolders = findAllAssetsFolderURLsInDirectory(url: directory)
37-
.map { AssetFolder(url: $0, fileManager: defaultFileManager) }
38+
// Generate
39+
let structs = [
40+
imageStructFromAssetFolders(assetFolders),
41+
segueStructFromStoryboards(storyboards),
42+
storyboardStructFromStoryboards(storyboards),
43+
nibStructFromNibs(nibs),
44+
reuseIdentifierStructFromReuseIdentifierContainers(reuseIdentifierContainers)
45+
]
46+
47+
let functions = [
48+
validateAllFunctionWithStoryboards(storyboards),
49+
]
3850

39-
let imageStruct = swiftImageStructWithAssetFolders(assetFolders)
51+
// Generate resource file contents
52+
let resourceStruct = Struct(name: "R", vars: [], functions: functions, structs: structs, lowercaseFirstCharacter: false)
53+
let fileContents = join("\n", [Header, "", Imports, "", resourceStruct.description])
4054

41-
// Write out the code
42-
let code = [imageStruct, segueStruct, storyboardStructs, validateAllStoryboardsFunction]
43-
.reduce("\(imports)\n\nstruct R {") { $0 + "\n" + indent(string: $1) } + "}\n"
44-
writeResourceFile(code, toFolderURL: directory)
55+
// Write file if we have changes
56+
if readResourceFile(directory) != fileContents {
57+
writeResourceFile(fileContents, toFolderURL: directory)
58+
}
4559
}

0 commit comments

Comments
 (0)