├── .gitignore
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ └── IDEWorkspaceChecks.plist
├── LICENSE
├── Package.swift
├── README.md
├── Sources
└── StringCase
│ └── StringCase.swift
└── Tests
├── LinuxMain.swift
└── StringCaseTests
├── StringCaseTests.swift
└── XCTestManifests.swift
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData/
8 |
9 | ## Various settings
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | xcuserdata/
19 |
20 | ## Other
21 | *.moved-aside
22 | *.xccheckout
23 | *.xcscmblueprint
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 | *.ipa
28 | *.dSYM.zip
29 | *.dSYM
30 |
31 | ## Playgrounds
32 | timeline.xctimeline
33 | playground.xcworkspace
34 |
35 | # Swift Package Manager
36 | #
37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
38 | # Packages/
39 | # Package.pins
40 | # Package.resolved
41 | .build/
42 |
43 | # CocoaPods
44 | #
45 | # We recommend against adding the Pods directory to your .gitignore. However
46 | # you should judge for yourself, the pros and cons are mentioned at:
47 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
48 | #
49 | # Pods/
50 | #
51 | # Add this line if you want to avoid checking in source code from the Xcode workspace
52 | # *.xcworkspace
53 |
54 | # Carthage
55 | #
56 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
57 | # Carthage/Checkouts
58 |
59 | Carthage/Build
60 |
61 | # Accio dependency management
62 | Dependencies/
63 | .accio/
64 |
65 | # fastlane
66 | #
67 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
68 | # screenshots whenever they are needed.
69 | # For more information about the recommended setup visit:
70 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
71 |
72 | fastlane/report.xml
73 | fastlane/Preview.html
74 | fastlane/screenshots/**/*.png
75 | fastlane/test_output
76 |
77 | # Code Injection
78 | #
79 | # After new code Injection tools there's a generated folder /iOSInjectionProject
80 | # https://github.com/johnno1962/injectionforxcode
81 |
82 | iOSInjectionProject/
83 | .DS_Store
84 | .swiftpm
85 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Devran "Cosmo" Ünal
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.0
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "StringCase",
8 | products: [
9 | // Products define the executables and libraries produced by a package, and make them visible to other packages.
10 | .library(
11 | name: "StringCase",
12 | targets: ["StringCase"]),
13 | ],
14 | dependencies: [
15 | // Dependencies declare other packages that this package depends on.
16 | // .package(url: /* package url */, from: "1.0.0"),
17 | ],
18 | targets: [
19 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
20 | // Targets can depend on other targets in this package, and on products in packages which this package depends on.
21 | .target(
22 | name: "StringCase",
23 | dependencies: []),
24 | .testTarget(
25 | name: "StringCaseTests",
26 | dependencies: ["StringCase"]),
27 | ]
28 | )
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # StringCase
2 |
3 | Converts `String` to `lowerCamelCase`, `UpperCamelCase` and `snake_case`.
4 |
5 | ## Usage
6 |
7 | ### Transformation
8 |
9 | ```swift
10 | let input = "Keynote Event"
11 |
12 | input.lowerCamelCased() // "keynoteEvent"
13 | input.upperCamelCased() // "KeynoteEvent"
14 | input.snakeCased() // "keynote_event"
15 | ```
16 |
17 | ```swift
18 | let input = "_this is* not-Very%difficult"
19 |
20 | input.lowerCamelCased() // "thisIsNotVeryDifficult"
21 | input.upperCamelCased() // "ThisIsNotVeryDifficult"
22 | input.snakeCased() // "this_is_not_very_difficult"
23 | ```
24 |
25 | ### Boolean checks
26 |
27 | ```swift
28 | "KeynoteEvent".isLowerCamelCased // false
29 | "keynoteEvent".isLowerCamelCased // true
30 | "keynote_event".isLowerCamelCased // false
31 | ```
32 |
33 | ```swift
34 | "KeynoteEvent".isUpperCamelCased // true
35 | "keynoteEvent".isUpperCamelCased // false
36 | "keynote_event".isUpperCamelCased // false
37 | ```
38 |
39 | ```swift
40 | "KeynoteEvent".isSnakeCase // false
41 | "keynoteEvent".isSnakeCase // false
42 | "keynote_event".isSnakeCase // true
43 | ```
44 |
45 | ## Contact
46 |
47 | * Devran "Cosmo" Uenal
48 | * Twitter: [@maccosmo](http://twitter.com/maccosmo)
49 | * LinkedIn: [devranuenal](https://www.linkedin.com/in/devranuenal)
50 |
51 | ## Other Projects
52 |
53 | * [BinaryKit](https://github.com/Cosmo/BinaryKit) — BinaryKit helps you to break down binary data into bits and bytes and easily access specific parts.
54 | * [Clippy](https://github.com/Cosmo/Clippy) — Clippy from Microsoft Office is back and runs on macOS! Written in Swift.
55 | * [GrammaticalNumber](https://github.com/Cosmo/GrammaticalNumber) — Turns singular words to the plural and vice-versa in Swift.
56 | * [HackMan](https://github.com/Cosmo/HackMan) — Stop writing boilerplate code yourself. Let hackman do it for you via the command line.
57 | * [ISO8859](https://github.com/Cosmo/ISO8859) — Convert ISO8859 1-16 Encoded Text to String in Swift. Supports iOS, tvOS, watchOS and macOS.
58 | * [SpriteMap](https://github.com/Cosmo/SpriteMap) — SpriteMap helps you to extract sprites out of a sprite map. Written in Swift.
59 | * [TinyConsole](https://github.com/Cosmo/TinyConsole) — TinyConsole is a micro-console that can help you log and display information inside an iOS application, where having a connection to a development computer is not possible.
60 |
61 |
62 | ## License
63 |
64 | StringCase is released under the [MIT License](http://www.opensource.org/licenses/MIT).
65 |
--------------------------------------------------------------------------------
/Sources/StringCase/StringCase.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | public extension String {
4 | /// A Boolean value indicating whether this string is considered snake case.
5 | ///
6 | /// For example, the following strings are all snake case:
7 | ///
8 | /// - "snake_case"
9 | /// - "example"
10 | /// - "date_formatter"
11 | ///
12 | /// String can contain lowercase letters and underscores only.
13 | /// In snake case, words are separated by underscores.
14 | var isSnakeCase: Bool {
15 | // Strip all underscores and check if the rest is lowercase
16 | return self.filter{ $0 != "_" }.allSatisfy { $0.isLowercase }
17 | }
18 |
19 | /// A Boolean value indicating whether this string is considered lower camel case.
20 | ///
21 | /// For example, the following strings are all lower camel case:
22 | ///
23 | /// - "lowerCamelCase"
24 | /// - "example"
25 | /// - "dateFormatter"
26 | ///
27 | /// String can contain lowercase and uppercase letters only.
28 | /// In lower camel case, words are separated by uppercase letters.
29 | var isLowerCamelCase: Bool {
30 | // Check if the first character is lowercase and the rest contains letters
31 | if let firstCharacter = self.first, firstCharacter.isLowercase && self.allSatisfy { $0.isLetter } {
32 | return true
33 | }
34 | return false
35 | }
36 |
37 | /// A Boolean value indicating whether this string is considered upper camel case.
38 | ///
39 | /// For example, the following strings are all upper camel case:
40 | ///
41 | /// - "UpperCamelCase"
42 | /// - "Example"
43 | /// - "DateFormatter"
44 | ///
45 | /// String can contain lowercase and uppercase letters only.
46 | /// In upper camel case, words are separated by uppercase letters.
47 | var isUpperCamelCase: Bool {
48 | // Check if the first character is uppercase and the rest contains letters
49 | if let firstCharacter = self.first, firstCharacter.isUppercase && self.allSatisfy { $0.isLetter } {
50 | return true
51 | }
52 | return false
53 | }
54 | }
55 |
56 | public extension String {
57 | /// Splits given string by variations between two characters and
58 | /// returns and array of strings.
59 | ///
60 | /// In this example, `lowercasedStrings` is used first to convert the names in the array
61 | /// to lowercase strings and then to count their characters.
62 | private func lowercasedStrings() -> [String] {
63 | var lastCharacter: Character = " "
64 | var results: [String] = []
65 |
66 | for character in Array(self) {
67 | if results.isEmpty && character.isLetter {
68 | results.append(String(character))
69 | } else if lastCharacter.isLetter && character.isLowercase {
70 | results[results.count - 1] = results[results.count - 1] + String(character)
71 | } else if character.isLetter {
72 | results.append(String(character))
73 | }
74 | lastCharacter = character
75 | }
76 |
77 | return results.map { $0.capitalized }
78 | }
79 |
80 | /// Returns a lower camel case version of the string.
81 | ///
82 | /// Here's an example of transforming a string to lower camel case.
83 | ///
84 | /// let event = "Keynote Event"
85 | /// print(event.lowerCamelCased())
86 | /// // Prints "keynoteEvent"
87 | ///
88 | /// - Returns: A lower camel case copy of the string.
89 | func lowerCamelCased() -> String {
90 | if self.isLowerCamelCase { return self }
91 | var strings = lowercasedStrings()
92 | if let firstString = strings.first {
93 | strings[0] = firstString.lowercased()
94 | }
95 | return strings.joined()
96 | }
97 |
98 | /// Returns an upper camel case version of the string.
99 | ///
100 | /// Here's an example of transforming a string to upper camel case.
101 | ///
102 | /// let event = "Keynote Event"
103 | /// print(event.upperCamelCased())
104 | /// // Prints "KeynoteEvent"
105 | ///
106 | /// - Returns: An upper camel case copy of the string.
107 | func upperCamelCased() -> String {
108 | if self.isUpperCamelCase { return self }
109 | return lowercasedStrings().joined()
110 | }
111 |
112 | /// Returns snake case version of the string.
113 | ///
114 | /// Here's an example of transforming a string to snake case.
115 | ///
116 | /// let event = "Keynote Event"
117 | /// print(event.snakeCased())
118 | /// // Prints "keynote_event"
119 | ///
120 | /// - Returns: A snake case copy of the string.
121 | func snakeCased() -> String {
122 | if self.isSnakeCase { return self }
123 | return lowercasedStrings().map{ $0.lowercased() }.joined(separator: "_")
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/Tests/LinuxMain.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | import StringCaseTests
4 |
5 | var tests = [XCTestCaseEntry]()
6 | tests += StringCaseTests.allTests()
7 | XCTMain(tests)
8 |
--------------------------------------------------------------------------------
/Tests/StringCaseTests/StringCaseTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import StringCase
3 |
4 | final class StringCaseTests: XCTestCase {
5 | func testSnakeCase() throws {
6 | XCTAssertTrue("snake".isSnakeCase)
7 | XCTAssertTrue("snake_case".isSnakeCase)
8 | XCTAssertTrue("snake_case_example".isSnakeCase)
9 | XCTAssertFalse("not_a_SNAKECASE_String".isSnakeCase)
10 | XCTAssertFalse("notSnakeCase".isSnakeCase)
11 | XCTAssertFalse("AlsoNotSnakeCase".isSnakeCase)
12 |
13 | XCTAssertEqual("snake".snakeCased(), "snake")
14 | XCTAssertEqual("snake cased".snakeCased(), "snake_cased")
15 | XCTAssertEqual("snakeCased".snakeCased(), "snake_cased")
16 | XCTAssertEqual("snake Cased_String".snakeCased(), "snake_cased_string")
17 | XCTAssertEqual("_this is* not-Very%difficult".snakeCased(), "this_is_not_very_difficult")
18 | }
19 |
20 | func testLowerCamelCase() throws {
21 | XCTAssertTrue("lower".isLowerCamelCase)
22 | XCTAssertTrue("lowerCamelCase".isLowerCamelCase)
23 | XCTAssertFalse("lowerCamelCase_with_underscore".isLowerCamelCase)
24 | XCTAssertFalse("UpperCamelCase".isLowerCamelCase)
25 | XCTAssertFalse("snake_case".isLowerCamelCase)
26 |
27 | XCTAssertEqual("lower".lowerCamelCased(), "lower")
28 | XCTAssertEqual("LowerCamelCased".lowerCamelCased(), "lowerCamelCased")
29 | XCTAssertEqual("lower_camel_cased".lowerCamelCased(), "lowerCamelCased")
30 | XCTAssertEqual("Lower Camel cased".lowerCamelCased(), "lowerCamelCased")
31 | XCTAssertEqual("_this is* not-Very%difficult".lowerCamelCased(), "thisIsNotVeryDifficult")
32 | }
33 |
34 | func testUpperCamelCase() throws {
35 | XCTAssertTrue("Upper".isUpperCamelCase)
36 | XCTAssertTrue("UpperCamelCase".isUpperCamelCase)
37 | XCTAssertTrue("UpperCamelCaseExample".isUpperCamelCase)
38 | XCTAssertFalse("UpperCamelCase_with_underscore".isUpperCamelCase)
39 | XCTAssertFalse("snake_case".isUpperCamelCase)
40 | XCTAssertFalse("lowerCamelCase".isUpperCamelCase)
41 |
42 | XCTAssertEqual("Upper".upperCamelCased(), "Upper")
43 | XCTAssertEqual("upperCamelCased".upperCamelCased(), "UpperCamelCased")
44 | XCTAssertEqual("upper_camel_cased".upperCamelCased(), "UpperCamelCased")
45 | XCTAssertEqual("upper_camel Cased".upperCamelCased(), "UpperCamelCased")
46 | XCTAssertEqual("_this is* not-Very%difficult".upperCamelCased(), "ThisIsNotVeryDifficult")
47 | }
48 |
49 | static var allTests = [
50 | ("testSnakeCase", testSnakeCase),
51 | ("testLowerCamelCase", testLowerCamelCase),
52 | ("testUpperCamelCase", testUpperCamelCase),
53 | ]
54 | }
55 |
--------------------------------------------------------------------------------
/Tests/StringCaseTests/XCTestManifests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | #if !canImport(ObjectiveC)
4 | public func allTests() -> [XCTestCaseEntry] {
5 | return [
6 | testCase(StringCaseTests.allTests),
7 | ]
8 | }
9 | #endif
10 |
--------------------------------------------------------------------------------