Last active
August 12, 2022 23:24
-
-
Save hashd/1fd8245483a1dab616c50215dae6a2c0 to your computer and use it in GitHub Desktop.
Casing Utilities - TypeScript
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
enum CharType { | |
NUMBER, | |
LOWERCASE_CHARACTER, | |
UPPERCASE_CHARACTER, | |
WHITESPACE, | |
OTHER, | |
} | |
type TRange = { | |
length: number; | |
ranges: [number, number][]; | |
}; | |
function classifyChar(ch): CharType { | |
if (ch >= "a" && ch <= "z") { | |
return CharType.LOWERCASE_CHARACTER; | |
} | |
if (ch >= "A" && ch <= "Z") { | |
return CharType.UPPERCASE_CHARACTER; | |
} | |
if (ch >= "0" && ch <= "9") { | |
return CharType.NUMBER; | |
} | |
if (["-", "_", "\t", ".", "\n", "\r", " "].indexOf(ch) >= 0) { | |
return CharType.WHITESPACE; | |
} | |
return CharType.OTHER; | |
} | |
function genericSplit(str: string) { | |
const charTypes = []; | |
for (let i = 0; i < str.length; i++) { | |
charTypes.push(classifyChar(str.charAt(i))); | |
} | |
function resultPlusNew(result: TRange, start: number, end: number) { | |
if (end > start) { | |
result.ranges.push([start, end]); | |
result.length = result.length + (end - start); | |
} | |
} | |
function loop(result: TRange, start: number, current: number) { | |
const next = current + 1; | |
if (current >= str.length) { | |
resultPlusNew(result, start, current); | |
return; | |
} else if (charTypes[current] === CharType.WHITESPACE) { | |
resultPlusNew(result, start, current); | |
return loop(result, next, next); | |
} else { | |
const currCharType = charTypes[current]; | |
const nextCharType = charTypes[current + 1]; | |
const skipLevelCharType = charTypes[current + 2]; | |
if ( | |
currCharType === CharType.UPPERCASE_CHARACTER && | |
nextCharType === CharType.UPPERCASE_CHARACTER && | |
skipLevelCharType === CharType.LOWERCASE_CHARACTER | |
) { | |
resultPlusNew(result, start, next); | |
return loop(result, next, next); | |
} else if ( | |
nextCharType === CharType.UPPERCASE_CHARACTER && | |
currCharType !== CharType.UPPERCASE_CHARACTER | |
) { | |
resultPlusNew(result, start, next); | |
return loop(result, next, next); | |
} else if ( | |
nextCharType === CharType.NUMBER && | |
currCharType !== CharType.NUMBER | |
) { | |
resultPlusNew(result, start, next); | |
return loop(result, next, next); | |
} else { | |
return loop(result, start, next); | |
} | |
} | |
} | |
const res: TRange = { | |
ranges: [], | |
length: 0, | |
}; | |
loop(res, 0, 0); | |
return res; | |
} | |
function genericToCase( | |
str: string, | |
firstCharMutateFn, | |
restMutateFn, | |
joinStr = "" | |
): string { | |
const result = genericSplit(str); | |
let length = result.length; | |
if (length === 0) { | |
return ""; | |
} | |
const ranges = result.ranges; | |
const nParts = ranges.length; | |
switch (joinStr) { | |
case undefined: | |
case null: | |
break; | |
default: | |
length = length + nParts - 1; | |
} | |
const finalParts = []; | |
ranges.reduce((pos, [start, end]) => { | |
let i = pos; | |
if (joinStr && i > 0) { | |
finalParts[i] = joinStr; | |
pos = pos + 1; | |
} | |
i = pos; | |
let char = str.charAt(start); | |
char = firstCharMutateFn(i, char); | |
finalParts[i] = char; | |
pos = pos + 1; | |
for (i = start + 1; i < end; i = i + 1) { | |
finalParts.push(restMutateFn(str.charAt(i))); | |
pos = pos + 1; | |
} | |
return pos; | |
}, 0); | |
// logger.info( | |
// ranges.map(([start, end]) => str.substring(start, end)).join(", ") | |
// ); | |
return finalParts.join(joinStr); | |
} | |
export function camelCase(str: string): string { | |
return genericToCase( | |
str, | |
(idx, char) => (idx === 0 ? char.toLowerCase() : char.toUpperCase()), | |
(char) => char.toLowerCase() | |
); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment