Better filtering of invalid characters

This commit is contained in:
Mathijs Kadijk
2015-11-01 14:43:28 +01:00
parent fe6e73c222
commit 10e482178e
8 changed files with 180 additions and 26 deletions

View File

@@ -12,6 +12,9 @@
D5646DE51BE2016E0034F4D7 /* PBXObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5646DE21BE2016E0034F4D7 /* PBXObject.swift */; };
D5646DE61BE2016E0034F4D7 /* Serialization.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5646DE31BE2016E0034F4D7 /* Serialization.swift */; };
D579466B1B9347C20044D2FC /* XCProjectFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = D579466A1B9347C20044D2FC /* XCProjectFile.swift */; };
D586D1C61BE20D8600F18FEC /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5646DE11BE2016E0034F4D7 /* Extensions.swift */; };
D586D1C71BE20D8600F18FEC /* PBXObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5646DE21BE2016E0034F4D7 /* PBXObject.swift */; };
D586D1C81BE20D8600F18FEC /* Serialization.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5646DE31BE2016E0034F4D7 /* Serialization.swift */; };
D5B53A171A7D2A9B00F64418 /* values.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5B53A161A7D2A9B00F64418 /* values.swift */; };
D5C5A8EF1BB7196000163E71 /* Core.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5C5A8EE1BB7196000163E71 /* Core.swift */; };
D5CD9B3C1B98258700C85F8C /* input.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5CD9B3B1B98258700C85F8C /* input.swift */; };
@@ -19,6 +22,15 @@
D5EA0DF81A3DF45600FFEBC4 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5EA0DF71A3DF45600FFEBC4 /* main.swift */; };
D5EA0DFF1A3DF4E300FFEBC4 /* util.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5EA0DFE1A3DF4E300FFEBC4 /* util.swift */; };
D5F105FF1A3E2BC30077263A /* func.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F105FE1A3E2BC30077263A /* func.swift */; };
D5F12D411BDACB87009A2C88 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5EA0DF71A3DF45600FFEBC4 /* main.swift */; };
D5F12D421BDACB87009A2C88 /* input.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5CD9B3B1B98258700C85F8C /* input.swift */; };
D5F12D431BDACB87009A2C88 /* processing.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5DA249C1B9CB1BF00AAA43E /* processing.swift */; };
D5F12D441BDACB87009A2C88 /* func.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F105FE1A3E2BC30077263A /* func.swift */; };
D5F12D451BDACB87009A2C88 /* types.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5032B681A7C1542007D1107 /* types.swift */; };
D5F12D461BDACB87009A2C88 /* util.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5EA0DFE1A3DF4E300FFEBC4 /* util.swift */; };
D5F12D471BDACB87009A2C88 /* values.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5B53A161A7D2A9B00F64418 /* values.swift */; };
D5F12D481BDACB8E009A2C88 /* XCProjectFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = D579466A1B9347C20044D2FC /* XCProjectFile.swift */; };
D5F12D491BDACB90009A2C88 /* Core.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5C5A8EE1BB7196000163E71 /* Core.swift */; };
D5F795C11BB9983900844EA2 /* MainTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F795C01BB9983900844EA2 /* MainTests.swift */; };
/* End PBXBuildFile section */
@@ -221,7 +233,19 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
D5F12D461BDACB87009A2C88 /* util.swift in Sources */,
D5F12D441BDACB87009A2C88 /* func.swift in Sources */,
D5F12D431BDACB87009A2C88 /* processing.swift in Sources */,
D586D1C71BE20D8600F18FEC /* PBXObject.swift in Sources */,
D5F12D411BDACB87009A2C88 /* main.swift in Sources */,
D5F12D451BDACB87009A2C88 /* types.swift in Sources */,
D5F12D421BDACB87009A2C88 /* input.swift in Sources */,
D5F795C11BB9983900844EA2 /* MainTests.swift in Sources */,
D5F12D481BDACB8E009A2C88 /* XCProjectFile.swift in Sources */,
D5F12D491BDACB90009A2C88 /* Core.swift in Sources */,
D586D1C61BE20D8600F18FEC /* Extensions.swift in Sources */,
D5F12D471BDACB87009A2C88 /* values.swift in Sources */,
D586D1C81BE20D8600F18FEC /* Serialization.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>classNames</key>
<dict>
<key>MainTests</key>
<dict>
<key>testPerformanceExample()</key>
<dict>
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
<dict>
<key>baselineAverage</key>
<real>0.092011</real>
<key>baselineIntegrationDisplayName</key>
<string>Local Baseline</string>
</dict>
</dict>
<key>testPerformanceSwiftNameSanitization()</key>
<dict>
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
<dict>
<key>baselineAverage</key>
<real>0.11421</real>
<key>baselineIntegrationDisplayName</key>
<string>Local Baseline</string>
</dict>
</dict>
</dict>
</dict>
</dict>
</plist>

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>runDestinationsByUUID</key>
<dict>
<key>14F5F524-DCA5-4BC2-96F0-91274CB940AF</key>
<dict>
<key>localComputer</key>
<dict>
<key>busSpeedInMHz</key>
<integer>100</integer>
<key>cpuCount</key>
<integer>1</integer>
<key>cpuKind</key>
<string>Intel Core i7</string>
<key>cpuSpeedInMHz</key>
<integer>2800</integer>
<key>logicalCPUCoresPerPackage</key>
<integer>4</integer>
<key>modelCode</key>
<string>MacBookPro11,1</string>
<key>physicalCPUCoresPerPackage</key>
<integer>2</integer>
<key>platformIdentifier</key>
<string>com.apple.platform.macosx</string>
</dict>
<key>targetArchitecture</key>
<string>x86_64</string>
</dict>
</dict>
</dict>
</plist>

View File

@@ -7,7 +7,7 @@
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForTesting = "NO"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
@@ -22,10 +22,10 @@
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForRunning = "NO"
buildForProfiling = "NO"
buildForArchiving = "NO"
buildForAnalyzing = "YES">
buildForAnalyzing = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "D5C4227C1B711FDF004EA9B9"

View File

@@ -46,12 +46,21 @@ func filterDirectoryContentsRecursively(fileManager: NSFileManager, filter: (NSU
return assetFolders
}
/*
Disallowed characters: whitespace, mathematical symbols, arrows, private-use and invalid Unicode points, line- and boxdrawing characters
Special rules: Can't begin with a number
*/
func sanitizedSwiftName(name: String, lowercaseFirstCharacter: Bool = true) -> String {
var components = name.componentsSeparatedByCharactersInSet(NSCharacterSet(charactersInString: " -.@~"))
let firstComponent = components.removeAtIndex(0)
let swiftName = components.reduce(firstComponent) { $0 + $1.capitalizedString }
let capitalizedSwiftName = lowercaseFirstCharacter ? swiftName.lowercaseFirstCharacter : swiftName
var nameComponents = name.componentsSeparatedByCharactersInSet(BlacklistedCharacters)
let firstComponent = nameComponents.removeAtIndex(0)
let cleanedSwiftName = nameComponents.reduce(firstComponent) { $0 + $1.uppercaseFirstCharacter }
let regex = try! NSRegularExpression(pattern: "^[0-9]+", options: .CaseInsensitive)
let fullRange = NSRange(location: 0, length: cleanedSwiftName.characters.count)
let sanitizedSwiftName = regex.stringByReplacingMatchesInString(cleanedSwiftName, options: NSMatchingOptions(rawValue: 0), range: fullRange, withTemplate: "")
let capitalizedSwiftName = lowercaseFirstCharacter ? sanitizedSwiftName.lowercaseFirstCharacter : sanitizedSwiftName
return SwiftKeywords.contains(capitalizedSwiftName) ? "`\(capitalizedSwiftName)`" : capitalizedSwiftName
}

View File

@@ -42,6 +42,12 @@ extension String {
let index = startIndex.advancedBy(1)
return substringToIndex(index).lowercaseString + substringFromIndex(index)
}
var uppercaseFirstCharacter: String {
if self.characters.count <= 1 { return self.uppercaseString }
let index = startIndex.advancedBy(1)
return substringToIndex(index).uppercaseString + substringFromIndex(index)
}
}
func indentWithString(indentation: String) -> String -> String {

View File

@@ -7,6 +7,8 @@
// License: MIT License
//
import Foundation
let ResourceFilename = "R.generated.swift"
let Header = [
@@ -245,6 +247,31 @@ let Ordinals = [
(number: 20, word: "twentieth"),
]
// Roughly based on http://www.unicode.org/Public/emoji/1.0//emoji-data.txt
let emojiRanges = [
0x2600...0x27BF,
0x1F300...0x1F6FF,
0x1F900...0x1F9FF,
0x1F1E6...0x1F1FF,
]
let BlacklistedCharacters = { () -> NSCharacterSet in
let blacklist = NSMutableCharacterSet(charactersInString: "")
blacklist.formUnionWithCharacterSet(NSCharacterSet.whitespaceAndNewlineCharacterSet())
blacklist.formUnionWithCharacterSet(NSCharacterSet.punctuationCharacterSet())
blacklist.formUnionWithCharacterSet(NSCharacterSet.symbolCharacterSet())
blacklist.formUnionWithCharacterSet(NSCharacterSet.illegalCharacterSet())
blacklist.formUnionWithCharacterSet(NSCharacterSet.controlCharacterSet())
blacklist.removeCharactersInString("_")
emojiRanges.forEach {
let range = NSRange(location: $0.startIndex, length: $0.endIndex - $0.startIndex)
blacklist.removeCharactersInRange(range)
}
return blacklist
}()
// Extensions
let AssetFolderExtensions: Set<String> = ["xcassets"]
let AssetExtensions: Set<String> = ["launchimage", "imageset"] // Note: "appiconset" is not loadable by default, so it's not included here

View File

@@ -10,26 +10,49 @@ import XCTest
class MainTests: XCTestCase {
override func setUp() {
super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class.
override func setUp() {
super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown()
}
let swiftNameData = [
"easy": "easy",
"easyAndSimple": "easyAndSimple",
"easy with some spaces": "easyWithSomeSpaces",
"(looks) easy": "looksEasy",
"looks-easy": "looksEasy",
"looks+like^some-kind*of%easy": "looksLikeSomeKindOfEasy",
"(looks) easy, but it's not really NeXT that easy!": "looksEasyButItSNotReallyNeXTThatEasy",
"easy 123 and done...": "easy123AndDone",
"123 easy!": "easy",
"123 456easy": "easy",
"123 😄": "😄",
"🇳🇱": "🇳🇱",
"🌂MakeItRain!": "🌂MakeItRain",
]
func testSwiftNameSanitization() {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct results.
swiftNameData.forEach {
let sanitizedResult = sanitizedSwiftName($0.0, lowercaseFirstCharacter: true)
XCTAssertEqual(sanitizedResult, $0.1)
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown()
}
func testExample() {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct results.
}
func testPerformanceExample() {
// This is an example of a performance test case.
self.measureBlock {
// Put the code you want to measure the time of here.
}
}
func testPerformanceSwiftNameSanitization() {
// This is an example of a performance test case.
self.measureBlock {
(0...1000).forEach { _ in
sanitizedSwiftName("(looks) easy, but it's not reallY that easy!", lowercaseFirstCharacter: true)
}
}
}
}