mirror of
https://github.com/djdevin/recflare.git
synced 2026-09-08 14:41:28 -07:00
329 lines
8.7 KiB
TypeScript
329 lines
8.7 KiB
TypeScript
import slugify from 'slugify'
|
|
|
|
export function slugifyText(text: string) {
|
|
const slug = slugify(text, {
|
|
lower: true,
|
|
remove: /['.]/g,
|
|
})
|
|
return slug
|
|
}
|
|
|
|
export function slugifyTextSingular(text: string) {
|
|
const slug = slugify(text, {
|
|
lower: true,
|
|
remove: /['.]/g,
|
|
})
|
|
if (slug.endsWith('s')) {
|
|
return slug.slice(0, slug.length - 1)
|
|
}
|
|
|
|
return slug
|
|
}
|
|
|
|
export function slugifyTextPlural(text: string) {
|
|
const slug = slugify(text, {
|
|
lower: true,
|
|
remove: /['.]/g,
|
|
})
|
|
if (slug.endsWith('s')) {
|
|
return slug
|
|
}
|
|
|
|
return `${slug}s`
|
|
}
|
|
|
|
export function pascalText(text: string) {
|
|
return pascalCase(text)
|
|
}
|
|
|
|
export function pascalTextSingular(text: string) {
|
|
const slug = pascalCase(text)
|
|
if (slug.endsWith('s')) {
|
|
return slug.slice(0, slug.length - 1)
|
|
}
|
|
|
|
return slug
|
|
}
|
|
|
|
export function pascalTextPlural(text: string) {
|
|
const slug = pascalCase(text)
|
|
|
|
if (slug.endsWith('s')) {
|
|
return slug
|
|
}
|
|
|
|
return `${slug}s`
|
|
}
|
|
|
|
// =========================== //
|
|
// ========= VENDOR ========== //
|
|
// =========================== //
|
|
|
|
// 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--
|
|
|
|
// oxlint-disable-next-line no-control-regex -- intentional null character split
|
|
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),
|
|
]
|
|
}
|