123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308 |
- "use strict";
- var wcwidth = require('./width');
- var _require = require('./utils');
- var padRight = _require.padRight;
- var padCenter = _require.padCenter;
- var padLeft = _require.padLeft;
- var splitIntoLines = _require.splitIntoLines;
- var splitLongWords = _require.splitLongWords;
- var truncateString = _require.truncateString;
- var DEFAULT_HEADING_TRANSFORM = function DEFAULT_HEADING_TRANSFORM(key) {
- return key.toUpperCase();
- };
- var DEFAULT_DATA_TRANSFORM = function DEFAULT_DATA_TRANSFORM(cell, column, index) {
- return cell;
- };
- var DEFAULTS = Object.freeze({
- maxWidth: Infinity,
- minWidth: 0,
- columnSplitter: ' ',
- truncate: false,
- truncateMarker: '…',
- preserveNewLines: false,
- paddingChr: ' ',
- showHeaders: true,
- headingTransform: DEFAULT_HEADING_TRANSFORM,
- dataTransform: DEFAULT_DATA_TRANSFORM
- });
- module.exports = function (items) {
- var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
- var columnConfigs = options.config || {};
- delete options.config; // remove config so doesn't appear on every column.
- var maxLineWidth = options.maxLineWidth || Infinity;
- if (maxLineWidth === 'auto') maxLineWidth = process.stdout.columns || Infinity;
- delete options.maxLineWidth; // this is a line control option, don't pass it to column
- // Option defaults inheritance:
- // options.config[columnName] => options => DEFAULTS
- options = mixin({}, DEFAULTS, options);
- options.config = options.config || Object.create(null);
- options.spacing = options.spacing || '\n'; // probably useless
- options.preserveNewLines = !!options.preserveNewLines;
- options.showHeaders = !!options.showHeaders;
- options.columns = options.columns || options.include; // alias include/columns, prefer columns if supplied
- var columnNames = options.columns || []; // optional user-supplied columns to include
- items = toArray(items, columnNames);
- // if not suppled column names, automatically determine columns from data keys
- if (!columnNames.length) {
- items.forEach(function (item) {
- for (var columnName in item) {
- if (columnNames.indexOf(columnName) === -1) columnNames.push(columnName);
- }
- });
- }
- // initialize column defaults (each column inherits from options.config)
- var columns = columnNames.reduce(function (columns, columnName) {
- var column = Object.create(options);
- columns[columnName] = mixin(column, columnConfigs[columnName]);
- return columns;
- }, Object.create(null));
- // sanitize column settings
- columnNames.forEach(function (columnName) {
- var column = columns[columnName];
- column.name = columnName;
- column.maxWidth = Math.ceil(column.maxWidth);
- column.minWidth = Math.ceil(column.minWidth);
- column.truncate = !!column.truncate;
- column.align = column.align || 'left';
- });
- // sanitize data
- items = items.map(function (item) {
- var result = Object.create(null);
- columnNames.forEach(function (columnName) {
- // null/undefined -> ''
- result[columnName] = item[columnName] != null ? item[columnName] : '';
- // toString everything
- result[columnName] = '' + result[columnName];
- if (columns[columnName].preserveNewLines) {
- // merge non-newline whitespace chars
- result[columnName] = result[columnName].replace(/[^\S\n]/gmi, ' ');
- } else {
- // merge all whitespace chars
- result[columnName] = result[columnName].replace(/\s/gmi, ' ');
- }
- });
- return result;
- });
- // transform data cells
- columnNames.forEach(function (columnName) {
- var column = columns[columnName];
- items = items.map(function (item, index) {
- var col = Object.create(column);
- item[columnName] = column.dataTransform(item[columnName], col, index);
- var changedKeys = Object.keys(col);
- // disable default heading transform if we wrote to column.name
- if (changedKeys.indexOf('name') !== -1) {
- if (column.headingTransform !== DEFAULT_HEADING_TRANSFORM) return;
- column.headingTransform = function (heading) {
- return heading;
- };
- }
- changedKeys.forEach(function (key) {
- return column[key] = col[key];
- });
- return item;
- });
- });
- // add headers
- var headers = {};
- if (options.showHeaders) {
- columnNames.forEach(function (columnName) {
- var column = columns[columnName];
- if (!column.showHeaders) {
- headers[columnName] = '';
- return;
- }
- headers[columnName] = column.headingTransform(column.name);
- });
- items.unshift(headers);
- }
- // get actual max-width between min & max
- // based on length of data in columns
- columnNames.forEach(function (columnName) {
- var column = columns[columnName];
- column.width = items.map(function (item) {
- return item[columnName];
- }).reduce(function (min, cur) {
- // if already at maxWidth don't bother testing
- if (min >= column.maxWidth) return min;
- return Math.max(min, Math.min(column.maxWidth, Math.max(column.minWidth, wcwidth(cur))));
- }, 0);
- });
- // split long words so they can break onto multiple lines
- columnNames.forEach(function (columnName) {
- var column = columns[columnName];
- items = items.map(function (item) {
- item[columnName] = splitLongWords(item[columnName], column.width, column.truncateMarker);
- return item;
- });
- });
- // wrap long lines. each item is now an array of lines.
- columnNames.forEach(function (columnName) {
- var column = columns[columnName];
- items = items.map(function (item, index) {
- var cell = item[columnName];
- item[columnName] = splitIntoLines(cell, column.width);
- // if truncating required, only include first line + add truncation char
- if (column.truncate && item[columnName].length > 1) {
- item[columnName] = splitIntoLines(cell, column.width - wcwidth(column.truncateMarker));
- var firstLine = item[columnName][0];
- if (!endsWith(firstLine, column.truncateMarker)) item[columnName][0] += column.truncateMarker;
- item[columnName] = item[columnName].slice(0, 1);
- }
- return item;
- });
- });
- // recalculate column widths from truncated output/lines
- columnNames.forEach(function (columnName) {
- var column = columns[columnName];
- column.width = items.map(function (item) {
- return item[columnName].reduce(function (min, cur) {
- if (min >= column.maxWidth) return min;
- return Math.max(min, Math.min(column.maxWidth, Math.max(column.minWidth, wcwidth(cur))));
- }, 0);
- }).reduce(function (min, cur) {
- if (min >= column.maxWidth) return min;
- return Math.max(min, Math.min(column.maxWidth, Math.max(column.minWidth, cur)));
- }, 0);
- });
- var rows = createRows(items, columns, columnNames, options.paddingChr); // merge lines into rows
- // conceive output
- return rows.reduce(function (output, row) {
- return output.concat(row.reduce(function (rowOut, line) {
- return rowOut.concat(line.join(options.columnSplitter));
- }, []));
- }, []).map(function (line) {
- return truncateString(line, maxLineWidth);
- }).join(options.spacing);
- };
- /**
- * Convert wrapped lines into rows with padded values.
- *
- * @param Array items data to process
- * @param Array columns column width settings for wrapping
- * @param Array columnNames column ordering
- * @return Array items wrapped in arrays, corresponding to lines
- */
- function createRows(items, columns, columnNames, paddingChr) {
- return items.map(function (item) {
- var row = [];
- var numLines = 0;
- columnNames.forEach(function (columnName) {
- numLines = Math.max(numLines, item[columnName].length);
- });
- // combine matching lines of each rows
- var _loop = function _loop(i) {
- row[i] = row[i] || [];
- columnNames.forEach(function (columnName) {
- var column = columns[columnName];
- var val = item[columnName][i] || ''; // || '' ensures empty columns get padded
- if (column.align === 'right') row[i].push(padLeft(val, column.width, paddingChr));else if (column.align === 'center' || column.align === 'centre') row[i].push(padCenter(val, column.width, paddingChr));else row[i].push(padRight(val, column.width, paddingChr));
- });
- };
- for (var i = 0; i < numLines; i++) {
- _loop(i);
- }
- return row;
- });
- }
- /**
- * Object.assign
- *
- * @return Object Object with properties mixed in.
- */
- function mixin() {
- var _Object;
- if (Object.assign) return (_Object = Object).assign.apply(_Object, arguments);
- return ObjectAssign.apply(undefined, arguments);
- }
- function ObjectAssign(target, firstSource) {
- "use strict";
- if (target === undefined || target === null) throw new TypeError("Cannot convert first argument to object");
- var to = Object(target);
- var hasPendingException = false;
- var pendingException;
- for (var i = 1; i < arguments.length; i++) {
- var nextSource = arguments[i];
- if (nextSource === undefined || nextSource === null) continue;
- var keysArray = Object.keys(Object(nextSource));
- for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
- var nextKey = keysArray[nextIndex];
- try {
- var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
- if (desc !== undefined && desc.enumerable) to[nextKey] = nextSource[nextKey];
- } catch (e) {
- if (!hasPendingException) {
- hasPendingException = true;
- pendingException = e;
- }
- }
- }
- if (hasPendingException) throw pendingException;
- }
- return to;
- }
- /**
- * Adapted from String.prototype.endsWith polyfill.
- */
- function endsWith(target, searchString, position) {
- position = position || target.length;
- position = position - searchString.length;
- var lastIndex = target.lastIndexOf(searchString);
- return lastIndex !== -1 && lastIndex === position;
- }
- function toArray(items, columnNames) {
- if (Array.isArray(items)) return items;
- var rows = [];
- for (var key in items) {
- var item = {};
- item[columnNames[0] || 'key'] = key;
- item[columnNames[1] || 'value'] = items[key];
- rows.push(item);
- }
- return rows;
- }
|