routie dev init since i didn't adhere to any proper guidance up until now
This commit is contained in:
+397
@@ -0,0 +1,397 @@
|
||||
import { convertBooleanToSign } from './convert-boolean-to-sign.js'
|
||||
import { compare } from 'natural-orderby'
|
||||
/**
|
||||
* Manages a customizable alphabet for sorting operations.
|
||||
*
|
||||
* Provides methods to generate, modify, and sort alphabets based on various
|
||||
* criteria including Unicode ranges, case prioritization, and locale-specific
|
||||
* ordering. Used by the 'custom' sort type to define character precedence.
|
||||
*
|
||||
* The class supports:
|
||||
*
|
||||
* - Generating alphabets from strings or Unicode ranges
|
||||
* - Case prioritization and manipulation
|
||||
* - Character positioning and removal
|
||||
* - Multiple sorting strategies (natural, locale, char code).
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Create a custom alphabet with specific character order
|
||||
* const alphabet = Alphabet.generateFrom('aAbBcC')
|
||||
* .prioritizeCase('uppercase')
|
||||
* .getCharacters()
|
||||
* // Returns: 'AaBbCc'
|
||||
* ```
|
||||
*/
|
||||
var Alphabet = class Alphabet {
|
||||
characters = []
|
||||
constructor(characters) {
|
||||
this.characters = characters
|
||||
}
|
||||
/**
|
||||
* Generates an alphabet from the given characters.
|
||||
*
|
||||
* @param values - The characters to generate the alphabet from.
|
||||
* @returns - The wrapped alphabet.
|
||||
*/
|
||||
static generateFrom(values) {
|
||||
let arrayValues = typeof values === 'string' ? [...values] : values
|
||||
if (arrayValues.length !== new Set(arrayValues).size) {
|
||||
throw new Error('The alphabet must not contain repeated characters')
|
||||
}
|
||||
if (arrayValues.some(value => value.length !== 1)) {
|
||||
throw new Error('The alphabet must contain single characters')
|
||||
}
|
||||
return new Alphabet(
|
||||
arrayValues.map(value =>
|
||||
Alphabet.getCharactersWithCase(value.codePointAt(0)),
|
||||
),
|
||||
)
|
||||
}
|
||||
/**
|
||||
* Generates an alphabet containing relevant characters from the Unicode
|
||||
* standard. Contains the Unicode planes 0 and 1.
|
||||
*
|
||||
* @returns - The generated alphabet.
|
||||
*/
|
||||
static generateRecommendedAlphabet() {
|
||||
return Alphabet.generateAlphabetToRange(131072)
|
||||
}
|
||||
/**
|
||||
* Generates an alphabet containing all characters from the Unicode standard
|
||||
* except for irrelevant Unicode planes. Contains the Unicode planes 0, 1, 2
|
||||
* and 3.
|
||||
*
|
||||
* @returns - The generated alphabet.
|
||||
*/
|
||||
static generateCompleteAlphabet() {
|
||||
return Alphabet.generateAlphabetToRange(262144)
|
||||
}
|
||||
static getCharactersWithCase(codePoint) {
|
||||
let character = String.fromCodePoint(codePoint)
|
||||
let lowercaseCharacter = character.toLowerCase()
|
||||
let uppercaseCharacter = character.toUpperCase()
|
||||
return {
|
||||
value: character,
|
||||
codePoint,
|
||||
...(lowercaseCharacter === character ? null : (
|
||||
{ lowercaseCharacterCodePoint: lowercaseCharacter.codePointAt(0) }
|
||||
)),
|
||||
...(uppercaseCharacter === character ? null : (
|
||||
{ uppercaseCharacterCodePoint: uppercaseCharacter.codePointAt(0) }
|
||||
)),
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Generates an alphabet containing relevant characters from the Unicode
|
||||
* standard.
|
||||
*
|
||||
* @param maxCodePoint - The maximum code point to generate the alphabet to.
|
||||
* @returns - The generated alphabet.
|
||||
*/
|
||||
static generateAlphabetToRange(maxCodePoint) {
|
||||
return new Alphabet(
|
||||
Array.from({ length: maxCodePoint }, (_, i) =>
|
||||
Alphabet.getCharactersWithCase(i),
|
||||
),
|
||||
)
|
||||
}
|
||||
/**
|
||||
* For each character with a lower and upper case, permutes the two cases so
|
||||
* that the alphabet is ordered by the case priority entered.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* Alphabet.generateFrom('aAbBcdCD').prioritizeCase('uppercase') // Returns 'AaBbCDcd'.
|
||||
* ```
|
||||
*
|
||||
* @param casePriority - The case to prioritize.
|
||||
* @returns - The same alphabet instance with the cases prioritized.
|
||||
*/
|
||||
prioritizeCase(casePriority) {
|
||||
let charactersWithCase = this.getCharactersWithCase()
|
||||
let parsedIndexes = /* @__PURE__ */ new Set()
|
||||
let indexByCodePoints = this.characters.reduce(
|
||||
(indexByCodePoint, character, index) => {
|
||||
indexByCodePoint[character.codePoint] = index
|
||||
return indexByCodePoint
|
||||
},
|
||||
{},
|
||||
)
|
||||
for (let { character, index } of charactersWithCase) {
|
||||
if (parsedIndexes.has(index)) {
|
||||
continue
|
||||
}
|
||||
parsedIndexes.add(index)
|
||||
let otherCharacterIndex =
|
||||
indexByCodePoints[
|
||||
character.uppercaseCharacterCodePoint ??
|
||||
character.lowercaseCharacterCodePoint
|
||||
]
|
||||
if (otherCharacterIndex === void 0) {
|
||||
continue
|
||||
}
|
||||
parsedIndexes.add(otherCharacterIndex)
|
||||
if (!character.uppercaseCharacterCodePoint) {
|
||||
if (
|
||||
(casePriority === 'uppercase' && index < otherCharacterIndex) ||
|
||||
(casePriority === 'lowercase' && index > otherCharacterIndex)
|
||||
) {
|
||||
continue
|
||||
}
|
||||
} else if (
|
||||
(casePriority === 'uppercase' && index > otherCharacterIndex) ||
|
||||
(casePriority === 'lowercase' && index < otherCharacterIndex)
|
||||
) {
|
||||
continue
|
||||
}
|
||||
this.characters[index] = this.characters[otherCharacterIndex]
|
||||
this.characters[otherCharacterIndex] = character
|
||||
}
|
||||
return this
|
||||
}
|
||||
/**
|
||||
* Adds specific characters to the end of the alphabet.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* Alphabet.generateFrom('ab').pushCharacters('cd')
|
||||
* // Returns 'abcd'
|
||||
* ```
|
||||
*
|
||||
* @param values - The characters to push to the alphabet.
|
||||
* @returns - The same alphabet instance without the specified characters.
|
||||
*/
|
||||
pushCharacters(values) {
|
||||
let arrayValues = typeof values === 'string' ? [...values] : values
|
||||
let valuesSet = new Set(arrayValues)
|
||||
let valuesAlreadyExisting = this.characters.filter(({ value }) =>
|
||||
valuesSet.has(value),
|
||||
)
|
||||
if (valuesAlreadyExisting.length > 0) {
|
||||
throw new Error(
|
||||
`The alphabet already contains the characters ${valuesAlreadyExisting
|
||||
.slice(0, 5)
|
||||
.map(({ value }) => value)
|
||||
.join(', ')}`,
|
||||
)
|
||||
}
|
||||
if (arrayValues.some(value => value.length !== 1)) {
|
||||
throw new Error('Only single characters may be pushed')
|
||||
}
|
||||
this.characters.push(
|
||||
...[...valuesSet].map(value =>
|
||||
Alphabet.getCharactersWithCase(value.codePointAt(0)),
|
||||
),
|
||||
)
|
||||
return this
|
||||
}
|
||||
/**
|
||||
* Permutes characters with cases so that all characters with the entered case
|
||||
* are put before the other characters.
|
||||
*
|
||||
* @param caseToComeFirst - The case to put before the other characters.
|
||||
* @returns - The same alphabet instance with all characters with case before
|
||||
* all the characters with the other case.
|
||||
*/
|
||||
placeAllWithCaseBeforeAllWithOtherCase(caseToComeFirst) {
|
||||
let otherCaseKey =
|
||||
caseToComeFirst === 'uppercase' ?
|
||||
'uppercaseCharacterCodePoint'
|
||||
: 'lowercaseCharacterCodePoint'
|
||||
let keep = []
|
||||
let move = []
|
||||
for (let character of this.characters) {
|
||||
;(character[otherCaseKey] === void 0 ? keep : move).push(character)
|
||||
}
|
||||
this.characters = [...keep, ...move]
|
||||
return this
|
||||
}
|
||||
/**
|
||||
* Places a specific character right before another character in the alphabet.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* Alphabet.generateFrom('ab-cd/').placeCharacterBefore({
|
||||
* characterBefore: '/',
|
||||
* characterAfter: '-',
|
||||
* })
|
||||
* // Returns 'ab/-cd'
|
||||
* ```
|
||||
*
|
||||
* @param params - The parameters for the operation.
|
||||
* @param params.characterBefore - The character to come before
|
||||
* characterAfter.
|
||||
* @param params.characterAfter - The target character.
|
||||
* @returns - The same alphabet instance with the specific character
|
||||
* prioritized.
|
||||
*/
|
||||
placeCharacterBefore({ characterBefore, characterAfter }) {
|
||||
return this.placeCharacterBeforeOrAfter({
|
||||
characterBefore,
|
||||
characterAfter,
|
||||
type: 'before',
|
||||
})
|
||||
}
|
||||
/**
|
||||
* Places a specific character right after another character in the alphabet.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* Alphabet.generateFrom('ab-cd/').placeCharacterAfter({
|
||||
* characterBefore: '/',
|
||||
* characterAfter: '-',
|
||||
* })
|
||||
* // Returns 'abcd/-'
|
||||
* ```
|
||||
*
|
||||
* @param params - The parameters for the operation.
|
||||
* @param params.characterBefore - The target character.
|
||||
* @param params.characterAfter - The character to come after characterBefore.
|
||||
* @returns - The same alphabet instance with the specific character
|
||||
* prioritized.
|
||||
*/
|
||||
placeCharacterAfter({ characterBefore, characterAfter }) {
|
||||
return this.placeCharacterBeforeOrAfter({
|
||||
characterBefore,
|
||||
characterAfter,
|
||||
type: 'after',
|
||||
})
|
||||
}
|
||||
/**
|
||||
* Removes specific characters from the alphabet by their range.
|
||||
*
|
||||
* @param range - The Unicode range to remove characters from.
|
||||
* @param range.start - The starting Unicode codepoint.
|
||||
* @param range.end - The ending Unicode codepoint.
|
||||
* @returns - The same alphabet instance without the characters from the
|
||||
* specified range.
|
||||
*/
|
||||
removeUnicodeRange({ start, end }) {
|
||||
this.characters = this.characters.filter(
|
||||
({ codePoint }) => codePoint < start || codePoint > end,
|
||||
)
|
||||
return this
|
||||
}
|
||||
/**
|
||||
* Sorts the alphabet by the sorting function provided.
|
||||
*
|
||||
* @param sortingFunction - The sorting function to use.
|
||||
* @returns - The same alphabet instance sorted by the sorting function
|
||||
* provided.
|
||||
*/
|
||||
sortBy(sortingFunction) {
|
||||
this.characters.sort((a, b) => sortingFunction(a.value, b.value))
|
||||
return this
|
||||
}
|
||||
/**
|
||||
* Sorts the alphabet by the natural order of the characters using
|
||||
* `natural-orderby`.
|
||||
*
|
||||
* @param locale - The locale to use for sorting.
|
||||
* @returns - The same alphabet instance sorted by the natural order of the
|
||||
* characters.
|
||||
*/
|
||||
sortByNaturalSort(locale) {
|
||||
let naturalCompare = compare({ locale })
|
||||
return this.sortBy((a, b) => naturalCompare(a, b))
|
||||
}
|
||||
/**
|
||||
* Removes specific characters from the alphabet.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* Alphabet.generateFrom('abcd').removeCharacters('dcc')
|
||||
* // Returns 'ab'
|
||||
* ```
|
||||
*
|
||||
* @param values - The characters to remove from the alphabet.
|
||||
* @returns - The same alphabet instance without the specified characters.
|
||||
*/
|
||||
removeCharacters(values) {
|
||||
this.characters = this.characters.filter(
|
||||
({ value }) => !values.includes(value),
|
||||
)
|
||||
return this
|
||||
}
|
||||
/**
|
||||
* Sorts the alphabet by the character code point.
|
||||
*
|
||||
* @returns - The same alphabet instance sorted by the character code point.
|
||||
*/
|
||||
sortByCharCodeAt() {
|
||||
return this.sortBy((a, b) =>
|
||||
convertBooleanToSign(a.charCodeAt(0) > b.charCodeAt(0)),
|
||||
)
|
||||
}
|
||||
/**
|
||||
* Sorts the alphabet by the locale order of the characters.
|
||||
*
|
||||
* @param locales - The locales to use for sorting.
|
||||
* @returns - The same alphabet instance sorted by the locale order of the
|
||||
* characters.
|
||||
*/
|
||||
sortByLocaleCompare(locales) {
|
||||
return this.sortBy((a, b) => a.localeCompare(b, locales))
|
||||
}
|
||||
/**
|
||||
* Retrieves the characters from the alphabet.
|
||||
*
|
||||
* @returns The characters from the alphabet.
|
||||
*/
|
||||
getCharacters() {
|
||||
return this.characters.map(({ value }) => value).join('')
|
||||
}
|
||||
placeCharacterBeforeOrAfter({ characterBefore, characterAfter, type }) {
|
||||
let indexOfCharacterAfter = this.characters.findIndex(
|
||||
({ value }) => value === characterAfter,
|
||||
)
|
||||
let indexOfCharacterBefore = this.characters.findIndex(
|
||||
({ value }) => value === characterBefore,
|
||||
)
|
||||
if (indexOfCharacterAfter === -1) {
|
||||
throw new Error(`Character ${characterAfter} not found in alphabet`)
|
||||
}
|
||||
if (indexOfCharacterBefore === -1) {
|
||||
throw new Error(`Character ${characterBefore} not found in alphabet`)
|
||||
}
|
||||
if (indexOfCharacterBefore <= indexOfCharacterAfter) {
|
||||
return this
|
||||
}
|
||||
this.characters.splice(
|
||||
type === 'before' ? indexOfCharacterAfter : indexOfCharacterBefore + 1,
|
||||
0,
|
||||
this.characters[
|
||||
type === 'before' ? indexOfCharacterBefore : indexOfCharacterAfter
|
||||
],
|
||||
)
|
||||
this.characters.splice(
|
||||
type === 'before' ? indexOfCharacterBefore + 1 : indexOfCharacterAfter,
|
||||
1,
|
||||
)
|
||||
return this
|
||||
}
|
||||
getCharactersWithCase() {
|
||||
return this.characters
|
||||
.map((character, index) => {
|
||||
if (
|
||||
!character.uppercaseCharacterCodePoint &&
|
||||
!character.lowercaseCharacterCodePoint
|
||||
) {
|
||||
return null
|
||||
}
|
||||
return {
|
||||
character,
|
||||
index,
|
||||
}
|
||||
})
|
||||
.filter(element => element !== null)
|
||||
}
|
||||
}
|
||||
export { Alphabet }
|
||||
Reference in New Issue
Block a user