Home Reference Source Repository

src/documentation/generate-table.js

import 'source-map-support/register';

import deepExtend from 'deep-extend';
import { isUndefined } from 'lodash';
import stripAnsi from 'strip-ansi';

import { pad, addPadding } from './helpers';

const defaultSettings = {
    groupTitleWrapper: (name) => name,
    cellDivider: '|',
    rowWrapper: (input) => `|${input}|`,
    cellWrapper: (input) => ` ${input} `,
    header: true,
    compact: false
};

/**
 * Creates a table based on a {@link rocDocumentationObject}.
 *
 * @param {rocDocumentationObject} initalDocumentationObject - The documentation object to create a table of.
 * @param {rocTableHeader} header - Header object to use.
 * @param {rocTableSettings} settings - The settings to use.
 *
 * @returns {string} - A table.
 */
export default function generateTable(initalDocumentationObject, header, settings) {
    settings = deepExtend({}, defaultSettings, settings);
    const headerLengths = createLengthObject(header, header, {}, true);
    const lengths = createLengthObject(initalDocumentationObject, header, headerLengths);

    const printTableCell = (key, element, renderer = (input) => input || '', fill = false) => {
        if (fill) {
            return settings.cellWrapper(pad(lengths[key], '-'));
        }
        const padding = isUndefined(header[key].padding) ? true : header[key].padding;
        const text = padding ?
            addPadding(renderer(element), lengths[key]) :
            renderer(element);

        return settings.cellWrapper(text);
    };

    const printTableRow = (object, isHeader = false) => {
        return settings.rowWrapper(Object.keys(header).map((key) => {
            const { value, renderer } = getValueAndRenderer(isHeader, header[key], object[key]);

            return printTableCell(key, value, renderer);
        }).join(settings.cellDivider));
    };

    const printTableSplitter = () => {
        return settings.rowWrapper(Object.keys(header).map((key) =>
            printTableCell(key, undefined, undefined, true)
        ).join(settings.cellDivider));
    };

    const printTableHead = (name, description, level = 0) => {
        const rows = [];
        rows.push(settings.groupTitleWrapper(name, level));

        if (description) {
            rows.push(description);
        }

        // Add a new empty row
        rows.push('');

        if (settings.header) {
            rows.push(printTableRow(header, true));
            rows.push(printTableSplitter());
        }

        return rows;
    };

    const recursiveHelper = (documentationObject = []) => {
        const spacing = settings.compact ? [] : [''];

        return documentationObject.map((group) => {
            let rows = [];
            const level = group.level || 0;

            if (level === 0 || !settings.compact) {
                rows = rows.concat(printTableHead(group.name, group.description, level));
            }

            rows = rows.concat(group.objects.map((element) =>
                printTableRow(element)));

            rows = rows.concat(recursiveHelper(group.children));

            if (level === 0 && settings.compact) {
                rows.push('');
            }

            return rows;
        }).reduce((a, b) => a.concat(b), spacing);
    };

    return recursiveHelper(initalDocumentationObject).join('\n');
}

function createLengthObject(initalElements, header, initalLengths, isHeader = false) {
    const getObjectLength = (object, renderer = (input) => input) => {
        return stripAnsi(renderer(object)).length;
    };

    const getLength = (newLength, currentLength = 0) => {
        return newLength > currentLength ? newLength : currentLength;
    };

    const recursiveHelper = (elements = [], lengths = {}) => {
        let newLengths = { ...lengths };
        elements.forEach((element) => {
            element.objects.forEach((object) => {
                Object.keys(header).forEach((key) => {
                    const { value, renderer } = getValueAndRenderer(isHeader, header[key], object[key]);
                    if (value) {
                        newLengths[key] = getLength(getObjectLength(value, renderer), newLengths[key]);
                    }
                });
            });

            newLengths = recursiveHelper(element.children, newLengths);
        });

        return newLengths;
    };

    // Make sure the data matches the expected format
    if (!Array.isArray(initalElements)) {
        initalElements = [{objects: [initalElements], children: []}];
    }

    return recursiveHelper(initalElements, initalLengths);
}

function getValueAndRenderer(isHeader, headerObject, object) {
    const value = isHeader ? object.name : object;
    const renderer = isHeader ? (input) => input : headerObject.renderer;

    return {
        value,
        renderer
    };
}