function lowerToPascalCase(str: string) { return str.replace(/(?:^|\s)\w/g, (match) => match.toUpperCase()).replaceAll( " ", "", ); } function lowerToTrainCase(str: string) { return str.replace(/(?:^|\s)\w/g, (match) => match.toUpperCase()).replaceAll( " ", "-", ); } /** * @param str * @returns camelCased string (single letter words are lower cased, e.g. SSN -> ssn) */ function lowerToCamelCase(str: string): string { const words = str.trim().split(/\s+/); const result: string[] = []; let i = 0; while (i < words.length) { if (words[i].length === 1) { // We’ve hit the start of a chain of single-letter words let j = i; while (j < words.length && words[j].length === 1) { j++; } const chainIsAtStart = i === 0; // Process that entire chain for (let k = i; k < j; k++) { result[k] = chainIsAtStart ? words[k].toLowerCase() : words[k].toUpperCase(); } i = j; } else { // Normal multi-letter word if (i === 0) { // first word: all lower result[i] = words[i].toLowerCase(); } else { // subsequent words: capitalize first letter result[i] = words[i][0].toUpperCase() + words[i].slice(1).toLowerCase(); } i++; } } return result.join(""); } function lowerToSnakeCase(str: string) { return str.replace(" ", "_"); } function lowerToKebabCase(str: string) { return str.replace(" ", "-"); } function lowerToMacroCase(str: string) { return str.replace(/\w\S*/g, (match) => match.toUpperCase()).replaceAll( " ", "_", ); } function lowerToTitleCase(str: string) { return str.replace(/(?:^|\s)\w/g, (match) => match.toUpperCase()); } type CaseType = | "pascal" | "camel" | "snake" | "kebab" | "macro" | "upper" | "lower" | "train" | "title" | ""; function parseCase(str: string) { const isCaseMap = new Map boolean>([ ["pascal", (str: string) => { return /^[A-Z][a-zA-Z]*$/.test(str); }], ["camel", (str: string) => { return /^[a-z][a-zA-Z]*$/.test(str); }], ["snake", (str: string) => { return /^[a-z][a-z0-9_]*$/.test(str); }], ["kebab", (str: string) => { return /^[a-z][a-z0-9-]*$/.test(str); }], ["macro", (str: string) => { return /^[A-Z]*$/.test(str); }], ["upper", (str: string) => { return /^[A-Z]*$/.test(str); }], ["lower", (str: string) => { return /^[a-z]*$/.test(str); }], ["train", (str: string) => { return /([A-Z][a-z]*(?:-|$))+/.test(str); }], ]); for (const [key, value] of isCaseMap) { if (value(str)) return key; } return ""; } function coerceCaseToLower(str: string, caseType: CaseType) { switch (caseType) { case "pascal": case "camel": return str.replace(/[A-Z]/g, (match) => " " + match.toLowerCase().trim()); case "macro": case "snake": case "upper": return str.replaceAll("_", " ").toLowerCase(); case "train": case "kebab": return str.replaceAll("-", " ").toLowerCase(); default: return str.toLowerCase(); } } export function toCase(str: string, toCase: CaseType) { const caseType = parseCase(str) || ""; if (caseType === toCase) return str; const lowerStr = coerceCaseToLower(str, caseType); switch (toCase) { case "pascal": return lowerToPascalCase(lowerStr); case "camel": return lowerToCamelCase(lowerStr); case "snake": return lowerToSnakeCase(lowerStr); case "kebab": return lowerToKebabCase(lowerStr); case "macro": return lowerToMacroCase(lowerStr); case "upper": return lowerStr.toUpperCase(); case "lower": return lowerStr.toLowerCase(); case "train": return lowerToTrainCase(lowerStr); case "title": return lowerToTitleCase(lowerStr); default: return str; } } if (import.meta.main) { console.log(toCase("SSN", "camel")); }