sounisi5011/metalsmith-netlify-published-date

View on GitHub
src/utils/index.ts

Summary

Maintainability
A
0 mins
Test Coverage
import path from 'path';
import util from 'util';

export function isNotVoid<T>(value: T | undefined | void): value is T {
    return value !== undefined;
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function isObject(value: unknown): value is Record<any, unknown> {
    return typeof value === 'object' && value !== null;
}

export function isStringArray(
    value: ReadonlyArray<unknown>,
): value is string[] {
    return value.every(v => typeof v === 'string');
}

export function hasProp<
    T extends object,
    U extends Parameters<typeof Object.prototype.hasOwnProperty>[0]
>(value: T, prop: U): value is typeof value & { [P in U]: unknown } {
    return Object.prototype.hasOwnProperty.call(value, prop);
}

export function getPropertyNames<T>(...values: T[]): (keyof T)[] {
    return [
        ...new Set(
            ([] as (keyof T)[]).concat(
                ...values.map(
                    value => Object.getOwnPropertyNames(value) as (keyof T)[],
                ),
            ),
        ),
    ];
}

/**
 * @see https://github.com/lodash/lodash/blob/f8c7064d450cc068144c4dad1d63535cba25ae6d/.internal/getAllKeys.js
 */
export function getAllProps<T extends object>(value: T): (keyof T)[] {
    const symbolProps = Object.getOwnPropertySymbols(value).filter(symbol =>
        Object.prototype.propertyIsEnumerable.call(value, symbol),
    );
    // eslint-disable-next-line @typescript-eslint/ban-ts-ignore
    // @ts-ignore: TS2322 -- Type '(string | symbol)[]' is not assignable to type '(keyof T)[]'.
    return [...Object.keys(value), ...symbolProps];
}

export function getPropertyDescriptor<O, P extends keyof O>(
    value: O,
    prop: P,
): TypedPropertyDescriptor<O[P]> | undefined {
    return Object.getOwnPropertyDescriptor(value, prop);
}

export function getPropertyDescriptorEntries<T extends object>(
    value: T,
): [keyof T, TypedPropertyDescriptor<T[keyof T]>][] {
    const descs = Object.getOwnPropertyDescriptors(value);
    const props = [
        ...(Object.getOwnPropertyNames(descs) as (keyof T)[]),
        ...(Object.getOwnPropertySymbols(descs) as (keyof T)[]),
    ];
    return props.map(prop => [prop, descs[prop]]);
}

export function pickProps<T extends object, U extends keyof T>(
    obj: T,
    props: readonly U[],
): Pick<T, U> {
    const desc = Object.getOwnPropertyDescriptors(obj);
    getAllProps(desc).forEach(prop => {
        if (!props.includes(prop as U)) {
            delete desc[prop];
        }
    });
    return Object.defineProperties({}, desc);
}

export function freezeProperty(obj: object, prop: string): void {
    Object.defineProperty(obj, prop, { configurable: false, writable: false });
}

export function initObject<O = unknown>(
    obj: O,
    origObj: object,
    propertiesObj: object | null = origObj,
): O {
    Object.setPrototypeOf(obj, Object.getPrototypeOf(origObj));
    if (propertiesObj) {
        Object.defineProperties(
            obj,
            Object.getOwnPropertyDescriptors(propertiesObj),
        );
    }
    return obj;
}

export function equalsMap<K = unknown, V = unknown>(
    map1: Map<K, V> | readonly (readonly [K, V])[],
    map2: Map<K, V>,
): boolean {
    const map1arr = [...map1];
    return (
        map1arr.length === map2.size &&
        map1arr.every(
            ([key, value]) => map2.has(key) && map2.get(key) === value,
        )
    );
}

export function equalsSet<V = unknown>(
    set1: Set<V> | readonly V[],
    set2: Set<V>,
): boolean {
    const set1arr = [...set1];
    return (
        set1arr.length === set2.size && set1arr.every(value => set2.has(value))
    );
}

export function map2obj<K extends PropertyKey, V = unknown>(
    map: Map<K, V>,
): Record<K, V> {
    return [...map].reduce(
        (obj, [key, value]) => Object.assign(obj, { [key]: value }),
        {} as Record<K, V>,
    );
}

export function value2str(
    value: unknown,
    options: Pick<util.InspectOptions, 'depth' | 'maxArrayLength'> = {},
): string {
    return util.inspect(value, { depth: 1, ...options, breakLength: Infinity });
}

export function rfc3986EncodeURIComponent(uriComponent: string): string {
    return encodeURIComponent(uriComponent).replace(
        /[!'()*]/g,
        char =>
            '%' +
            char
                .charCodeAt(0)
                .toString(16)
                .toUpperCase(),
    );
}

export function joinURL(rootURL: string, urlPath: string): string {
    if (urlPath === '') {
        return rootURL;
    }
    return rootURL.replace(/\/*$/, '/') + urlPath.replace(/^\/*/, '');
}

export function path2url(pathstr: string): string {
    return pathstr
        .split(path.sep === '\\' ? /[\\/]/ : path.sep)
        .map(rfc3986EncodeURIComponent)
        .join('/');
}

export function url2path(urlpath: string): string {
    return urlpath
        .split('/')
        .map(decodeURIComponent)
        .join(path.sep);
}

export function findEqualsPath(
    baseDirpath: string,
    filepath: string,
    pathList: readonly string[],
): string | undefined {
    const absoluteFilepath = path.resolve(baseDirpath, filepath);
    return pathList.find(
        targetPath =>
            path.resolve(baseDirpath, targetPath) === absoluteFilepath,
    );
}

/**
 * @see https://stackoverflow.com/a/51321724/4907315
 */
export class MapWithDefault<K, V> extends Map<K, V> {
    private defaultGenerator: (key: K) => V;

    public constructor(defaultGenerator: (key: K) => V) {
        super();
        this.defaultGenerator = defaultGenerator;
    }

    public get(key: K): V {
        const value = super.get(key);
        if (value !== undefined) {
            return value;
        } else {
            const defaultValue = this.defaultGenerator(key);
            this.set(key, defaultValue);
            return defaultValue;
        }
    }
}

export function toBuffer(value: string | Buffer): Buffer {
    if (Buffer.isBuffer(value)) {
        return value;
    } else {
        return Buffer.from(value);
    }
}