runlocally

runlocally engineering notes

Convert Case

How Convert Case is built

By Geppetto · · Open Convert Case →

Convert Case converts identifiers between camelCase, PascalCase, snake_case, kebab-case, CONSTANT_CASE, Title Case, and dot.case. This post is about the one function that has to be right for all seven output formats to be right: splitting the input into words in the first place.

Tech used

A single tokenizer feeding seven formatters

The engine is one tokenize() function that turns any input string into an array of words, followed by seven small formatters that each just join that array back together with a different separator and casing rule. Getting the seven outputs right is entirely downstream of getting tokenize() right — there’s no per-format splitting logic to get out of sync. No dependency is involved; the whole thing is string iteration and regular expressions.

Implementation & operational notes

Consecutive capital letters are the tokenizer’s hardest case, because the naive rule (split before every uppercase letter) is wrong. Applied naively, HTTPResponse splits into H, T, T, P, Response — every letter of the acronym becomes its own word. The correct read is that HTTP is one word and Response is the next, which means a run of capitals has to be split before its last letter, not before every letter, so that the last capital in the run starts the following word (HTTPResponseHTTP + Response, and parseXMLDocumentparse + XML + Document). Getting this rule backwards is the single most common bug in hand-rolled case converters.

Digits attach to the word before them, not the word after. user2FA tokenizes to user2 + FA, and oauth2Token to oauth2 + Token — the digit is treated as part of the preceding identifier rather than as its own boundary or the start of the next word. This matches how these identifiers are actually read: oauth2 is one concept, not oauth followed by a stray 2.

Existing separators and non-ASCII text both have to survive tokenization without corruption. An input that already mixes _, -, spaces, and . needs those treated as word boundaries directly rather than re-derived from casing. And a string like 日本語HTTPResponse — non-ASCII text sitting next to an identifier — has to keep the non-ASCII run intact as its own token while still correctly splitting the acronym boundary in the ASCII portion beside it, rather than one polluting the other’s boundary detection.

An optional acronym-preserving toggle exists because “correct” tokenization still leaves a judgment call. Once HTTPResponse is correctly split into HTTP + Response, converting to Title Case still has to decide between Http Response and HTTP Response — both are defensible, and which one a given codebase wants is a matter of house style, not correctness. The toggle exposes that choice instead of picking one silently.

This is a case where the honest thing to say is that the tool doesn’t have much of a privacy story. Nothing here processes sensitive data by nature — it’s identifier strings, not documents or photos — so the copy doesn’t reach for a privacy angle it doesn’t have. The pitch is just what the tool does: convert case, entirely in the browser, without pretending that matters more than it does for this particular kind of input.

Try it / source