Package for tokenizing and applying case transformations to arbitrary strings.
Swift does not provide a standard way to tokenize strings or to build formatted strings from such tokens.
As a result, even common transformations like camelCase are often implemented differently across projects, leading to duplicated logic and inconsistent results.
This package focuses on providing a set of predefined, reusable string modifiers for common casing and formatting transformations, with consistent behavior across codebases.
A set of predefined string modifiers is available out of the box
| Modifiers | Examples |
|---|---|
upper |
some string → SOME STRING |
lower |
Some string → some string |
upperFirst |
some String → Some String |
lowerFirst |
Some string → some String |
capital |
some string → Some String |
swap |
Some String → sOME sTRING |
Those modifiers are a bit more complex and do support configuration
| Modifiers | Examples |
|---|---|
camel(.camel) |
camel |
camel(.pascal) |
pascal |
camel |
camel(.camel) |
pascal |
camel(.pascal) |
camel(.automatic) |
camel() |
camel(.automatic) |
camel() |
snake |
some string 1 x value → some_string_1x_value |
kebab |
some string 1 x value → some-string-1x-value |
dot |
some string 1 x value → some.string.1x.value |
Note
See Tests for more examples
Tokenization uses list of acronyms to properly handle common values like UUID, but the list is limited and statically defined, which may lead to tokenization misses for such values. Tokenization misses will lead to incorrect formatting for some modifiers.
There are 2 ways to override existing acronyms
First one is overriding default values globally using prepareConfiguration(_:) function and should be performed at the application start (as early as possible):
String.Casification.prepareConfiguration {
$0.acronyms.formUnion(["uml", "Uml", "UML"])
}
"uml_string".case(.pascal) // UMLStringSecond one is providing contextual override using withAcronyms(_:operation:) function:
"uml_string".case(.pascal) // UmlString
withAcronyms { $0
.formUnion(["uml", "Uml", "UML"])
} operation: {
"uml_string".case(.pascal) // UMLString
}Tip
Explore Configuration to find out more about available options, for example predefined modifiers do handle numbers gently out-of-the box:
"1.23 in a Sentence".case(.pascal) → "1_23_InASentence"
"Lens1x".case(.snake) → "lens_1x" instead of "lens_1_x"
"Grid1x1".case(.camel) → "grid_1x1" instead of "grid_1_X_1"
Though you can disable this behavior with:
String.Casification.prepareConfiguration {
// Default is `[.singleLetter([.disableSeparators, .disableNextTokenProcessing])]`
$0.common.numbers.boundaryOptions = []
// You can also enable allowed delimeters
// "1.23 String".case(.snake) -> 1.23_string
$0.common.numbers.allowedDelimeters = ["."]
}Camel case modifiers do also support advanced configuration
You can use explicit parameters:
"string_id".case(.camel()) // stringID
withCasification { $0.camelCase.acronyms.processingPolicy = .alwaysCapitalize } operation: {
"string_id".case(.camel(.)) // stringId
}You can override default config using prepareConfiguration(_:) function and it also should be performed at the application start (as early as possible):
String.Casification.prepareConfiguration {
$0.camelCase.acronyms.processingPolicy = .alwaysCapitalize
}
"string_id".case(.camel()) // stringIdModifiers can be combined using combined(with:) method. Order matters – transformations are applied sequentially.
// "myString" → "mystring" → "Mystring"
"myString".case(.lower.combined(with: .upperFirst))// "myString" → "Mystring" → "mystring"
"myString".case(.upperFirst.combined(with: .lower)) // mystringFor simple modifiers, conforming a type to String.Casification.Modifier is enough
extension String.Casification.Modifiers {
/// Deletes input string
public struct Delete: String.Casification.Modifier {
public init() {}
@inlinable
public func transform(_ input: Substring) -> Substring {
""
}
}
}Tip
It's a good idea to declare convenience accessor for the protocol
extension String.Casification.Modifier
where Self == String.Casification.Modifiers.Delete {
public var delete: Self { .init() }
}"myString".case(.delete) // ""For more complex processing, you can operate on tokens instead of raw strings by conforming to String.Casification.TokensProcessor
extension String.Casification.TokensProcessors {
public struct RemoveSeparators: String.Casification.TokensProcessor {
public init() {}
@inlinable
public func processTokens(
_ tokens: ArraySlice<String.Casification.Token>
) -> ArraySlice<String.Casification.Token> {
return tokens.filter { $0.kind != .separator }[...]
}
}
}
extension String.Casification.TokensProcessor
where Self == String.Casification.TokensProcessors.RemoveSeparators {
public static var removeSeparators: Self { .init() }
}
extension String.Casification.Modifier
where Self == String.Casification.Modifiers.AnyModifier {
public var noSeparators: Self {
.processingTokens(with: .removeSeparators)
}
}"my test-string".case(.noSeparators) // "myteststring"Note
The package is primarily designed around predefined reusable modifiers. Custom modifiers are supported, but declarations can be verbose due to namespacing and generic types.
You can add swift-casification to an Xcode project by adding it as a package dependency.
- From the File menu, select Swift Packages › Add Package Dependency…
- Enter
"https://github.com/capturecontext/swift-casification"into the package repository URL text field - Choose products you need to link to your project.
If you use SwiftPM for your project structure, add swift-casification dependency to your package file
.package(
url: "https://github.com/capturecontext/swift-casification.git",
.upToNextMinor("0.5.0")
)Do not forget about target dependencies
.product(
name: "Casification",
package: "swift-casification"
)This library is released under the MIT license. See LICENSE for details.