/
BONSpecialGenerator.swift
executable file
·219 lines (174 loc) · 7.42 KB
/
BONSpecialGenerator.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
#!/usr/bin/env swift
//
// BONSpecialGenerator.swift
// BonMot
//
// Created by Zev Eisenberg on 7/16/15.
//
import Foundation
import AppKit
// Please keep this array sorted
let specialCharacters: [unichar] = [
0x0009,
0x000A,
0x0020,
0x00A0,
0x2002,
0x2003,
0x2007,
0x2009,
0x200A,
0x200B,
0x2011,
0x2012,
0x2013,
0x2014,
0x2026,
0x2028,
0x2029,
0x202F,
0x2060,
0x2212,
unichar(NSAttachmentCharacter), // 0xFFFC
]
// CFStringTransform doesn't do a good job of naming these characters, so override with custom names
let customMappings: [unichar: String] = [
0x0009: "Tab",
0x000A: "Line Feed",
0x0020: "Space",
]
// These characters can't be represented with universal character syntax (@"\uXXXX"),
// so we use [NSString stringWithFormat:@"%C", BONCharacterFoo]. We don't do this with all
// characters, even though it would make the code look nicer, for performance reasons
let charactersRequiringFormatStrings: Set<unichar> = [
0x0009,
0x000A,
0x0020,
]
extension unichar {
var unicodeName: String {
get {
if let customName = customMappings[self] {
return customName // bail early!
}
let swiftCharacter = Character(UnicodeScalar(self))
let theCFMutableString = NSMutableString(string: String(swiftCharacter)) as CFMutableString
CFStringTransform(theCFMutableString, UnsafeMutablePointer<CFRange>(), kCFStringTransformToUnicodeName, false)
let characterName = theCFMutableString as String
var trimmedName = characterName
if characterName != String(swiftCharacter) {
// characterName will look like "\N{NO-BREAK SPACE}", so trim "\N{" and "}"
trimmedName = characterName[3..<characterName.utf8.count - 1]
}
return trimmedName
}
}
}
extension String {
subscript(i: Int) -> Character {
return self[startIndex.advancedBy(i)]
}
subscript(range: Range<Int>) -> String {
return self[startIndex.advancedBy(range.startIndex)..<startIndex.advancedBy(range.endIndex)]
}
func camelCaseName(initialLetterCapitalized initialLetterCapitalized: Bool) -> String {
let components: [String] = self.characters.split{$0 == " " || $0 == "-"}.map(String.init)
var camelCaseComponents = components.map { $0.capitalizedString }
if !initialLetterCapitalized && camelCaseComponents.count > 0 {
camelCaseComponents[0] = camelCaseComponents[0].lowercaseString
}
return camelCaseComponents.joinWithSeparator("")
}
var methodName: String {
return self.camelCaseName(initialLetterCapitalized: false)
}
var enumerationValueName: String {
let camelCaseName = self.camelCaseName(initialLetterCapitalized: true)
let fullName = "BONCharacter" + camelCaseName
return fullName
}
}
// from http://stackoverflow.com/a/31480534/255489
func pathToFolderContainingThisScript() -> String {
let cwd = NSFileManager.defaultManager().currentDirectoryPath
let script = Process.arguments[0];
if script.hasPrefix("/") { // absolute
let path = (script as NSString).stringByDeletingLastPathComponent
return path
}
else { // relative
let urlCwd = NSURL(fileURLWithPath: cwd)
if let urlPath = NSURL(string: script, relativeToURL: urlCwd) {
if let path = urlPath.path {
let path = (path as NSString).stringByDeletingLastPathComponent
return path
}
}
}
return ""
}
// ******************************
// * *
// * Real program starts here *
// * *
// ******************************
// Make sure specialCharacters is sorted
let sortedSpecialCharacters = specialCharacters.sort({$0 < $1})
if sortedSpecialCharacters != specialCharacters {
print("ERROR: The specialCharacters array is not sorted, and it must be.")
exit(1)
}
// Populate strings with the declaration and implementation of the methods
var headerEnumString = "typedef NS_ENUM(unichar, BONCharacter) {\n"
var headerCodeString = ""
var implementationCodeString = ""
for theUnichar in specialCharacters {
let characterName = theUnichar.unicodeName
let methodName = characterName.methodName
let enumerationName = characterName.enumerationValueName
let hexValueString = NSString(format:"%.4X", theUnichar)
let enumerationStatement = " \(enumerationName) = 0x\(hexValueString),\n"
headerEnumString += enumerationStatement
let methodPrototype = "+ (NSString *)\(methodName)"
let methodInterface = methodPrototype + ";"
let returnExpression: String
if charactersRequiringFormatStrings.contains(theUnichar) {
returnExpression = NSString(format:"return [NSString stringWithFormat:@\"%%C\", %@];", enumerationName) as String
}
else {
returnExpression = NSString(format:"return @\"\\u%.4X\";", theUnichar) as String
}
let methodImplementation = "\(methodPrototype) { \(returnExpression as String) }"
headerCodeString += (methodInterface + "\n")
implementationCodeString += (methodImplementation + "\n")
}
headerEnumString += "};"
// Get the contents of the template files
let currentDirectory = pathToFolderContainingThisScript()
let headerTemplatePath = (currentDirectory as NSString).stringByAppendingPathComponent("BONSpecial.h template.txt")
let implementationTemplatePath = (currentDirectory as NSString).stringByAppendingPathComponent("BONSpecial.m template.txt")
var headerTemplateString: String!
var implementationTemplateString: String!
do {
headerTemplateString = try! NSString(contentsOfFile: headerTemplatePath, encoding: NSUTF8StringEncoding) as String
implementationTemplateString = try! NSString(contentsOfFile: implementationTemplatePath, encoding: NSUTF8StringEncoding) as String
}
// Replace the template regions of the template files with the generated code
let replacementString = "{{ contents }}"
let fullHeaderString = headerEnumString + "\n\n" + headerCodeString
let headerOutputString = headerTemplateString.stringByReplacingOccurrencesOfString(replacementString, withString: fullHeaderString)
let implementationOutputString = implementationTemplateString.stringByReplacingOccurrencesOfString(replacementString, withString: implementationCodeString)
// Write the files out to the project directory
let projectDirectory = (currentDirectory as NSString).stringByDeletingLastPathComponent
let classesDirectory = (projectDirectory as NSString).stringByAppendingPathComponent("Pod/Classes")
let baseFileName = "BONSpecial"
let headerFileName = (baseFileName as NSString).stringByAppendingPathExtension("h")!
let implementationFileName = (baseFileName as NSString).stringByAppendingPathExtension("m")!
let headerFilePath = (classesDirectory as NSString).stringByAppendingPathComponent(headerFileName)
let implementationFilePath = (classesDirectory as NSString).stringByAppendingPathComponent(implementationFileName)
do {
try! headerOutputString.writeToFile(headerFilePath, atomically: true, encoding: NSUTF8StringEncoding)
try! implementationOutputString.writeToFile(implementationFilePath, atomically: true, encoding: NSUTF8StringEncoding)
print("Updated \(headerFileName) and \(implementationFileName) in \(classesDirectory)")
print("Please run `pod install` to update the headers in the example project.")
}