insta-toc
Version:
Simultaneously generate, update, and maintain a table of contents for your notes in real time.
83 lines (69 loc) • 3.2 kB
text/typescript
export function assert<T>(value: T | undefined | null, message?: string): asserts value is NonNullable<T> {
if (value == null || value === undefined) {
throw new Error(message ?? "Expected value to be defined");
}
}
export function isNothing<T>(value: T): value is Extract<T, null | undefined> {
return value === null || value === undefined;
}
export function sortRecord<T extends Record<string, any>>(
obj: T,
sortFn?: (a: [keyof T, T[keyof T]?], b: [keyof T, T[keyof T]?]) => number
): Record<string, T[keyof T]> {
const entries = Object.entries(obj);
const sortedEntries = entries.sort(sortFn ? sortFn : ([ a ]: string[], [ b ]: string[]) => a.localeCompare(b));
return Object.fromEntries(sortedEntries);
}
export function sortMap<K, V>(map: Map<K, V>, sortFn?: (a: [K, V], b: [K, V]) => number): Map<K, V> {
const entries = [ ...map.entries() ];
const sortedEntries = entries.sort(sortFn ? sortFn : ([ a ], [ b ]) => String(a).localeCompare(String(b)));
return new Map(sortedEntries);
}
/**
* Check if a value is an object that can be merged
* @param value The input value to check for being a mergeable object
* @returns A boolean value indicating whether the input value is a mergeable object (true) or not (false), based on whether it is a non-null object that is not an array
*/
export function isRecord<T extends Record<string, unknown>>(value: unknown): value is T {
if (isNothing(value)) return false;
let maybeRecord: string;
try {
maybeRecord = JSON.stringify(value, undefined, 2) as string;
}
catch {
return false;
}
const hasBrackets = maybeRecord.startsWith("{") && maybeRecord.endsWith("}");
const isObjectNotArray = value instanceof Object && !Array.isArray(value);
return hasBrackets && isObjectNotArray;
}
export function isMap<K = unknown, V = unknown>(value: unknown): value is Map<K, V> {
return value instanceof Map;
}
RegExp.isRegexPattern = function isRegexPattern(str: string): str is `/${string}/` {
// Checks if the string starts and ends with '/'
if (!/^\/.*\/$/.test(str)) return false;
// `/<str>/<flags>` => <str>
const endPattern = str.lastIndexOf("/");
const pattern = str.slice(1, endPattern);
try {
// Attempting to create a new RegExp instance will throw a SyntaxError
// at runtime if the pattern is invalid.
new RegExp(pattern);
}
catch {
return false;
}
return true;
};
RegExp.escape = function (value: string): string {
return value.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&");
};
export function injectGlobals(): void {
(globalThis as typeof globalThis & { assert: typeof assert; }).assert = assert;
(globalThis as typeof globalThis & { isNothing: typeof isNothing; }).isNothing = isNothing;
(globalThis as typeof globalThis & { sortRecord: typeof sortRecord; }).sortRecord = sortRecord;
(globalThis as typeof globalThis & { sortMap: typeof sortMap; }).sortMap = sortMap;
(globalThis as typeof globalThis & { isRecord: typeof isRecord; }).isRecord = isRecord;
(globalThis as typeof globalThis & { isMap: typeof isMap; }).isMap = isMap;
}