vgno/roc-config

View on GitHub
src/documentation/generateTable.js

Summary

Maintainability
B
4 hrs
Test Coverage
import { isUndefined } from 'lodash';
import stripAnsi from 'strip-ansi';

import merge from '../helpers/merge';

import addPadding from './helpers/addPadding';
import pad from './helpers/pad';

/* eslint-disable no-unused-vars */
const defaultSettings = {
    groupTitleWrapper: (name, level, parents) => name,
    cellDivider: '|',
    rowWrapper: (input, level) => `|${input}|`,
    cellWrapper: (input) => ` ${input} `,
    header: true,
    compact: false,
};
/* eslint-enable */

/**
 * Creates a table based on a documentation object.
 *
 * @returns {string} - A formatted table.
 */
export default function generateTable(initalDocumentationObject, header, settings) {
    const finalSettings = merge(defaultSettings, settings);
    const headerLengths = createLengthObject(header, header, {}, true);
    const lengths = createLengthObject(initalDocumentationObject, header, headerLengths);

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

        return finalSettings.cellWrapper(text);
    };

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

            return printTableCell(key, value, renderer, undefined, object);
        }).join(finalSettings.cellDivider), level);

    const printTableSplitter = (level) =>
        finalSettings.rowWrapper(Object.keys(header).map((key) =>
            printTableCell(key, undefined, undefined, true)
        ).join(finalSettings.cellDivider), level);

    const printTableHead = ({ name, description, raw }, parentNames, level = 0, printTableHeader = true) => {
        const rows = [];
        rows.push(finalSettings.groupTitleWrapper(name, level, parentNames));

        if (description) {
            rows.push(description, '');
        } else if (finalSettings.header) {
            rows.push('');
        }

        if (raw) {
            rows.push('✓ ― Supports __raw', '');
        }

        if (finalSettings.header && printTableHeader) {
            rows.push(printTableRow(header, level, true));
            rows.push(printTableSplitter(level));
        }

        return rows;
    };

    const recursiveHelper = (documentationObject = [], parentNames, first = false) => {
        const spacing = finalSettings.compact || first ? [] : [''];

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

            if (level === 0 || !finalSettings.compact) {
                rows = rows.concat(
                    printTableHead(group, parentNames, level, objects.length > 0)
                );
            }

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

            rows = rows.concat(recursiveHelper(group.children, parentNames.concat(group.name)));

            if (group.level !== undefined && level === 0 && finalSettings.compact) {
                rows.push('');
            }

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

    return recursiveHelper(initalDocumentationObject, [], true).join('\n');
}

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

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

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

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

        return newLengths;
    };

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

    return recursiveHelper(elements, initalLengths);
}

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

    return {
        value,
        renderer,
    };
}