Skip to content

Instantly share code, notes, and snippets.

@anthonyec
Last active July 24, 2024 10:05
Show Gist options
  • Save anthonyec/ec86c518d9729c1e208a9fdc8e89e8de to your computer and use it in GitHub Desktop.
Save anthonyec/ec86c518d9729c1e208a9fdc8e89e8de to your computer and use it in GitHub Desktop.
The Utils I Have Always Wanted dot TS
export function prepend<T>(array: T[], value: T): T[] {
return [value, ...array];
}
export function append<T>(array: T[], value: T): T[] {
return [...array, value];
}
export function insert<T>(array: T[], index: number, value: T): T[] {
return [...array.slice(0, index), value, ...array.slice(index, array.length)];
}
export function replace<T>(array: T[], index: number, value: T): T[] {
return [...array.slice(0, index), value, ...array.slice(index + 1)];
}
export function remove<T>(array: T[], index: number): T[] {
return [...array.slice(0, index), ...array.slice(index + 1)];
}
export function first<T>(array: T[]): NonNullable<T> | undefined {
const item = array[0];
if (item === undefined || item === null) return;
return item;
}
export function last<T>(array: T[]): NonNullable<T> | undefined {
const item = array[array.length - 1];
if (item === undefined || item === null) return;
return item;
}
export function reverse<T>(array: T[]): T[] {
return [...array].reverse();
}
export function sort<T>(array: T[], compare?: (a: T, b: T) => number): T[] {
return [...array].sort(compare);
}
export function previous<T>(array: T[], index: number): NonNullable<T> | undefined {
if (index === 0) return;
const item = array[index - 1];
if (item === undefined || item === null) return;
return item;
}
export function next<T>(array: T[], index: number): NonNullable<T> | undefined {
if (index >= array.length - 1) return;
const item = array[index + 1];
if (item === undefined || item === null) return;
return item;
}
export function forwards(iterations: number, length: number): number {
return iterations + 1;
}
export function backwards(iterations: number, length: number): number {
return length - 1 - iterations;
}
export function skip(every: number): IterationBehaviour {
return (iterations: number): number => {
return iterations * every;
};
}
type IterationBehaviour = (iterations: number, length: number) => number;
export function iterate<T>(array: T[], behaviour: IterationBehaviour = forwards) {
return {
[Symbol.iterator]() {
let index = behaviour(-1, array.length);
let iterations = 0;
return {
next() {
if (iterations > array.length * 3) {
throw Error('Infinite loop protection');
}
const value = {
index,
current: array[index],
previous: previous(array, index),
next: next(array, index),
isStart: index === 0,
isEnd: index === array.length - 1
};
if (index >= 0 && index < array.length) {
index = behaviour(iterations, array.length);
iterations += 1;
return { value, done: false };
}
return { value: value, done: true };
}
};
}
};
}
export function assert(condition: unknown, ...message: unknown[]): asserts condition {
if (condition) return;
throw Error(`Assertion error: ${message.join(', ')}`);
}
export type Range = [start: number, end: number];
export function indicesOf(haystack: string, needle: string): number[] {
if (haystack.length === 0 || needle.length === 0) return [];
const indices: number[] = [];
let index = haystack.indexOf(needle);
while (index !== -1) {
indices.push(index);
index = haystack.indexOf(needle, index + needle.length);
}
return indices;
}
export function isLowerCase(text: string): boolean {
return text === text.toLowerCase() && text !== text.toUpperCase();
}
export function isUpperCase(text: string): boolean {
return text !== text.toLowerCase() && text === text.toUpperCase();
}
export function matchCase(original: string, template: string): string {
const maxIndex = Math.min(original.length, template.length);
let output = '';
for (let index = 0; index < original.length; index++) {
const originalCharacter = original[index];
if (index >= maxIndex) {
output += originalCharacter;
continue;
}
const templateCharacter = template[index];
if (isUpperCase(templateCharacter)) {
output += originalCharacter.toUpperCase();
continue;
}
output += originalCharacter;
}
return output;
}
export function replaceRange(
text: string,
replacement: string,
start: number,
end: number
): string {
const before = text.slice(0, start);
const after = text.slice(end);
return before + replacement + after;
}
export function replaceAllRanges(
text: string,
replacement: string,
ranges: Range[],
preserveCase?: boolean
) {
let result: string = text;
let offset: number = 0;
for (const [start, end] of ranges) {
const previousLength = result.length;
const offsetStart = start - offset;
const offsetEnd = end - offset;
result = replaceRange(
result,
preserveCase ? matchCase(replacement, result.slice(offsetStart, offsetEnd)) : replacement,
offsetStart,
offsetEnd
);
offset += previousLength - result.length;
}
return result;
}
export function pluralize(singular: string, plural: string, count: number) {
if (count === 1) return singular;
return plural;
}
export function findRanges(
text: string,
query: string,
isCaseSensitive: boolean,
isRegex: boolean
): Range[] {
if (!text || !query) return [];
const haystack = isCaseSensitive ? text : text.toLowerCase();
const needle = isCaseSensitive ? query : query.toLowerCase();
if (isRegex) {
const regex = new RegExp(needle, 'g');
const matches = haystack.matchAll(regex);
const ranges: Range[] = [];
for (const match of matches) {
const start = match.index ?? 0;
const end = start + match[0].length;
ranges.push([start, end]);
}
return ranges;
}
const indices = indicesOf(haystack, needle);
if (indices.length === 0) return [];
const ranges: Range[] = [];
for (const start of indices) {
ranges.push([start, start + needle.length]);
}
return ranges;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment