sounisi5011/metalsmith-pug-extra

View on GitHub
src/render.ts

Summary

Maintainability
B
5 hrs
Test Coverage
import createDebug from 'debug';
import deepFreeze from 'deep-freeze-strict';
import cloneDeep from 'lodash.clonedeep';
import Metalsmith from 'metalsmith';
import pug from 'pug';

import compileTemplateMap from './compileTemplateMap';
import {
    createPluginGenerator,
    FileInterface,
    getMatchedFiles,
    isFile,
} from './utils';
import { DeepReadonly } from './utils/types';

const debug = createDebug('metalsmith-pug-extra:render');
const debugOptions = debug.extend('options');

/*
 * Interfaces
 */

export type RenderOptionsInterface = DeepReadonly<
    WritableRenderOptionsInterface
>;

export interface WritableRenderOptionsInterface {
    locals: pug.LocalsObject;
    useMetadata: boolean;
    pattern: string | string[];
    reuse: boolean;
}

/*
 * Utility functions
 */

const previousRenderOptionsMap: WeakMap<
    Metalsmith,
    Partial<RenderOptionsInterface>
> = new WeakMap();

export function normalizeRenderOptions(
    metalsmith: Metalsmith,
    defaultOptions: RenderOptionsInterface,
    options: Partial<RenderOptionsInterface>,
): RenderOptionsInterface {
    debugOptions('normalizing options: %o', options);

    const partialOptions = {};
    const mergedOptions = { ...defaultOptions, ...options };

    if (mergedOptions.reuse && previousRenderOptionsMap.has(metalsmith)) {
        const previousRenderOptions = previousRenderOptionsMap.get(metalsmith);
        debugOptions('reuse previous options: %o', previousRenderOptions);
        Object.assign(partialOptions, previousRenderOptions);
    }

    Object.assign(partialOptions, options);

    debugOptions('save current options: %o', partialOptions);
    previousRenderOptionsMap.set(metalsmith, partialOptions);

    const normalizedOptions = {
        ...defaultOptions,
        ...partialOptions,
    };
    debugOptions('normalized options: %o', normalizedOptions);

    return normalizedOptions;
}

export function getRenderOptions<T extends RenderOptionsInterface>(
    options: T,
): RenderOptionsInterface & {
    otherOptions: Omit<T, keyof RenderOptionsInterface>;
} {
    const { locals, useMetadata, pattern, reuse, ...otherOptions } = options;
    return { locals, useMetadata, pattern, reuse, otherOptions };
}

export function getRenderedText(
    compileTemplate: pug.compileTemplate,
    filename: string,
    data: FileInterface,
    metalsmith: Metalsmith,
    options: RenderOptionsInterface,
): string {
    const { locals, useMetadata } = getRenderOptions(options);
    const pugLocals = cloneDeep(
        useMetadata ? { ...locals, ...metalsmith.metadata(), ...data } : locals,
    );

    debug(`rendering ${filename}`);
    const convertedText = compileTemplate(pugLocals);
    debug(`done rendering ${filename}`);

    return convertedText;
}

/*
 * Default options
 */

export const renderDefaultOptions: RenderOptionsInterface = deepFreeze({
    locals: {},
    useMetadata: false,
    pattern: ['**/*'],
    reuse: false,
});

/*
 * Main function
 */

export const render = createPluginGenerator((opts = {}) => {
    return (files, metalsmith, done) => {
        const options = normalizeRenderOptions(
            metalsmith,
            renderDefaultOptions,
            opts,
        );

        getMatchedFiles(files, options.pattern).forEach(filename => {
            const data: unknown = files[filename];
            if (!isFile(data)) {
                return;
            }

            const compileTemplate = compileTemplateMap.get(data);
            if (compileTemplate) {
                const convertedText = getRenderedText(
                    compileTemplate,
                    filename,
                    data,
                    metalsmith,
                    options,
                );

                data.contents = Buffer.from(convertedText, 'utf8');
                debug(`file contents updated: ${filename}`);
            }
        });

        done(null, files, metalsmith);
    };
}, renderDefaultOptions);