// COPY OF THIS: https://github.com/blakeembrey/change-case/blob/main/packages/change-case/src/index.ts // Regexps involved with splitting words in various case formats. const SPLIT_LOWER_UPPER_RE = /([\p{Ll}\d])(\p{Lu})/gu const SPLIT_UPPER_UPPER_RE = /(\p{Lu})([\p{Lu}][\p{Ll}])/gu // Used to iterate over the initial split result and separate numbers. const SPLIT_SEPARATE_NUMBER_RE = /(\d)\p{Ll}|(\p{L})\d/u // Regexp involved with stripping non-word characters from the result. const DEFAULT_STRIP_REGEXP = /[^\p{L}\d]+/giu // The replacement value for splits. const SPLIT_REPLACE_VALUE = '$1\0$2' // The default characters to keep after transforming case. const DEFAULT_PREFIX_SUFFIX_CHARACTERS = '' /** * Supported locale values. Use `false` to ignore locale. * Defaults to `undefined`, which uses the host environment. */ export type Locale = string[] | string | false | undefined /** * Options used for converting strings to pascal/camel case. */ export interface PascalCaseOptions extends Options { mergeAmbiguousCharacters?: boolean } /** * Options used for converting strings to any case. */ export interface Options { locale?: Locale split?: (value: string) => string[] /** @deprecated Pass `split: splitSeparateNumbers` instead. */ separateNumbers?: boolean delimiter?: string prefixCharacters?: string suffixCharacters?: string } /** * Split any cased input strings into an array of words. */ export function split(value: string) { let result = value.trim() result = result .replace(SPLIT_LOWER_UPPER_RE, SPLIT_REPLACE_VALUE) .replace(SPLIT_UPPER_UPPER_RE, SPLIT_REPLACE_VALUE) result = result.replace(DEFAULT_STRIP_REGEXP, '\0') let start = 0 let end = result.length // Trim the delimiter from around the output string. while (result.charAt(start) === '\0') start++ if (start === end) return [] while (result.charAt(end - 1) === '\0') end-- return result.slice(start, end).split(/\0/g) } /** * Split the input string into an array of words, separating numbers. */ export function splitSeparateNumbers(value: string) { const words = split(value) for (let i = 0; i < words.length; i++) { const word = words[i] const match = SPLIT_SEPARATE_NUMBER_RE.exec(word) if (match) { const offset = match.index + (match[1] ?? match[2]).length words.splice(i, 1, word.slice(0, offset), word.slice(offset)) } } return words } /** * Convert a string to space separated lower case (`foo bar`). */ export function noCase(input: string, options?: Options) { const [prefix, words, suffix] = splitPrefixSuffix(input, options) return prefix + words.map(lowerFactory(options?.locale)).join(options?.delimiter ?? ' ') + suffix } /** * Convert a string to camel case (`fooBar`). */ export function camelCase(input: string, options?: PascalCaseOptions) { const [prefix, words, suffix] = splitPrefixSuffix(input, options) const lower = lowerFactory(options?.locale) const upper = upperFactory(options?.locale) const transform = options?.mergeAmbiguousCharacters ? capitalCaseTransformFactory(lower, upper) : pascalCaseTransformFactory(lower, upper) return ( prefix + words .map((word, index) => { if (index === 0) return lower(word) return transform(word, index) }) .join(options?.delimiter ?? '') + suffix ) } /** * Convert a string to pascal case (`FooBar`). */ export function pascalCase(input: string, options?: PascalCaseOptions) { const [prefix, words, suffix] = splitPrefixSuffix(input, options) const lower = lowerFactory(options?.locale) const upper = upperFactory(options?.locale) const transform = options?.mergeAmbiguousCharacters ? capitalCaseTransformFactory(lower, upper) : pascalCaseTransformFactory(lower, upper) return prefix + words.map(transform).join(options?.delimiter ?? '') + suffix } /** * Convert a string to pascal snake case (`Foo_Bar`). */ export function pascalSnakeCase(input: string, options?: Options) { return capitalCase(input, { delimiter: '_', ...options }) } /** * Convert a string to capital case (`Foo Bar`). */ export function capitalCase(input: string, options?: Options) { const [prefix, words, suffix] = splitPrefixSuffix(input, options) const lower = lowerFactory(options?.locale) const upper = upperFactory(options?.locale) return ( prefix + words.map(capitalCaseTransformFactory(lower, upper)).join(options?.delimiter ?? ' ') + suffix ) } /** * Convert a string to constant case (`FOO_BAR`). */ export function constantCase(input: string, options?: Options) { const [prefix, words, suffix] = splitPrefixSuffix(input, options) return prefix + words.map(upperFactory(options?.locale)).join(options?.delimiter ?? '_') + suffix } /** * Convert a string to dot case (`foo.bar`). */ export function dotCase(input: string, options?: Options) { return noCase(input, { delimiter: '.', ...options }) } /** * Convert a string to kebab case (`foo-bar`). */ export function kebabCase(input: string, options?: Options) { return noCase(input, { delimiter: '-', ...options }) } /** * Convert a string to path case (`foo/bar`). */ export function pathCase(input: string, options?: Options) { return noCase(input, { delimiter: '/', ...options }) } /** * Convert a string to path case (`Foo bar`). */ export function sentenceCase(input: string, options?: Options) { const [prefix, words, suffix] = splitPrefixSuffix(input, options) const lower = lowerFactory(options?.locale) const upper = upperFactory(options?.locale) const transform = capitalCaseTransformFactory(lower, upper) return ( prefix + words .map((word, index) => { if (index === 0) return transform(word) return lower(word) }) .join(options?.delimiter ?? ' ') + suffix ) } /** * Convert a string to snake case (`foo_bar`). */ export function snakeCase(input: string, options?: Options) { return noCase(input, { delimiter: '_', ...options }) } /** * Convert a string to header case (`Foo-Bar`). */ export function trainCase(input: string, options?: Options) { return capitalCase(input, { delimiter: '-', ...options }) } function lowerFactory(locale: Locale): (input: string) => string { return locale === false ? (input: string) => input.toLowerCase() : (input: string) => input.toLocaleLowerCase(locale) } function upperFactory(locale: Locale): (input: string) => string { return locale === false ? (input: string) => input.toUpperCase() : (input: string) => input.toLocaleUpperCase(locale) } function capitalCaseTransformFactory( lower: (input: string) => string, upper: (input: string) => string ) { return (word: string) => `${upper(word[0])}${lower(word.slice(1))}` } function pascalCaseTransformFactory( lower: (input: string) => string, upper: (input: string) => string ) { return (word: string, index: number) => { const char0 = word[0] const initial = index > 0 && char0 >= '0' && char0 <= '9' ? '_' + char0 : upper(char0) return initial + lower(word.slice(1)) } } function splitPrefixSuffix(input: string, options: Options = {}): [string, string[], string] { const splitFn = options.split ?? (options.separateNumbers ? splitSeparateNumbers : split) const prefixCharacters = options.prefixCharacters ?? DEFAULT_PREFIX_SUFFIX_CHARACTERS const suffixCharacters = options.suffixCharacters ?? DEFAULT_PREFIX_SUFFIX_CHARACTERS let prefixIndex = 0 let suffixIndex = input.length while (prefixIndex < input.length) { const char = input.charAt(prefixIndex) if (!prefixCharacters.includes(char)) break prefixIndex++ } while (suffixIndex > prefixIndex) { const index = suffixIndex - 1 const char = input.charAt(index) if (!suffixCharacters.includes(char)) break suffixIndex = index } return [ input.slice(0, prefixIndex), splitFn(input.slice(prefixIndex, suffixIndex)), input.slice(suffixIndex), ] }