11cb0ef41Sopenharmony_ci"use strict" 21cb0ef41Sopenharmony_ci 31cb0ef41Sopenharmony_ciconst wcwidth = require('./width') 41cb0ef41Sopenharmony_ciconst { 51cb0ef41Sopenharmony_ci padRight, 61cb0ef41Sopenharmony_ci padCenter, 71cb0ef41Sopenharmony_ci padLeft, 81cb0ef41Sopenharmony_ci splitIntoLines, 91cb0ef41Sopenharmony_ci splitLongWords, 101cb0ef41Sopenharmony_ci truncateString 111cb0ef41Sopenharmony_ci} = require('./utils') 121cb0ef41Sopenharmony_ci 131cb0ef41Sopenharmony_ciconst DEFAULT_HEADING_TRANSFORM = key => key.toUpperCase() 141cb0ef41Sopenharmony_ci 151cb0ef41Sopenharmony_ciconst DEFAULT_DATA_TRANSFORM = (cell, column, index) => cell 161cb0ef41Sopenharmony_ci 171cb0ef41Sopenharmony_ciconst DEFAULTS = Object.freeze({ 181cb0ef41Sopenharmony_ci maxWidth: Infinity, 191cb0ef41Sopenharmony_ci minWidth: 0, 201cb0ef41Sopenharmony_ci columnSplitter: ' ', 211cb0ef41Sopenharmony_ci truncate: false, 221cb0ef41Sopenharmony_ci truncateMarker: '…', 231cb0ef41Sopenharmony_ci preserveNewLines: false, 241cb0ef41Sopenharmony_ci paddingChr: ' ', 251cb0ef41Sopenharmony_ci showHeaders: true, 261cb0ef41Sopenharmony_ci headingTransform: DEFAULT_HEADING_TRANSFORM, 271cb0ef41Sopenharmony_ci dataTransform: DEFAULT_DATA_TRANSFORM 281cb0ef41Sopenharmony_ci}) 291cb0ef41Sopenharmony_ci 301cb0ef41Sopenharmony_cimodule.exports = function(items, options = {}) { 311cb0ef41Sopenharmony_ci 321cb0ef41Sopenharmony_ci let columnConfigs = options.config || {} 331cb0ef41Sopenharmony_ci delete options.config // remove config so doesn't appear on every column. 341cb0ef41Sopenharmony_ci 351cb0ef41Sopenharmony_ci let maxLineWidth = options.maxLineWidth || Infinity 361cb0ef41Sopenharmony_ci if (maxLineWidth === 'auto') maxLineWidth = process.stdout.columns || Infinity 371cb0ef41Sopenharmony_ci delete options.maxLineWidth // this is a line control option, don't pass it to column 381cb0ef41Sopenharmony_ci 391cb0ef41Sopenharmony_ci // Option defaults inheritance: 401cb0ef41Sopenharmony_ci // options.config[columnName] => options => DEFAULTS 411cb0ef41Sopenharmony_ci options = mixin({}, DEFAULTS, options) 421cb0ef41Sopenharmony_ci 431cb0ef41Sopenharmony_ci options.config = options.config || Object.create(null) 441cb0ef41Sopenharmony_ci 451cb0ef41Sopenharmony_ci options.spacing = options.spacing || '\n' // probably useless 461cb0ef41Sopenharmony_ci options.preserveNewLines = !!options.preserveNewLines 471cb0ef41Sopenharmony_ci options.showHeaders = !!options.showHeaders; 481cb0ef41Sopenharmony_ci options.columns = options.columns || options.include // alias include/columns, prefer columns if supplied 491cb0ef41Sopenharmony_ci let columnNames = options.columns || [] // optional user-supplied columns to include 501cb0ef41Sopenharmony_ci 511cb0ef41Sopenharmony_ci items = toArray(items, columnNames) 521cb0ef41Sopenharmony_ci 531cb0ef41Sopenharmony_ci // if not suppled column names, automatically determine columns from data keys 541cb0ef41Sopenharmony_ci if (!columnNames.length) { 551cb0ef41Sopenharmony_ci items.forEach(function(item) { 561cb0ef41Sopenharmony_ci for (let columnName in item) { 571cb0ef41Sopenharmony_ci if (columnNames.indexOf(columnName) === -1) columnNames.push(columnName) 581cb0ef41Sopenharmony_ci } 591cb0ef41Sopenharmony_ci }) 601cb0ef41Sopenharmony_ci } 611cb0ef41Sopenharmony_ci 621cb0ef41Sopenharmony_ci // initialize column defaults (each column inherits from options.config) 631cb0ef41Sopenharmony_ci let columns = columnNames.reduce((columns, columnName) => { 641cb0ef41Sopenharmony_ci let column = Object.create(options) 651cb0ef41Sopenharmony_ci columns[columnName] = mixin(column, columnConfigs[columnName]) 661cb0ef41Sopenharmony_ci return columns 671cb0ef41Sopenharmony_ci }, Object.create(null)) 681cb0ef41Sopenharmony_ci 691cb0ef41Sopenharmony_ci // sanitize column settings 701cb0ef41Sopenharmony_ci columnNames.forEach(columnName => { 711cb0ef41Sopenharmony_ci let column = columns[columnName] 721cb0ef41Sopenharmony_ci column.name = columnName 731cb0ef41Sopenharmony_ci column.maxWidth = Math.ceil(column.maxWidth) 741cb0ef41Sopenharmony_ci column.minWidth = Math.ceil(column.minWidth) 751cb0ef41Sopenharmony_ci column.truncate = !!column.truncate 761cb0ef41Sopenharmony_ci column.align = column.align || 'left' 771cb0ef41Sopenharmony_ci }) 781cb0ef41Sopenharmony_ci 791cb0ef41Sopenharmony_ci // sanitize data 801cb0ef41Sopenharmony_ci items = items.map(item => { 811cb0ef41Sopenharmony_ci let result = Object.create(null) 821cb0ef41Sopenharmony_ci columnNames.forEach(columnName => { 831cb0ef41Sopenharmony_ci // null/undefined -> '' 841cb0ef41Sopenharmony_ci result[columnName] = item[columnName] != null ? item[columnName] : '' 851cb0ef41Sopenharmony_ci // toString everything 861cb0ef41Sopenharmony_ci result[columnName] = '' + result[columnName] 871cb0ef41Sopenharmony_ci if (columns[columnName].preserveNewLines) { 881cb0ef41Sopenharmony_ci // merge non-newline whitespace chars 891cb0ef41Sopenharmony_ci result[columnName] = result[columnName].replace(/[^\S\n]/gmi, ' ') 901cb0ef41Sopenharmony_ci } else { 911cb0ef41Sopenharmony_ci // merge all whitespace chars 921cb0ef41Sopenharmony_ci result[columnName] = result[columnName].replace(/\s/gmi, ' ') 931cb0ef41Sopenharmony_ci } 941cb0ef41Sopenharmony_ci }) 951cb0ef41Sopenharmony_ci return result 961cb0ef41Sopenharmony_ci }) 971cb0ef41Sopenharmony_ci 981cb0ef41Sopenharmony_ci // transform data cells 991cb0ef41Sopenharmony_ci columnNames.forEach(columnName => { 1001cb0ef41Sopenharmony_ci let column = columns[columnName] 1011cb0ef41Sopenharmony_ci items = items.map((item, index) => { 1021cb0ef41Sopenharmony_ci let col = Object.create(column) 1031cb0ef41Sopenharmony_ci item[columnName] = column.dataTransform(item[columnName], col, index) 1041cb0ef41Sopenharmony_ci 1051cb0ef41Sopenharmony_ci let changedKeys = Object.keys(col) 1061cb0ef41Sopenharmony_ci // disable default heading transform if we wrote to column.name 1071cb0ef41Sopenharmony_ci if (changedKeys.indexOf('name') !== -1) { 1081cb0ef41Sopenharmony_ci if (column.headingTransform !== DEFAULT_HEADING_TRANSFORM) return 1091cb0ef41Sopenharmony_ci column.headingTransform = heading => heading 1101cb0ef41Sopenharmony_ci } 1111cb0ef41Sopenharmony_ci changedKeys.forEach(key => column[key] = col[key]) 1121cb0ef41Sopenharmony_ci return item 1131cb0ef41Sopenharmony_ci }) 1141cb0ef41Sopenharmony_ci }) 1151cb0ef41Sopenharmony_ci 1161cb0ef41Sopenharmony_ci // add headers 1171cb0ef41Sopenharmony_ci let headers = {} 1181cb0ef41Sopenharmony_ci if(options.showHeaders) { 1191cb0ef41Sopenharmony_ci columnNames.forEach(columnName => { 1201cb0ef41Sopenharmony_ci let column = columns[columnName] 1211cb0ef41Sopenharmony_ci 1221cb0ef41Sopenharmony_ci if(!column.showHeaders){ 1231cb0ef41Sopenharmony_ci headers[columnName] = ''; 1241cb0ef41Sopenharmony_ci return; 1251cb0ef41Sopenharmony_ci } 1261cb0ef41Sopenharmony_ci 1271cb0ef41Sopenharmony_ci headers[columnName] = column.headingTransform(column.name) 1281cb0ef41Sopenharmony_ci }) 1291cb0ef41Sopenharmony_ci items.unshift(headers) 1301cb0ef41Sopenharmony_ci } 1311cb0ef41Sopenharmony_ci // get actual max-width between min & max 1321cb0ef41Sopenharmony_ci // based on length of data in columns 1331cb0ef41Sopenharmony_ci columnNames.forEach(columnName => { 1341cb0ef41Sopenharmony_ci let column = columns[columnName] 1351cb0ef41Sopenharmony_ci column.width = items 1361cb0ef41Sopenharmony_ci .map(item => item[columnName]) 1371cb0ef41Sopenharmony_ci .reduce((min, cur) => { 1381cb0ef41Sopenharmony_ci // if already at maxWidth don't bother testing 1391cb0ef41Sopenharmony_ci if (min >= column.maxWidth) return min 1401cb0ef41Sopenharmony_ci return Math.max(min, Math.min(column.maxWidth, Math.max(column.minWidth, wcwidth(cur)))) 1411cb0ef41Sopenharmony_ci }, 0) 1421cb0ef41Sopenharmony_ci }) 1431cb0ef41Sopenharmony_ci 1441cb0ef41Sopenharmony_ci // split long words so they can break onto multiple lines 1451cb0ef41Sopenharmony_ci columnNames.forEach(columnName => { 1461cb0ef41Sopenharmony_ci let column = columns[columnName] 1471cb0ef41Sopenharmony_ci items = items.map(item => { 1481cb0ef41Sopenharmony_ci item[columnName] = splitLongWords(item[columnName], column.width, column.truncateMarker) 1491cb0ef41Sopenharmony_ci return item 1501cb0ef41Sopenharmony_ci }) 1511cb0ef41Sopenharmony_ci }) 1521cb0ef41Sopenharmony_ci 1531cb0ef41Sopenharmony_ci // wrap long lines. each item is now an array of lines. 1541cb0ef41Sopenharmony_ci columnNames.forEach(columnName => { 1551cb0ef41Sopenharmony_ci let column = columns[columnName] 1561cb0ef41Sopenharmony_ci items = items.map((item, index) => { 1571cb0ef41Sopenharmony_ci let cell = item[columnName] 1581cb0ef41Sopenharmony_ci item[columnName] = splitIntoLines(cell, column.width) 1591cb0ef41Sopenharmony_ci 1601cb0ef41Sopenharmony_ci // if truncating required, only include first line + add truncation char 1611cb0ef41Sopenharmony_ci if (column.truncate && item[columnName].length > 1) { 1621cb0ef41Sopenharmony_ci item[columnName] = splitIntoLines(cell, column.width - wcwidth(column.truncateMarker)) 1631cb0ef41Sopenharmony_ci let firstLine = item[columnName][0] 1641cb0ef41Sopenharmony_ci if (!endsWith(firstLine, column.truncateMarker)) item[columnName][0] += column.truncateMarker 1651cb0ef41Sopenharmony_ci item[columnName] = item[columnName].slice(0, 1) 1661cb0ef41Sopenharmony_ci } 1671cb0ef41Sopenharmony_ci return item 1681cb0ef41Sopenharmony_ci }) 1691cb0ef41Sopenharmony_ci }) 1701cb0ef41Sopenharmony_ci 1711cb0ef41Sopenharmony_ci // recalculate column widths from truncated output/lines 1721cb0ef41Sopenharmony_ci columnNames.forEach(columnName => { 1731cb0ef41Sopenharmony_ci let column = columns[columnName] 1741cb0ef41Sopenharmony_ci column.width = items.map(item => { 1751cb0ef41Sopenharmony_ci return item[columnName].reduce((min, cur) => { 1761cb0ef41Sopenharmony_ci if (min >= column.maxWidth) return min 1771cb0ef41Sopenharmony_ci return Math.max(min, Math.min(column.maxWidth, Math.max(column.minWidth, wcwidth(cur)))) 1781cb0ef41Sopenharmony_ci }, 0) 1791cb0ef41Sopenharmony_ci }).reduce((min, cur) => { 1801cb0ef41Sopenharmony_ci if (min >= column.maxWidth) return min 1811cb0ef41Sopenharmony_ci return Math.max(min, Math.min(column.maxWidth, Math.max(column.minWidth, cur))) 1821cb0ef41Sopenharmony_ci }, 0) 1831cb0ef41Sopenharmony_ci }) 1841cb0ef41Sopenharmony_ci 1851cb0ef41Sopenharmony_ci 1861cb0ef41Sopenharmony_ci let rows = createRows(items, columns, columnNames, options.paddingChr) // merge lines into rows 1871cb0ef41Sopenharmony_ci // conceive output 1881cb0ef41Sopenharmony_ci return rows.reduce((output, row) => { 1891cb0ef41Sopenharmony_ci return output.concat(row.reduce((rowOut, line) => { 1901cb0ef41Sopenharmony_ci return rowOut.concat(line.join(options.columnSplitter)) 1911cb0ef41Sopenharmony_ci }, [])) 1921cb0ef41Sopenharmony_ci }, []) 1931cb0ef41Sopenharmony_ci .map(line => truncateString(line, maxLineWidth)) 1941cb0ef41Sopenharmony_ci .join(options.spacing) 1951cb0ef41Sopenharmony_ci} 1961cb0ef41Sopenharmony_ci 1971cb0ef41Sopenharmony_ci/** 1981cb0ef41Sopenharmony_ci * Convert wrapped lines into rows with padded values. 1991cb0ef41Sopenharmony_ci * 2001cb0ef41Sopenharmony_ci * @param Array items data to process 2011cb0ef41Sopenharmony_ci * @param Array columns column width settings for wrapping 2021cb0ef41Sopenharmony_ci * @param Array columnNames column ordering 2031cb0ef41Sopenharmony_ci * @return Array items wrapped in arrays, corresponding to lines 2041cb0ef41Sopenharmony_ci */ 2051cb0ef41Sopenharmony_ci 2061cb0ef41Sopenharmony_cifunction createRows(items, columns, columnNames, paddingChr) { 2071cb0ef41Sopenharmony_ci return items.map(item => { 2081cb0ef41Sopenharmony_ci let row = [] 2091cb0ef41Sopenharmony_ci let numLines = 0 2101cb0ef41Sopenharmony_ci columnNames.forEach(columnName => { 2111cb0ef41Sopenharmony_ci numLines = Math.max(numLines, item[columnName].length) 2121cb0ef41Sopenharmony_ci }) 2131cb0ef41Sopenharmony_ci // combine matching lines of each rows 2141cb0ef41Sopenharmony_ci for (let i = 0; i < numLines; i++) { 2151cb0ef41Sopenharmony_ci row[i] = row[i] || [] 2161cb0ef41Sopenharmony_ci columnNames.forEach(columnName => { 2171cb0ef41Sopenharmony_ci let column = columns[columnName] 2181cb0ef41Sopenharmony_ci let val = item[columnName][i] || '' // || '' ensures empty columns get padded 2191cb0ef41Sopenharmony_ci if (column.align === 'right') row[i].push(padLeft(val, column.width, paddingChr)) 2201cb0ef41Sopenharmony_ci else if (column.align === 'center' || column.align === 'centre') row[i].push(padCenter(val, column.width, paddingChr)) 2211cb0ef41Sopenharmony_ci else row[i].push(padRight(val, column.width, paddingChr)) 2221cb0ef41Sopenharmony_ci }) 2231cb0ef41Sopenharmony_ci } 2241cb0ef41Sopenharmony_ci return row 2251cb0ef41Sopenharmony_ci }) 2261cb0ef41Sopenharmony_ci} 2271cb0ef41Sopenharmony_ci 2281cb0ef41Sopenharmony_ci/** 2291cb0ef41Sopenharmony_ci * Object.assign 2301cb0ef41Sopenharmony_ci * 2311cb0ef41Sopenharmony_ci * @return Object Object with properties mixed in. 2321cb0ef41Sopenharmony_ci */ 2331cb0ef41Sopenharmony_ci 2341cb0ef41Sopenharmony_cifunction mixin(...args) { 2351cb0ef41Sopenharmony_ci if (Object.assign) return Object.assign(...args) 2361cb0ef41Sopenharmony_ci return ObjectAssign(...args) 2371cb0ef41Sopenharmony_ci} 2381cb0ef41Sopenharmony_ci 2391cb0ef41Sopenharmony_cifunction ObjectAssign(target, firstSource) { 2401cb0ef41Sopenharmony_ci "use strict"; 2411cb0ef41Sopenharmony_ci if (target === undefined || target === null) 2421cb0ef41Sopenharmony_ci throw new TypeError("Cannot convert first argument to object"); 2431cb0ef41Sopenharmony_ci 2441cb0ef41Sopenharmony_ci var to = Object(target); 2451cb0ef41Sopenharmony_ci 2461cb0ef41Sopenharmony_ci var hasPendingException = false; 2471cb0ef41Sopenharmony_ci var pendingException; 2481cb0ef41Sopenharmony_ci 2491cb0ef41Sopenharmony_ci for (var i = 1; i < arguments.length; i++) { 2501cb0ef41Sopenharmony_ci var nextSource = arguments[i]; 2511cb0ef41Sopenharmony_ci if (nextSource === undefined || nextSource === null) 2521cb0ef41Sopenharmony_ci continue; 2531cb0ef41Sopenharmony_ci 2541cb0ef41Sopenharmony_ci var keysArray = Object.keys(Object(nextSource)); 2551cb0ef41Sopenharmony_ci for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) { 2561cb0ef41Sopenharmony_ci var nextKey = keysArray[nextIndex]; 2571cb0ef41Sopenharmony_ci try { 2581cb0ef41Sopenharmony_ci var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey); 2591cb0ef41Sopenharmony_ci if (desc !== undefined && desc.enumerable) 2601cb0ef41Sopenharmony_ci to[nextKey] = nextSource[nextKey]; 2611cb0ef41Sopenharmony_ci } catch (e) { 2621cb0ef41Sopenharmony_ci if (!hasPendingException) { 2631cb0ef41Sopenharmony_ci hasPendingException = true; 2641cb0ef41Sopenharmony_ci pendingException = e; 2651cb0ef41Sopenharmony_ci } 2661cb0ef41Sopenharmony_ci } 2671cb0ef41Sopenharmony_ci } 2681cb0ef41Sopenharmony_ci 2691cb0ef41Sopenharmony_ci if (hasPendingException) 2701cb0ef41Sopenharmony_ci throw pendingException; 2711cb0ef41Sopenharmony_ci } 2721cb0ef41Sopenharmony_ci return to; 2731cb0ef41Sopenharmony_ci} 2741cb0ef41Sopenharmony_ci 2751cb0ef41Sopenharmony_ci/** 2761cb0ef41Sopenharmony_ci * Adapted from String.prototype.endsWith polyfill. 2771cb0ef41Sopenharmony_ci */ 2781cb0ef41Sopenharmony_ci 2791cb0ef41Sopenharmony_cifunction endsWith(target, searchString, position) { 2801cb0ef41Sopenharmony_ci position = position || target.length; 2811cb0ef41Sopenharmony_ci position = position - searchString.length; 2821cb0ef41Sopenharmony_ci let lastIndex = target.lastIndexOf(searchString); 2831cb0ef41Sopenharmony_ci return lastIndex !== -1 && lastIndex === position; 2841cb0ef41Sopenharmony_ci} 2851cb0ef41Sopenharmony_ci 2861cb0ef41Sopenharmony_ci 2871cb0ef41Sopenharmony_cifunction toArray(items, columnNames) { 2881cb0ef41Sopenharmony_ci if (Array.isArray(items)) return items 2891cb0ef41Sopenharmony_ci let rows = [] 2901cb0ef41Sopenharmony_ci for (let key in items) { 2911cb0ef41Sopenharmony_ci let item = {} 2921cb0ef41Sopenharmony_ci item[columnNames[0] || 'key'] = key 2931cb0ef41Sopenharmony_ci item[columnNames[1] || 'value'] = items[key] 2941cb0ef41Sopenharmony_ci rows.push(item) 2951cb0ef41Sopenharmony_ci } 2961cb0ef41Sopenharmony_ci return rows 2971cb0ef41Sopenharmony_ci} 298