| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287 | 'use strict';const align = {    right: alignRight,    center: alignCenter};const top = 0;const right = 1;const bottom = 2;const left = 3;export class UI {    constructor(opts) {        var _a;        this.width = opts.width;        this.wrap = (_a = opts.wrap) !== null && _a !== void 0 ? _a : true;        this.rows = [];    }    span(...args) {        const cols = this.div(...args);        cols.span = true;    }    resetOutput() {        this.rows = [];    }    div(...args) {        if (args.length === 0) {            this.div('');        }        if (this.wrap && this.shouldApplyLayoutDSL(...args) && typeof args[0] === 'string') {            return this.applyLayoutDSL(args[0]);        }        const cols = args.map(arg => {            if (typeof arg === 'string') {                return this.colFromString(arg);            }            return arg;        });        this.rows.push(cols);        return cols;    }    shouldApplyLayoutDSL(...args) {        return args.length === 1 && typeof args[0] === 'string' &&            /[\t\n]/.test(args[0]);    }    applyLayoutDSL(str) {        const rows = str.split('\n').map(row => row.split('\t'));        let leftColumnWidth = 0;        // simple heuristic for layout, make sure the        // second column lines up along the left-hand.        // don't allow the first column to take up more        // than 50% of the screen.        rows.forEach(columns => {            if (columns.length > 1 && mixin.stringWidth(columns[0]) > leftColumnWidth) {                leftColumnWidth = Math.min(Math.floor(this.width * 0.5), mixin.stringWidth(columns[0]));            }        });        // generate a table:        //  replacing ' ' with padding calculations.        //  using the algorithmically generated width.        rows.forEach(columns => {            this.div(...columns.map((r, i) => {                return {                    text: r.trim(),                    padding: this.measurePadding(r),                    width: (i === 0 && columns.length > 1) ? leftColumnWidth : undefined                };            }));        });        return this.rows[this.rows.length - 1];    }    colFromString(text) {        return {            text,            padding: this.measurePadding(text)        };    }    measurePadding(str) {        // measure padding without ansi escape codes        const noAnsi = mixin.stripAnsi(str);        return [0, noAnsi.match(/\s*$/)[0].length, 0, noAnsi.match(/^\s*/)[0].length];    }    toString() {        const lines = [];        this.rows.forEach(row => {            this.rowToString(row, lines);        });        // don't display any lines with the        // hidden flag set.        return lines            .filter(line => !line.hidden)            .map(line => line.text)            .join('\n');    }    rowToString(row, lines) {        this.rasterize(row).forEach((rrow, r) => {            let str = '';            rrow.forEach((col, c) => {                const { width } = row[c]; // the width with padding.                const wrapWidth = this.negatePadding(row[c]); // the width without padding.                let ts = col; // temporary string used during alignment/padding.                if (wrapWidth > mixin.stringWidth(col)) {                    ts += ' '.repeat(wrapWidth - mixin.stringWidth(col));                }                // align the string within its column.                if (row[c].align && row[c].align !== 'left' && this.wrap) {                    const fn = align[row[c].align];                    ts = fn(ts, wrapWidth);                    if (mixin.stringWidth(ts) < wrapWidth) {                        ts += ' '.repeat((width || 0) - mixin.stringWidth(ts) - 1);                    }                }                // apply border and padding to string.                const padding = row[c].padding || [0, 0, 0, 0];                if (padding[left]) {                    str += ' '.repeat(padding[left]);                }                str += addBorder(row[c], ts, '| ');                str += ts;                str += addBorder(row[c], ts, ' |');                if (padding[right]) {                    str += ' '.repeat(padding[right]);                }                // if prior row is span, try to render the                // current row on the prior line.                if (r === 0 && lines.length > 0) {                    str = this.renderInline(str, lines[lines.length - 1]);                }            });            // remove trailing whitespace.            lines.push({                text: str.replace(/ +$/, ''),                span: row.span            });        });        return lines;    }    // if the full 'source' can render in    // the target line, do so.    renderInline(source, previousLine) {        const match = source.match(/^ */);        const leadingWhitespace = match ? match[0].length : 0;        const target = previousLine.text;        const targetTextWidth = mixin.stringWidth(target.trimRight());        if (!previousLine.span) {            return source;        }        // if we're not applying wrapping logic,        // just always append to the span.        if (!this.wrap) {            previousLine.hidden = true;            return target + source;        }        if (leadingWhitespace < targetTextWidth) {            return source;        }        previousLine.hidden = true;        return target.trimRight() + ' '.repeat(leadingWhitespace - targetTextWidth) + source.trimLeft();    }    rasterize(row) {        const rrows = [];        const widths = this.columnWidths(row);        let wrapped;        // word wrap all columns, and create        // a data-structure that is easy to rasterize.        row.forEach((col, c) => {            // leave room for left and right padding.            col.width = widths[c];            if (this.wrap) {                wrapped = mixin.wrap(col.text, this.negatePadding(col), { hard: true }).split('\n');            }            else {                wrapped = col.text.split('\n');            }            if (col.border) {                wrapped.unshift('.' + '-'.repeat(this.negatePadding(col) + 2) + '.');                wrapped.push("'" + '-'.repeat(this.negatePadding(col) + 2) + "'");            }            // add top and bottom padding.            if (col.padding) {                wrapped.unshift(...new Array(col.padding[top] || 0).fill(''));                wrapped.push(...new Array(col.padding[bottom] || 0).fill(''));            }            wrapped.forEach((str, r) => {                if (!rrows[r]) {                    rrows.push([]);                }                const rrow = rrows[r];                for (let i = 0; i < c; i++) {                    if (rrow[i] === undefined) {                        rrow.push('');                    }                }                rrow.push(str);            });        });        return rrows;    }    negatePadding(col) {        let wrapWidth = col.width || 0;        if (col.padding) {            wrapWidth -= (col.padding[left] || 0) + (col.padding[right] || 0);        }        if (col.border) {            wrapWidth -= 4;        }        return wrapWidth;    }    columnWidths(row) {        if (!this.wrap) {            return row.map(col => {                return col.width || mixin.stringWidth(col.text);            });        }        let unset = row.length;        let remainingWidth = this.width;        // column widths can be set in config.        const widths = row.map(col => {            if (col.width) {                unset--;                remainingWidth -= col.width;                return col.width;            }            return undefined;        });        // any unset widths should be calculated.        const unsetWidth = unset ? Math.floor(remainingWidth / unset) : 0;        return widths.map((w, i) => {            if (w === undefined) {                return Math.max(unsetWidth, _minWidth(row[i]));            }            return w;        });    }}function addBorder(col, ts, style) {    if (col.border) {        if (/[.']-+[.']/.test(ts)) {            return '';        }        if (ts.trim().length !== 0) {            return style;        }        return '  ';    }    return '';}// calculates the minimum width of// a column, based on padding preferences.function _minWidth(col) {    const padding = col.padding || [];    const minWidth = 1 + (padding[left] || 0) + (padding[right] || 0);    if (col.border) {        return minWidth + 4;    }    return minWidth;}function getWindowWidth() {    /* istanbul ignore next: depends on terminal */    if (typeof process === 'object' && process.stdout && process.stdout.columns) {        return process.stdout.columns;    }    return 80;}function alignRight(str, width) {    str = str.trim();    const strWidth = mixin.stringWidth(str);    if (strWidth < width) {        return ' '.repeat(width - strWidth) + str;    }    return str;}function alignCenter(str, width) {    str = str.trim();    const strWidth = mixin.stringWidth(str);    /* istanbul ignore next */    if (strWidth >= width) {        return str;    }    return ' '.repeat((width - strWidth) >> 1) + str;}let mixin;export function cliui(opts, _mixin) {    mixin = _mixin;    return new UI({        width: (opts === null || opts === void 0 ? void 0 : opts.width) || getWindowWidth(),        wrap: opts === null || opts === void 0 ? void 0 : opts.wrap    });}
 |