mirror of
https://github.com/Kir-Antipov/mc-publish.git
synced 2024-12-01 21:01:00 -05:00
466 lines
14 KiB
TypeScript
466 lines
14 KiB
TypeScript
|
import { createHash, randomBytes } from "node:crypto";
|
||
|
import { IGNORE_CASE_COMPARER, ORDINAL_COMPARER } from "@/utils/comparison";
|
||
|
|
||
|
/**
|
||
|
* Returns the input value converted to a string.
|
||
|
*
|
||
|
* If the input value is already a string, it is returned as-is.
|
||
|
* Otherwise, the output of `String()` is returned.
|
||
|
*
|
||
|
* @param s - The input value to convert to a string.
|
||
|
*
|
||
|
* @returns The input value as a string.
|
||
|
*/
|
||
|
export function asString(s: unknown): string {
|
||
|
return typeof s === "string" ? s : String(s);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* A regular expression that matches a string consisting of a single letter character.
|
||
|
*/
|
||
|
export const IS_LETTER_REGEX = /^\p{L}$/u;
|
||
|
|
||
|
/**
|
||
|
* Checks if the provided string is a single letter.
|
||
|
*
|
||
|
* @param s - The string to check.
|
||
|
*
|
||
|
* @returns `true` if the string is a single letter; otherwise, `false`.
|
||
|
*/
|
||
|
export function isLetter(s: string): boolean {
|
||
|
return s?.length === 1 && IS_LETTER_REGEX.test(s);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* A regular expression that matches a string consisting of a single digit character.
|
||
|
*/
|
||
|
export const IS_DIGIT_REGEX = /^\d$/;
|
||
|
|
||
|
/**
|
||
|
* Checks if the provided string is a single digit.
|
||
|
*
|
||
|
* @param s - The string to check.
|
||
|
*
|
||
|
* @returns `true` if the string is a single digit; otherwise, `false`.
|
||
|
*/
|
||
|
export function isDigit(s: string): boolean {
|
||
|
return s?.length === 1 && s >= "0" && s <= "9";
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* A regular expression that matches a string consisting of a single letter or digit character.
|
||
|
*/
|
||
|
export const IS_LETTER_OR_DIGIT_REGEX = /^(?:\p{L}|\d)$/u;
|
||
|
|
||
|
/**
|
||
|
* Checks if the provided string is a single letter or digit.
|
||
|
*
|
||
|
* @param s - The string to check.
|
||
|
*
|
||
|
* @returns `true` if the string is a single letter or digit; otherwise, `false`.
|
||
|
*/
|
||
|
export function isLetterOrDigit(s: string): boolean {
|
||
|
return s?.length === 1 && IS_LETTER_OR_DIGIT_REGEX.test(s);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* A regular expression that matches strings containing only uppercase characters
|
||
|
* and not containing any lowercase Unicode characters.
|
||
|
*/
|
||
|
export const IS_UPPER_CASE_REGEX = /^[^\p{Ll}]*$/u;
|
||
|
|
||
|
/**
|
||
|
* Checks if a string contains only uppercase characters.
|
||
|
*
|
||
|
* @param s - The string to check.
|
||
|
*
|
||
|
* @returns `true` if the input string contains only uppercase characters; otherwise, `false`.
|
||
|
*/
|
||
|
export function isUpperCase(s: string): boolean {
|
||
|
return IS_UPPER_CASE_REGEX.test(s);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* A regular expression that matches strings containing only lowercase characters
|
||
|
* and not containing any uppercase Unicode characters.
|
||
|
*/
|
||
|
export const IS_LOWER_CASE_REGEX = /^[^\p{Lu}]*$/u;
|
||
|
|
||
|
/**
|
||
|
* Checks if a string contains only lowercase characters.
|
||
|
*
|
||
|
* @param s - The string to check.
|
||
|
*
|
||
|
* @returns `true` if the input string contains only lowercase characters; otherwise, `false`.
|
||
|
*/
|
||
|
export function isLowerCase(s: string): boolean {
|
||
|
return IS_LOWER_CASE_REGEX.test(s);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Checks if a given string represents a valid number.
|
||
|
*
|
||
|
* @param s - The string to be checked.
|
||
|
*
|
||
|
* @returns `true` if the string represents a valid number; otherwise, `false`.
|
||
|
*/
|
||
|
export function isNumberString(s: string): boolean {
|
||
|
return String(+s) === s;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Checks if a given string represents a valid integer number.
|
||
|
*
|
||
|
* @param s - The string to be checked.
|
||
|
*
|
||
|
* @returns `true` if the string represents a valid integer number; otherwise, `false`.
|
||
|
*/
|
||
|
export function isIntegerString(s: string): boolean {
|
||
|
return String(parseInt(s)) === s;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Options for comparing strings.
|
||
|
*/
|
||
|
export interface StringComparisonOptions {
|
||
|
/**
|
||
|
* Indicates whether the comparison should ignore the case of the strings being compared.
|
||
|
* If `ignoreCase` is `true`, the comparison will use lexicographical sort rules while
|
||
|
* ignoring the case of the strings being compared.
|
||
|
*/
|
||
|
ignoreCase?: boolean;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Compares two strings lexicographically and returns a value indicating whether one string is less than, equal to, or greater than the other.
|
||
|
*
|
||
|
* @param left - The first string to compare.
|
||
|
* @param right - The second string to compare.
|
||
|
* @param options - Options for comparing strings.
|
||
|
*
|
||
|
* @returns A value indicating the comparison result:
|
||
|
*
|
||
|
* - A value less than 0 indicates that `left` is less than `right`.
|
||
|
* - 0 indicates that `left` is equal to `right`.
|
||
|
* - A value greater than 0 indicates that `left` is greater than `right`.
|
||
|
*/
|
||
|
export function stringCompare(left: string, right: string, options?: StringComparisonOptions): number {
|
||
|
const comparer = options?.ignoreCase ? IGNORE_CASE_COMPARER : ORDINAL_COMPARER;
|
||
|
return comparer.compare(left, right);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Compares two strings.
|
||
|
*
|
||
|
* @param left - The first string to compare.
|
||
|
* @param right - The second string to compare.
|
||
|
* @param options - Options for comparing strings.
|
||
|
*
|
||
|
* @returns `true` if the strings are equal; otherwise, `false`.
|
||
|
*/
|
||
|
export function stringEquals(left: string, right: string, options?: StringComparisonOptions): boolean {
|
||
|
return stringCompare(left, right, options) === 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Capitalizes the first letter of a string.
|
||
|
*
|
||
|
* @param s - The string to capitalize.
|
||
|
*
|
||
|
* @returns The capitalized string.
|
||
|
*/
|
||
|
export function capitalize(s: string): string {
|
||
|
return s.charAt(0).toUpperCase() + s.slice(1);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Converts the first character of a string to lowercase.
|
||
|
*
|
||
|
* @param s - The input string.
|
||
|
*
|
||
|
* @returns The input string with the first character converted to lowercase.
|
||
|
*/
|
||
|
export function uncapitalize(s: string): string {
|
||
|
return s.charAt(0).toLowerCase() + s.slice(1);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Converts a string to PascalCase.
|
||
|
*
|
||
|
* This function can handle input strings in the following formats:
|
||
|
* - PascalCase
|
||
|
* - camelCase
|
||
|
* - kebab-case
|
||
|
* - snake_case
|
||
|
* - SCREAMING_SNAKE_CASE
|
||
|
*
|
||
|
* @param s - The input string to be converted to PascalCase.
|
||
|
*
|
||
|
* @returns The input string converted to PascalCase.
|
||
|
*/
|
||
|
export function toPascalCase(s: string): string {
|
||
|
// Convert input to lowercase if the entire string is in uppercase (SCREAMING_SNAKE_CASE)
|
||
|
if (isUpperCase(s)) {
|
||
|
s = s.toLowerCase();
|
||
|
}
|
||
|
|
||
|
return s
|
||
|
// Replace any character following a non-word character (such as - or _) with its uppercase counterpart
|
||
|
.replace(/(?:^|[\s_-])(\w)/g, (_, char) => char.toUpperCase())
|
||
|
// Remove any non-word characters (such as - or _) from the result
|
||
|
.replace(/[\s_-]/g, "");
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Specifies how the results should be transformed when splitting a string into substrings.
|
||
|
*/
|
||
|
export interface StringSplitOptions {
|
||
|
/**
|
||
|
* Remove empty (zero-length) substrings from the result.
|
||
|
*/
|
||
|
removeEmptyEntries?: boolean;
|
||
|
|
||
|
/**
|
||
|
* Trim whitespace from each substring in the result.
|
||
|
*/
|
||
|
trimEntries?: boolean;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Splits a string into an array of substrings based on a separator.
|
||
|
*
|
||
|
* @param s - The input string to split.
|
||
|
* @param separator - The separator to split the string by.
|
||
|
* @param options - Options for splitting the string.
|
||
|
*
|
||
|
* @returns An array of substrings from the original string.
|
||
|
*/
|
||
|
export function split(s: string, separator: string, options?: StringSplitOptions): string[];
|
||
|
|
||
|
/**
|
||
|
* Splits a string into an array of substrings based on the given separators.
|
||
|
*
|
||
|
* @param s - The input string to split.
|
||
|
* @param separators - The array of separators to split the string by.
|
||
|
* @param options - Options for splitting the string.
|
||
|
*
|
||
|
* @returns An array of substrings from the original string.
|
||
|
*/
|
||
|
export function split(s: string, separators: readonly string[], options?: StringSplitOptions): string[];
|
||
|
|
||
|
/**
|
||
|
* Returns an array of substrings that were delimited by strings in the original input that
|
||
|
* match against the `splitter` regular expression.
|
||
|
*
|
||
|
* @param s - The input string to split.
|
||
|
* @param splitter - The regular expression to split the string by.
|
||
|
* @param options - Options for splitting the string.
|
||
|
*
|
||
|
* @returns An array of substrings from the original string.
|
||
|
*/
|
||
|
export function split(s: string, splitter: RegExp, options?: StringSplitOptions): string[];
|
||
|
|
||
|
/**
|
||
|
* Splits a string into an array of substrings based on a separator.
|
||
|
*
|
||
|
* @param s - The string to split.
|
||
|
* @param separator - The separator to split the string by. Can be a string, a regular expression, or an array of strings.
|
||
|
* @param options - Options for splitting the string.
|
||
|
*
|
||
|
* @returns An array of substrings from the original string.
|
||
|
*/
|
||
|
export function split(s: string, separator: string | RegExp | readonly string[], options?: StringSplitOptions): string[] {
|
||
|
if (!s) {
|
||
|
return [];
|
||
|
}
|
||
|
|
||
|
if (Array.isArray(separator)) {
|
||
|
return splitByArrayOfStrings(s, separator, options);
|
||
|
}
|
||
|
return splitByStringOrRegex(s, separator as string | RegExp, options);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Split a string into substrings using the specified separator and return them as an array.
|
||
|
*
|
||
|
* @param s - The string to split.
|
||
|
* @param separator - The separator to split the string by. Can be a string, or a regular expression.
|
||
|
* @param options - Options for splitting the string.
|
||
|
*
|
||
|
* @returns An array of substrings from the original string.
|
||
|
*/
|
||
|
function splitByStringOrRegex(s: string, separator: string | RegExp, options?: StringSplitOptions): string[] {
|
||
|
const trimEntries = options?.trimEntries ?? false;
|
||
|
const removeEmptyEntries = options?.removeEmptyEntries ?? false;
|
||
|
const parts = s.split(separator);
|
||
|
|
||
|
if (trimEntries) {
|
||
|
for (let i = 0; i < parts.length; ++i) {
|
||
|
parts[i] = parts[i].trim();
|
||
|
}
|
||
|
}
|
||
|
return removeEmptyEntries ? parts.filter(x => x) : parts;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Splits a string into an array of substrings based on the given separators.
|
||
|
*
|
||
|
* @param s - The input string to split.
|
||
|
* @param separators - The array of separators to split the string by.
|
||
|
* @param options - Options for splitting the string.
|
||
|
*
|
||
|
* @returns An array of substrings from the original string.
|
||
|
*/
|
||
|
function splitByArrayOfStrings(s: string, separators: readonly string[], options?: StringSplitOptions): string[] {
|
||
|
const trimEntries = options?.trimEntries ?? false;
|
||
|
const removeEmptyEntries = options?.removeEmptyEntries ?? false;
|
||
|
const splitted = [];
|
||
|
|
||
|
let previousIndex = -1;
|
||
|
for (let i = 0; i < s.length; ++i) {
|
||
|
if (!separators.includes(s.charAt(i))) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
let part = s.substring(previousIndex + 1, i);
|
||
|
previousIndex = i;
|
||
|
|
||
|
if (trimEntries) {
|
||
|
part = part.trim();
|
||
|
}
|
||
|
|
||
|
if (part || !removeEmptyEntries) {
|
||
|
splitted.push(part);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
let lastPart = s.substring(previousIndex + 1);
|
||
|
|
||
|
if (trimEntries) {
|
||
|
lastPart = lastPart.trim();
|
||
|
}
|
||
|
|
||
|
if (lastPart || !removeEmptyEntries) {
|
||
|
splitted.push(lastPart);
|
||
|
}
|
||
|
|
||
|
return splitted;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Represents options for splitting a string into multiple lines.
|
||
|
*/
|
||
|
export interface StringSplitLineOptions {
|
||
|
/**
|
||
|
* The maximum length of each line.
|
||
|
*/
|
||
|
maxLength?: number;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Splits a string into an array of lines.
|
||
|
*
|
||
|
* @param text - The input string to split.
|
||
|
* @param options - An optional object that specifies the options for splitting the string, including the maximum line length.
|
||
|
*
|
||
|
* @returns An array of strings, where each string represents a line from the input string. If the `maxLength` option is specified, the lines will be truncated at the specified length.
|
||
|
*/
|
||
|
export function splitLines(text: string, options?: StringSplitLineOptions): string[] {
|
||
|
const maxLength = options?.maxLength || 0;
|
||
|
|
||
|
const lines = text.split(/\r?\n/);
|
||
|
if (maxLength <= 0) {
|
||
|
return lines;
|
||
|
}
|
||
|
|
||
|
const shortenedLines = lines.flatMap(line => {
|
||
|
if (line.length <= maxLength) {
|
||
|
return line;
|
||
|
}
|
||
|
|
||
|
const words = line.split(" ");
|
||
|
const linesFromCurrentLine = [] as string[];
|
||
|
let currentLine = "";
|
||
|
|
||
|
for (const word of words) {
|
||
|
if (currentLine.length + word.length <= maxLength) {
|
||
|
currentLine += (currentLine ? " " : "") + word;
|
||
|
} else {
|
||
|
linesFromCurrentLine.push(currentLine);
|
||
|
currentLine = word;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
linesFromCurrentLine.push(currentLine);
|
||
|
return linesFromCurrentLine;
|
||
|
});
|
||
|
|
||
|
return shortenedLines;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Represents options for padding a string with spaces or a specific fill character.
|
||
|
*/
|
||
|
export interface StringPadOptions {
|
||
|
/**
|
||
|
* The fill character to use when padding the string. If not specified, spaces will be used.
|
||
|
*/
|
||
|
fillString?: string;
|
||
|
|
||
|
/**
|
||
|
* The alignment of the padded string within the specified maximum length.
|
||
|
*/
|
||
|
align?: "left" | "center" | "right";
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Pads a string with spaces or a specific fill character to the specified maximum length.
|
||
|
*
|
||
|
* @param s - The input string to pad.
|
||
|
* @param maxLength - The maximum length of the padded string.
|
||
|
* @param options - An optional object that specifies the options for padding the string.
|
||
|
*
|
||
|
* @returns A string that represents the padded input string according to the specified options.
|
||
|
*/
|
||
|
export function pad(s: string, maxLength: number, options?: StringPadOptions): string {
|
||
|
s ||= "";
|
||
|
|
||
|
switch (options?.align) {
|
||
|
case "left":
|
||
|
return s.padEnd(maxLength, options?.fillString);
|
||
|
case "right":
|
||
|
return s.padStart(maxLength, options?.fillString);
|
||
|
default:
|
||
|
const availableLength = maxLength - s.length;
|
||
|
if (availableLength <= 0) {
|
||
|
return s;
|
||
|
}
|
||
|
const padStartLength = (availableLength >> 1) + s.length;
|
||
|
return s.padStart(padStartLength, options?.fillString).padEnd(maxLength, options?.fillString);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Generates a secure random string of a specified length.
|
||
|
*
|
||
|
* @param length - The desired length of the generated string.
|
||
|
*
|
||
|
* @returns The secure random string in hexadecimal format.
|
||
|
*/
|
||
|
export function generateSecureRandomString(length: number): string {
|
||
|
const bytes = randomBytes(Math.ceil(length / 2));
|
||
|
return bytes.toString("hex").slice(0, length);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Hashes a string using the specified algorithm.
|
||
|
*
|
||
|
* @param input - The string to be hashed.
|
||
|
* @param algorithm - The hashing algorithm to use (default: "sha256").
|
||
|
*
|
||
|
* @returns The hashed string in hexadecimal format.
|
||
|
*/
|
||
|
export function hashString(input: string, algorithm: string = "sha256"): string {
|
||
|
return createHash(algorithm).update(input).digest("hex");
|
||
|
}
|