Case Converter — Kotlin 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 Kotlin implementation — the same logic the interactive tool runs, in a shareable, citable form.
// case-converter — pure, deterministic text-case transformations.
//
// Language: Kotlin 1.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. Case mapping is Unicode-aware and locale-invariant
// (String.uppercase() / lowercase()), matching JavaScript.
// --- Compiled patterns -------------------------------------------------------
// Each mirrors a regex from the TypeScript reference. The inline (?U) flag
// widens \s / \S to Unicode whitespace (java.util.regex's
// UNICODE_CHARACTER_CLASS), matching the Python and Rust ports; the explicit
// [a-z0-9] / [A-Za-z0-9_] classes stay ASCII exactly like JavaScript's.
// A camelCase seam: a lowercase letter or digit immediately followed by an
// uppercase letter. We splice a space in here before splitting.
private val CAMEL_BOUNDARY = Regex("([a-z0-9])([A-Z])")
// Word delimiters: any run of whitespace, underscore, hyphen, or dot.
private val SEPARATORS = Regex("(?U)[\\s_\\-.]+")
// Anything that is not an ASCII letter or digit — stripped from each token.
private val NON_ALNUM = Regex("[^a-zA-Z0-9]")
// A title "word": a maximal run that starts with an ASCII word character and
// continues with non-whitespace (JavaScript's /\w\S*/g, \w spelled out).
private val TITLE_WORD = Regex("(?U)[A-Za-z0-9_]\\S*")
/**
* Tokenize [s] into word units.
*
* The pipeline reproduces the TypeScript reference step for step:
* 1. Insert a space at every camelCase boundary.
* 2. Split on runs of whitespace / underscore / hyphen / dot.
* 3. Strip remaining non-alphanumerics and drop empty tokens.
*/
fun words(s: String): List<String> =
SEPARATORS.split(CAMEL_BOUNDARY.replace(s, "$1 $2"))
.map { NON_ALNUM.replace(it, "") }
.filter { it.isNotEmpty() } // filter(Boolean) in the TS source
/** Capitalize the first character and lowercase the rest. */
fun cap(s: String): String =
if (s.isEmpty()) s else s[0].uppercaseChar().toString() + s.substring(1).lowercase()
/** Upper-case every character. */
fun upper(s: String): String = s.uppercase()
/** Lower-case every character. */
fun lower(s: String): String = s.lowercase()
/**
* Title case: capitalize the first character of each maximal word-run.
* Characters between runs (spacing, punctuation) are left untouched.
*/
fun title(s: String): String = TITLE_WORD.replace(s) { cap(it.value) }
/**
* Sentence case: lowercase + trim the whole string, then uppercase only the
* first character. Empty input yields empty output.
*/
fun sentence(s: String): String {
val t = s.lowercase().trim()
return if (t.isEmpty()) t else t[0].uppercaseChar().toString() + t.substring(1)
}
/** camelCase: the first word stays lowercase, every later word is capitalized. */
fun camel(s: String): String =
words(s).mapIndexed { i, w -> if (i == 0) w.lowercase() else cap(w) }.joinToString("")
/** PascalCase: every word capitalized and concatenated. */
fun pascal(s: String): String = words(s).joinToString("") { cap(it) }
/** snake_case — "_".join(words).lower() in the reference. */
fun snake(s: String): String = words(s).joinToString("_").lowercase()
/** kebab-case — "-".join(words).lower() in the reference. */
fun kebab(s: String): String = words(s).joinToString("-").lowercase()
/** CONSTANT_CASE — "_".join(words).upper() in the reference. */
fun constant(s: String): String = words(s).joinToString("_").uppercase()
/** slug (kebab's URL-safe twin) — "-".join(words).lower() in the reference. */
fun slug(s: String): String = words(s).joinToString("-").lowercase()
/**
* The case-transform registry: CaseId -> pure function. Keys mirror the
* TypeScript `caseTransforms` object 1:1; LinkedHashMap preserves insertion
* order and the map is read-only.
*/
val caseTransforms: Map<String, (String) -> String> = linkedMapOf(
"upper" to ::upper,
"lower" to ::lower,
"title" to ::title,
"sentence" to ::sentence,
"camel" to ::camel,
"pascal" to ::pascal,
"snake" to ::snake,
"kebab" to ::kebab,
"constant" to ::constant,
"slug" to ::slug,
)
/** Valid case identifiers, in registry order. */
val caseIds: List<String> = caseTransforms.keys.toList()
/**
* Apply a named case transform.
*
* @throws IllegalArgumentException if [caseId] is not a known case
*/
fun convert(caseId: String, s: String): String =
caseTransforms[caseId]?.invoke(s)
?: throw IllegalArgumentException("Unknown case: $caseId")
/** Like [convert] but returns null for an unknown case id. */
fun tryConvert(caseId: String, s: String): String? = caseTransforms[caseId]?.invoke(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 →