Case Converter — Swift source
Convert text between UPPER, lower, Title, Sentence, camelCase, PascalCase, snake_case, kebab-case, CONSTANT_CASE and slug. Live, one-click copy.
This is the Swift implementation — the same logic the interactive tool runs, in a shareable, citable form.
// case-converter — pure, deterministic text-case transformations.
//
// Language: Swift 5.9 (standard library only)
// Source: CosmoDev polyglot showcase port of the Case Converter tool.
// Canonical: src/lib/case.ts (the live TypeScript) plus this
// tool's python.py / rust.rs reference ports.
// License: display source — part of CosmoDev's polyglot tool pages.
//
// Split any input into word tokens and re-emit it in upper / lower / title /
// sentence / camel / pascal / snake / kebab / constant / slug case — same
// inputs produce the same outputs as the TypeScript original, including edge
// cases. Swift String case mapping is Unicode-aware (uppercased() /
// lowercased()), matching JavaScript's toUpperCase / toLowerCase; the token
// alphabet itself is ASCII ([A-Za-z0-9]), so the classifiers below gate on
// isASCII exactly like the reference's regex classes.
import Foundation
/// Identifier for one of the supported case transformations.
///
/// A RawRepresentable enum plus `convert` is the idiomatic Swift equivalent
/// of the TypeScript `caseTransforms` object + `CaseId` union: exhaustive,
/// statically dispatched, and impossible to get out of sync at runtime.
/// `allCases` iterates in declaration order — the same order as the TS keys.
enum CaseId: String, CaseIterable {
case upper, lower, title, sentence, camel, pascal, snake, kebab, constant, slug
}
/// JavaScript's \w = [A-Za-z0-9_].
private func isWordChar(_ c: Character) -> Bool {
c.isASCII && (c.isLetter || c.isNumber || c == "_")
}
/// An ASCII letter or digit — the token alphabet of the reference.
private func isAsciiAlnum(_ c: Character) -> Bool {
c.isASCII && (c.isLetter || c.isNumber)
}
/// Close the current token, if any, pushing it into the output.
private func flush(_ current: inout String, _ out: inout [String]) {
if !current.isEmpty {
out.append(current)
current = ""
}
}
/// Tokenize `s` into word units.
///
/// Mirrors the TypeScript reference exactly, as one fused forward pass:
/// 1. Break at every camelCase boundary (a lowercase letter or digit
/// immediately followed by an uppercase letter).
/// 2. Break on runs of whitespace / underscore / hyphen / dot.
/// 3. Strip any remaining non-alphanumeric characters and drop empty tokens.
func words(_ s: String) -> [String] {
let chars = Array(s)
var out: [String] = []
var current = ""
for (i, c) in chars.enumerated() {
// 1. camelCase boundary: the previous char was lowercase-or-digit and
// this one is uppercase — close the current token.
if i > 0 {
let prev = chars[i - 1]
let prevIsLowerOrDigit = prev.isASCII && (prev.isLowercase || prev.isNumber)
let cIsUpper = c.isASCII && c.isUppercase
if prevIsLowerOrDigit && cIsUpper {
flush(¤t, &out)
}
}
if c.isWhitespace || c == "_" || c == "-" || c == "." {
// 2. delimiter — end the current token.
flush(¤t, &out)
} else if isAsciiAlnum(c) {
// 3a. keep ASCII letters and digits.
current.append(c)
}
// 3b. anything else (punctuation, symbols) is silently dropped,
// matching the reference's `[^a-zA-Z0-9]` stripping.
}
flush(¤t, &out)
return out
}
/// Upper-case every character.
func upper(_ s: String) -> String { s.uppercased() }
/// Lower-case every character.
func lower(_ s: String) -> String { s.lowercased() }
/// Capitalize the first character and lowercase the rest.
func cap(_ s: String) -> String {
guard let first = s.first else { return "" }
return String(first).uppercased() + s.dropFirst().lowercased()
}
/// Title case: capitalize the first character of each maximal word-run.
///
/// A "word-run" starts at an ASCII word character ([A-Za-z0-9_]) and runs to
/// the next whitespace, mirroring JavaScript's /\w\S*/g. Characters between
/// runs (spacing, punctuation) are preserved verbatim — only each run's first
/// character is touched.
func title(_ s: String) -> String {
let chars = Array(s)
var out = ""
var i = 0
while i < chars.count {
if isWordChar(chars[i]) {
// Consume the whole `\w\S*` run up to the next whitespace.
let start = i
i += 1
while i < chars.count && !chars[i].isWhitespace {
i += 1
}
out += cap(String(chars[start..<i]))
} else {
// Not the start of a word-run: copy verbatim.
out.append(chars[i])
i += 1
}
}
return out
}
/// Sentence case: lowercase + trim the whole string, then uppercase the first
/// character. Empty (or all-whitespace) input yields empty output.
func sentence(_ s: String) -> String {
let t = s.lowercased().trimmingCharacters(in: .whitespacesAndNewlines)
guard let first = t.first else { return "" }
return String(first).uppercased() + t.dropFirst()
}
/// camelCase: the first word stays lowercase, every later word is capitalized.
func camel(_ s: String) -> String {
var out = ""
for (i, w) in words(s).enumerated() {
out += i == 0 ? w.lowercased() : cap(w)
}
return out
}
/// PascalCase: every word capitalized and concatenated.
func pascal(_ s: String) -> String {
var out = ""
for w in words(s) {
out += cap(w)
}
return out
}
func snake(_ s: String) -> String { words(s).joined(separator: "_").lowercased() }
func kebab(_ s: String) -> String { words(s).joined(separator: "-").lowercased() }
func constant(_ s: String) -> String { words(s).joined(separator: "_").uppercased() }
func slug(_ s: String) -> String { words(s).joined(separator: "-").lowercased() }
/// Apply the case transform identified by `id`.
///
/// String-keyed dispatch is available via `tryConvert`:
/// `CaseId(rawValue: name).map { convert($0, s) }`.
func convert(_ id: CaseId, _ s: String) -> String {
switch id {
case .upper: return upper(s)
case .lower: return lower(s)
case .title: return title(s)
case .sentence: return sentence(s)
case .camel: return camel(s)
case .pascal: return pascal(s)
case .snake: return snake(s)
case .kebab: return kebab(s)
case .constant: return constant(s)
case .slug: return slug(s)
}
}
/// Like `convert` but keyed by the raw string form; nil for an unknown case id.
func tryConvert(_ caseId: String, _ s: String) -> String? {
guard let id = CaseId(rawValue: caseId) else { return nil }
return convert(id, s)
}
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →