sounisi5011/metalsmith-netlify-published-date

View on GitHub
src/options/filename2urlPath.ts

Summary

Maintainability
C
1 day
Test Coverage
import importCwd from 'import-cwd';

import { OptionsInterface } from '../plugin';
import { hasProp, isObject, isStringArray, value2str } from '../utils';
import { strReturnFunc } from './utils';

const PROP = 'filename2urlPath';
type ReturnFuncType = OptionsInterface[typeof PROP];

/**
 * @example
 * {
 *   "filename2urlPath": "./get-page-url.js"
 * }
 */
export function convertStr(filepath: string): ReturnFuncType {
    /**
     * @see https://github.com/nodejs/node/blob/v12.7.0/lib/internal/modules/cjs/loader.js#L677-L680
     */
    if (filepath === '') {
        throw new TypeError(`"${PROP}" option must be a non-empty string`);
    }

    let func: unknown;
    try {
        func = importCwd(filepath);
    } catch (err) {
        throw new TypeError(
            `Failed to import module "${filepath}" specified in option "${PROP}"`,
        );
    }

    if (typeof func !== 'function') {
        throw new TypeError(
            `Module "${filepath}" specified in option "${PROP}" did not export the function: ${value2str(
                func,
            )}`,
        );
    }

    return strReturnFunc(
        func,
        `The function exported by module "${filepath}" specified in option "${PROP}" did not return a string`,
    );
}

/**
 * @example
 * {
 *   "filename2urlPath": {
 *     "metadata": "canonicalURLPath"
 *   }
 * }
 * @example
 * {
 *   "filename2urlPath": {
 *     "metadata": [
 *       "canonical",
 *       "canonicalURLPath"
 *     ]
 *   }
 * }
 */
export function convertMetadata({
    metadata: schema,
}: {
    metadata: unknown;
}): ReturnFuncType {
    if (typeof schema === 'string') {
        return (filename, { fileData }) => {
            if (hasProp(fileData, schema)) {
                const value = fileData[schema];
                if (typeof value === 'string') {
                    return value;
                } else {
                    throw new Error(
                        `The value of "${schema}" field of metadata of file "${filename}" is not a string: ${value2str(
                            value,
                        )}`,
                    );
                }
            } else {
                throw new Error(
                    `"${schema}" field does not exist in metadata of file "${filename}": ${value2str(
                        fileData,
                        { depth: 0 },
                    )}`,
                );
            }
        };
    } else if (Array.isArray(schema)) {
        const propList = schema;
        if (propList.length < 1) {
            throw new TypeError(
                `The value of the "metadata" field specified in option "${PROP}" is an empty array`,
            );
        } else if (isStringArray(propList)) {
            const propNameList = propList.join(', ');
            return (filename, { fileData }) => {
                for (const prop of propList) {
                    const value = fileData[prop];
                    if (typeof value === 'string') {
                        return value;
                    }
                }
                throw new Error(
                    `The following fields that stored string values in the metadata of file "${filename}" were not found: ${propNameList}`,
                );
            };
        } else {
            throw new TypeError(
                `The value of the "metadata" field specified in option "${PROP}" is not an array of strings: ${value2str(
                    propList,
                )}`,
            );
        }
    } else {
        throw new TypeError(
            `The value of the "metadata" field specified in option "${PROP}" is neither a string nor an array: ${value2str(
                schema,
            )}`,
        );
    }
}

/**
 * @example
 * {
 *   "filename2urlPath": {
 *     "replace": {
 *       "fromRegExp": "\\.[^\\\\/.]+$",
 *       "to": ".html"
 *     }
 *   }
 * }
 * @example
 * {
 *   "filename2urlPath": {
 *     "replace": {
 *       "fromStr": "/index.html",
 *       "to": "/"
 *     }
 *   }
 * }
 */
export function convertReplace({
    replace: schema,
}: {
    replace: unknown;
}): ReturnFuncType {
    if (!isObject(schema)) {
        throw new TypeError(
            `The value of the "replace" field specified in option "${PROP}" is not a object: ${value2str(
                schema,
            )}`,
        );
    }

    if (hasProp(schema, 'fromRegExp') && hasProp(schema, 'fromStr')) {
        throw new TypeError(
            `An object in the "replace" field of the "${PROP}" option can not contain both the "fromRegExp" property and the "fromStr" property: ${value2str(
                schema,
                { depth: 0 },
            )}`,
        );
    }
    if (hasProp(schema, 'fromRegExp') || hasProp(schema, 'fromStr')) {
        if (!hasProp(schema, 'to')) {
            throw new TypeError(
                `The value of the "replace" field of the "${PROP}" option must contain the "to" property: ${value2str(
                    schema,
                    { depth: 0 },
                )}`,
            );
        }
    } else {
        if (hasProp(schema, 'to')) {
            throw new TypeError(
                `The value of the "replace" field of the "${PROP}" option must contain the "fromRegExp" or "fromStr" property: ${value2str(
                    schema,
                    { depth: 0 },
                )}`,
            );
        } else {
            throw new TypeError(
                `The value of the "replace" field of the "${PROP}" option must contain the "fromRegExp" or "fromStr" property and the "to" property: ${value2str(
                    schema,
                    { depth: 0 },
                )}`,
            );
        }
    }

    const to = schema.to;
    if (typeof to !== 'string') {
        throw new TypeError(
            `The "to" property of the object in the "replace" field specified in the option "${PROP}" is not a string value: ${value2str(
                to,
            )}`,
        );
    }

    if (hasProp(schema, 'fromRegExp')) {
        const from = schema.fromRegExp;
        if (typeof from === 'string') {
            try {
                const fromPattern = new RegExp(from);
                return filename => filename.replace(fromPattern, to);
            } catch (error) {
                if (error instanceof SyntaxError) {
                    throw new SyntaxError(
                        `The "fromRegExp" property of the object in the "replace" field specified in the option "${PROP}" is an invalid regular expression: ${error.message}`,
                    );
                } else {
                    throw error;
                }
            }
        } else {
            throw new TypeError(
                `The "fromRegExp" property of the object in the "replace" field specified in the option "${PROP}" is not a string value: ${value2str(
                    from,
                )}`,
            );
        }
    } else {
        const from = schema.fromStr;
        if (typeof from === 'string') {
            return filename => filename.replace(from, to);
        } else {
            throw new TypeError(
                `The "fromStr" property of the object in the "replace" field specified in the option "${PROP}" is not a string value: ${value2str(
                    from,
                )}`,
            );
        }
    }
}

export function normalize(value: unknown): ReturnFuncType {
    if (typeof value === 'function') {
        return strReturnFunc(
            value,
            `The function set to option "${PROP}" did not return a string`,
        );
    } else if (typeof value === 'string') {
        return convertStr(value);
    } else if (isObject(value)) {
        if (hasProp(value, 'metadata')) {
            if (hasProp(value, 'replace')) {
                throw new TypeError(
                    `Object of option "${PROP}" must not contain both the "replace" property and "metadata" property: ${value2str(
                        value,
                        { depth: 0 },
                    )}`,
                );
            }
            return convertMetadata(value);
        } else if (hasProp(value, 'replace')) {
            return convertReplace(value);
        } else {
            throw new TypeError(
                `Object of option "${PROP}" must contain "metadata" property or "replace" property: ${value2str(
                    value,
                    { depth: 0 },
                )}`,
            );
        }
    } else {
        throw new TypeError(
            `The value of option "${PROP}" must be either a function, a string, or an object: ${value2str(
                value,
                { depth: 0 },
            )}`,
        );
    }
}