diff --git a/Documentation/filters-strings.md b/Documentation/filters-strings.md index b4a7c50e..406ae8b3 100644 --- a/Documentation/filters-strings.md +++ b/Documentation/filters-strings.md @@ -184,6 +184,8 @@ https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift | `some$URL` | `Some_URL` | | `25 Ultra Light` | `_25_Ultra_Light` | | `26_extra_ultra_light` | `_26_extra_ultra_light` | +| `apples.count` | `Apples_Count` | +| `foo_bar.baz.qux-yay` | `Foo_bar_Baz_Qux_Yay` | **pretty**: Same as normal, but it will first replace whitespaces by underscores and apply the `snakeToCamelCase` filter. @@ -191,9 +193,12 @@ https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift |------------------------|----------------------| | `hello` | `Hello` | | `42hello` | `_42hello` | -| `some$URL` | `Some_URL` | +| `some$URL` | `SomeURL` | | `25 Ultra Light` | `_25UltraLight` | | `26_extra_ultra_light` | `_26ExtraUltraLight` | +| `apples.count` | `ApplesCount` | +| `foo_bar.baz.qux-yay` | `FooBarBazQuxYay` | + ## Filter: `titlecase` diff --git a/Sources/Filters+Strings.swift b/Sources/Filters+Strings.swift index 523b2c9f..ee3f4869 100644 --- a/Sources/Filters+Strings.swift +++ b/Sources/Filters+Strings.swift @@ -73,9 +73,9 @@ extension Filters { case .normal: return StencilSwiftKit.swiftIdentifier(from: string, replaceWithUnderscores: true) case .pretty: - string = string.replacingOccurrences(of: " ", with: "_") + string = StencilSwiftKit.swiftIdentifier(from: string, replaceWithUnderscores: true) string = try snakeToCamelCase(string, stripLeading: true) - return StencilSwiftKit.swiftIdentifier(from: string, replaceWithUnderscores: true) + return StencilSwiftKit.prefixWithUnderscoreIfNeeded(string: string) } } diff --git a/Sources/SwiftIdentifier.swift b/Sources/SwiftIdentifier.swift index 68d9e5e8..9a6ab369 100644 --- a/Sources/SwiftIdentifier.swift +++ b/Sources/SwiftIdentifier.swift @@ -32,7 +32,7 @@ private let tailRanges: [CountableClosedRange] = [ 0x30...0x39, 0x300...0x36F, 0x1dc0...0x1dff, 0x20d0...0x20ff, 0xfe20...0xfe2f ] -private func identifierCharacterSets() -> (head: NSMutableCharacterSet, tail: NSMutableCharacterSet) { +private func identifierCharacterSets(exceptions: String) -> (head: NSMutableCharacterSet, tail: NSMutableCharacterSet) { let addRange: (NSMutableCharacterSet, CountableClosedRange) -> Void = { (mcs, range) in mcs.addCharacters(in: NSRange(location: range.lowerBound, length: range.count)) } @@ -41,6 +41,7 @@ private func identifierCharacterSets() -> (head: NSMutableCharacterSet, tail: NS for range in headRanges { addRange(head, range) } + head.removeCharacters(in: exceptions) guard let tail = head.mutableCopy() as? NSMutableCharacterSet else { fatalError("Internal error: mutableCopy() should have returned a valid NSMutableCharacterSet") @@ -48,6 +49,7 @@ private func identifierCharacterSets() -> (head: NSMutableCharacterSet, tail: NS for range in tailRanges { addRange(tail, range) } + tail.removeCharacters(in: exceptions) return (head, tail) } @@ -56,14 +58,8 @@ func swiftIdentifier(from string: String, forbiddenChars exceptions: String = "", replaceWithUnderscores underscores: Bool = false) -> String { - let (head, tail) = identifierCharacterSets() - head.removeCharacters(in: exceptions) - tail.removeCharacters(in: exceptions) + let (_, tail) = identifierCharacterSets(exceptions: exceptions) - let chars = string.unicodeScalars - let firstChar = chars[chars.startIndex] - - let prefix = !head.longCharacterIsMember(firstChar.value) && tail.longCharacterIsMember(firstChar.value) ? "_" : "" let parts = string.components(separatedBy: tail.inverted) let replacement = underscores ? "_" : "" let mappedParts = parts.map({ (string: String) -> String in @@ -78,5 +74,19 @@ func swiftIdentifier(from string: String, return "" } }) - return prefix + mappedParts.joined(separator: replacement) + + let result = mappedParts.joined(separator: replacement) + return prefixWithUnderscoreIfNeeded(string: result, forbiddenChars: exceptions) +} + +func prefixWithUnderscoreIfNeeded(string: String, + forbiddenChars exceptions: String = "") -> String { + + let (head, tail) = identifierCharacterSets(exceptions: exceptions) + + let chars = string.unicodeScalars + let firstChar = chars[chars.startIndex] + let prefix = !head.longCharacterIsMember(firstChar.value) && tail.longCharacterIsMember(firstChar.value) ? "_" : "" + + return prefix + string } diff --git a/Tests/StencilSwiftKitTests/SwiftIdentifierTests.swift b/Tests/StencilSwiftKitTests/SwiftIdentifierTests.swift index fd035d02..b9ecef4e 100644 --- a/Tests/StencilSwiftKitTests/SwiftIdentifierTests.swift +++ b/Tests/StencilSwiftKitTests/SwiftIdentifierTests.swift @@ -70,6 +70,12 @@ extension SwiftIdentifierTests { "42hello": "_42hello", "some$URL": "Some_URL", "with space": "With_Space", + "apples.count": "Apples_Count", + ".SFNSDisplay": "_SFNSDisplay", + "Show-NavCtrl": "Show_NavCtrl", + "HEADER_TITLE": "HEADER_TITLE", + "multiLine\nKey": "MultiLine_Key", + "foo_bar.baz.qux-yay": "Foo_bar_Baz_Qux_Yay", "25 Ultra Light": "_25_Ultra_Light", "26_extra_ultra_light": "_26_extra_ultra_light", "12 @ 34 % 56 + 78 Hello world": "_12___34___56___78_Hello_World" @@ -85,11 +91,17 @@ extension SwiftIdentifierTests { let expectations = [ "hello": "Hello", "42hello": "_42hello", - "some$URL": "Some_URL", + "some$URL": "SomeURL", "with space": "WithSpace", + "apples.count": "ApplesCount", + ".SFNSDisplay": "SFNSDisplay", + "Show-NavCtrl": "ShowNavCtrl", + "HEADER_TITLE": "HeaderTitle", + "multiLine\nKey": "MultiLineKey", + "foo_bar.baz.qux-yay": "FooBarBazQuxYay", "25 Ultra Light": "_25UltraLight", "26_extra_ultra_light": "_26ExtraUltraLight", - "12 @ 34 % 56 + 78 Hello world": "_12_34_56_78HelloWorld" + "12 @ 34 % 56 + 78 Hello world": "_12345678HelloWorld" ] for (input, expected) in expectations {