Comparing version 1.3.2 to 1.4.0
211
index.js
"use strict" | ||
var wcwidth = require('./width') | ||
var utils = require('./utils') | ||
var padRight = utils.padRight | ||
var padCenter = utils.padCenter | ||
var padLeft = utils.padLeft | ||
var splitIntoLines = utils.splitIntoLines | ||
var splitLongWords = utils.splitLongWords | ||
var truncateString = utils.truncateString | ||
const wcwidth = require('./width') | ||
const { | ||
padRight, | ||
padCenter, | ||
padLeft, | ||
splitIntoLines, | ||
splitLongWords, | ||
truncateString | ||
} = require('./utils') | ||
var DEFAULT_HEADING_TRANSFORM = function(key) { | ||
return key.toUpperCase() | ||
} | ||
const DEFAULT_HEADING_TRANSFORM = key => key.toUpperCase() | ||
var DEFAULT_DATA_TRANSFORM = function(cell, column, index) { | ||
return cell | ||
} | ||
const DEFAULT_DATA_TRANSFORM = (cell, column, index) => cell | ||
var DEFAULTS = { | ||
const DEFAULTS = Object.freeze({ | ||
maxWidth: Infinity, | ||
@@ -31,12 +28,11 @@ minWidth: 0, | ||
dataTransform: DEFAULT_DATA_TRANSFORM | ||
} | ||
}) | ||
module.exports = function(items, options) { | ||
module.exports = function(items, options = {}) { | ||
options = options || {} | ||
var columnConfigs = options.config || {} | ||
let columnConfigs = options.config || {} | ||
delete options.config // remove config so doesn't appear on every column. | ||
var maxLineWidth = options.maxLineWidth || Infinity | ||
let 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 | ||
@@ -46,3 +42,4 @@ | ||
// options.config[columnName] => options => DEFAULTS | ||
options = mixin(options, DEFAULTS) | ||
options = mixin({}, DEFAULTS, options) | ||
options.config = options.config || Object.create(null) | ||
@@ -54,3 +51,3 @@ | ||
options.columns = options.columns || options.include // alias include/columns, prefer columns if supplied | ||
var columnNames = options.columns || [] // optional user-supplied columns to include | ||
let columnNames = options.columns || [] // optional user-supplied columns to include | ||
@@ -62,3 +59,3 @@ items = toArray(items, columnNames) | ||
items.forEach(function(item) { | ||
for (var columnName in item) { | ||
for (let columnName in item) { | ||
if (columnNames.indexOf(columnName) === -1) columnNames.push(columnName) | ||
@@ -70,4 +67,4 @@ } | ||
// initialize column defaults (each column inherits from options.config) | ||
var columns = columnNames.reduce(function(columns, columnName) { | ||
var column = Object.create(options) | ||
let columns = columnNames.reduce((columns, columnName) => { | ||
let column = Object.create(options) | ||
columns[columnName] = mixin(column, columnConfigs[columnName]) | ||
@@ -78,4 +75,4 @@ return columns | ||
// sanitize column settings | ||
columnNames.forEach(function(columnName) { | ||
var column = columns[columnName] | ||
columnNames.forEach(columnName => { | ||
let column = columns[columnName] | ||
column.name = columnName | ||
@@ -89,5 +86,5 @@ column.maxWidth = Math.ceil(column.maxWidth) | ||
// sanitize data | ||
items = items.map(function(item) { | ||
var result = Object.create(null) | ||
columnNames.forEach(function(columnName) { | ||
items = items.map(item => { | ||
let result = Object.create(null) | ||
columnNames.forEach(columnName => { | ||
// null/undefined -> '' | ||
@@ -109,17 +106,15 @@ result[columnName] = item[columnName] != null ? item[columnName] : '' | ||
// transform data cells | ||
columnNames.forEach(function(columnName) { | ||
var column = columns[columnName] | ||
items = items.map(function(item, index) { | ||
var col = Object.create(column) | ||
columnNames.forEach(columnName => { | ||
let column = columns[columnName] | ||
items = items.map((item, index) => { | ||
let col = Object.create(column) | ||
item[columnName] = column.dataTransform(item[columnName], col, index) | ||
var changedKeys = Object.keys(col) | ||
let 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} | ||
column.headingTransform = heading => heading | ||
} | ||
changedKeys.forEach(function(key) { | ||
column[key] = col[key] | ||
}) | ||
changedKeys.forEach(key => column[key] = col[key]) | ||
return item | ||
@@ -130,6 +125,6 @@ }) | ||
// add headers | ||
var headers = {} | ||
let headers = {} | ||
if(options.showHeaders) { | ||
columnNames.forEach(function(columnName) { | ||
var column = columns[columnName] | ||
columnNames.forEach(columnName => { | ||
let column = columns[columnName] | ||
headers[columnName] = column.headingTransform(column.name) | ||
@@ -141,7 +136,7 @@ }) | ||
// 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) { | ||
columnNames.forEach(columnName => { | ||
let column = columns[columnName] | ||
column.width = items | ||
.map(item => item[columnName]) | ||
.reduce((min, cur) => { | ||
return Math.max(min, Math.min(column.maxWidth, Math.max(column.minWidth, wcwidth(cur)))) | ||
@@ -152,5 +147,5 @@ }, 0) | ||
// split long words so they can break onto multiple lines | ||
columnNames.forEach(function(columnName) { | ||
var column = columns[columnName] | ||
items = items.map(function(item) { | ||
columnNames.forEach(columnName => { | ||
let column = columns[columnName] | ||
items = items.map(item => { | ||
item[columnName] = splitLongWords(item[columnName], column.width, column.truncateMarker) | ||
@@ -162,6 +157,6 @@ 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] | ||
columnNames.forEach(columnName => { | ||
let column = columns[columnName] | ||
items = items.map((item, index) => { | ||
let cell = item[columnName] | ||
item[columnName] = splitIntoLines(cell, column.width) | ||
@@ -171,6 +166,6 @@ | ||
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) | ||
item[columnName] = splitIntoLines(cell, column.width - wcwidth(column.truncateMarker)) | ||
let firstLine = item[columnName][0] | ||
if (!endsWith(firstLine, column.truncateMarker)) item[columnName][0] += column.truncateMarker | ||
item[columnName] = item[columnName].slice(0, 1) | ||
} | ||
@@ -182,9 +177,9 @@ 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) { | ||
columnNames.forEach(columnName => { | ||
let column = columns[columnName] | ||
column.width = items.map(item => { | ||
return item[columnName].reduce((min, cur) => { | ||
return Math.max(min, Math.min(column.maxWidth, Math.max(column.minWidth, wcwidth(cur)))) | ||
}, 0) | ||
}).reduce(function(min, cur) { | ||
}).reduce((min, cur) => { | ||
return Math.max(min, Math.min(column.maxWidth, Math.max(column.minWidth, cur))) | ||
@@ -195,11 +190,11 @@ }, 0) | ||
var rows = createRows(items, columns, columnNames, options.paddingChr) // merge lines into rows | ||
let 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 rows.reduce((output, row) => { | ||
return output.concat(row.reduce((rowOut, line) => { | ||
return rowOut.concat(line.join(options.columnSplitter)) | ||
}, [])) | ||
}, []).map(function(line) { | ||
return truncateString(line, maxLineWidth) | ||
}).join(options.spacing) | ||
}, []) | ||
.map(line => truncateString(line, maxLineWidth)) | ||
.join(options.spacing) | ||
} | ||
@@ -217,14 +212,14 @@ | ||
function createRows(items, columns, columnNames, paddingChr) { | ||
return items.map(function(item) { | ||
var row = [] | ||
var numLines = 0 | ||
columnNames.forEach(function(columnName) { | ||
return items.map(item => { | ||
let row = [] | ||
let numLines = 0 | ||
columnNames.forEach(columnName => { | ||
numLines = Math.max(numLines, item[columnName].length) | ||
}) | ||
// combine matching lines of each rows | ||
for (var i = 0; i < numLines; i++) { | ||
for (let i = 0; i < numLines; i++) { | ||
row[i] = row[i] || [] | ||
columnNames.forEach(function(columnName) { | ||
var column = columns[columnName] | ||
var val = item[columnName][i] || '' // || '' ensures empty columns get padded | ||
columnNames.forEach(columnName => { | ||
let column = columns[columnName] | ||
let val = item[columnName][i] || '' // || '' ensures empty columns get padded | ||
if (column.align === 'right') row[i].push(padLeft(val, column.width, paddingChr)) | ||
@@ -240,18 +235,46 @@ else if (column.align === 'center' || column.align === 'centre') row[i].push(padCenter(val, column.width, paddingChr)) | ||
/** | ||
* Generic source->target mixin. | ||
* Copy properties from `source` into `target` if target doesn't have them. | ||
* Destructive. Modifies `target`. | ||
* Object.assign | ||
* | ||
* @param target Object target for mixin properties. | ||
* @param source Object source of mixin properties. | ||
* @return Object `target` after mixin applied. | ||
* @return Object Object with properties mixed in. | ||
*/ | ||
function mixin(target, source) { | ||
source = source || {} | ||
for (var key in source) { | ||
if (target.hasOwnProperty(key)) continue | ||
target[key] = source[key] | ||
function mixin(...args) { | ||
if (Object.assign) return Object.assign(...args) | ||
return ObjectAssign(...args) | ||
} | ||
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 target | ||
return to; | ||
} | ||
@@ -266,3 +289,3 @@ | ||
position = position - searchString.length; | ||
var lastIndex = target.lastIndexOf(searchString); | ||
let lastIndex = target.lastIndexOf(searchString); | ||
return lastIndex !== -1 && lastIndex === position; | ||
@@ -274,5 +297,5 @@ } | ||
if (Array.isArray(items)) return items | ||
var rows = [] | ||
for (var key in items) { | ||
var item = {} | ||
let rows = [] | ||
for (let key in items) { | ||
let item = {} | ||
item[columnNames[0] || 'key'] = key | ||
@@ -279,0 +302,0 @@ item[columnNames[1] || 'value'] = items[key] |
{ | ||
"name": "columnify", | ||
"version": "1.3.2", | ||
"version": "1.4.0", | ||
"description": "Render data in text columns. supports in-column text-wrap.", | ||
"main": "index.js", | ||
"main": "columnify.js", | ||
"scripts": { | ||
"pretest": "npm prune", | ||
"test": "tape test/*.js | tap-spec", | ||
"bench": "npm test && node bench" | ||
"test": "make prepublish && tape test/*.js | tap-spec", | ||
"bench": "npm test && node bench", | ||
"prepublish": "make prepublish" | ||
}, | ||
@@ -14,2 +15,3 @@ "author": "Tim Oxley", | ||
"devDependencies": { | ||
"6to5": "^2.13.7", | ||
"chalk": "^0.5.1", | ||
@@ -16,0 +18,0 @@ "tap-spec": "^2.1.1", |
178
Readme.md
@@ -39,3 +39,2 @@ # columnify | ||
```javascript | ||
var data = { | ||
@@ -104,11 +103,54 @@ "commander@0.6.1": 1, | ||
### Wrapping Column Cells | ||
### Filtering & Ordering Columns | ||
You can define the maximum width before wrapping for individual cells in | ||
columns. Minimum width is also supported. Wrapping will happen at word | ||
boundaries. Empty cells or those which do not fill the max/min width | ||
will be padded with spaces. | ||
By default, all properties are converted into columns, whether or not | ||
they exist on every object or not. | ||
To explicitly specify which columns to include, and in which order, | ||
supply a "columns" or "include" array ("include" is just an alias). | ||
```javascript | ||
var data = [{ | ||
name: 'module1', | ||
description: 'some description', | ||
version: '0.0.1', | ||
}, { | ||
name: 'module2', | ||
description: 'another description', | ||
version: '0.2.0', | ||
}] | ||
var columns = columnify(data, { | ||
columns: ['name', 'version'] | ||
}) | ||
console.log(columns) | ||
``` | ||
#### Output: | ||
``` | ||
NAME VERSION | ||
module1 0.0.1 | ||
module2 0.2.0 | ||
``` | ||
## Global and Per Column Options | ||
You can set a number of options at a global level (ie. for all columns) or on a per column basis. | ||
Set options on a per column basis by using the `config` option to specify individual columns: | ||
```javascript | ||
var columns = columnify(data, { | ||
optionName: optionValue, | ||
config: { | ||
columnName: {optionName: optionValue}, | ||
columnName: {optionName: optionValue}, | ||
} | ||
}) | ||
``` | ||
### Maximum and Minimum Column Widths | ||
As with all options, you can define the `maxWidth` and `minWidth` globally, or for specified columns. By default, wrapping will happen at word boundaries. Empty cells or those which do not fill the `minWidth` will be padded with spaces. | ||
```javascript | ||
var columns = columnify([{ | ||
@@ -122,2 +164,7 @@ name: 'mod1', | ||
version: '0.2.0', | ||
}], { | ||
minWidth: 20, | ||
config: { | ||
description: {maxWidth: 30} | ||
} | ||
}) | ||
@@ -127,16 +174,28 @@ | ||
``` | ||
#### Output: | ||
``` | ||
NAME DESCRIPTION VERSION | ||
mod1 some description which happens 0.0.1 | ||
to be far larger than the max | ||
module-two another description larger 0.2.0 | ||
than the max | ||
NAME DESCRIPTION VERSION | ||
mod1 some description which happens 0.0.1 | ||
to be far larger than the max | ||
module-two another description larger 0.2.0 | ||
than the max | ||
``` | ||
### Truncating Column Cells | ||
#### Maximum Line Width | ||
You can set a hard maximum line width using the `maxLineWidth` option. | ||
Beyond this value data is unceremoniously truncated with no truncation | ||
marker. | ||
This can either be a number or 'auto' to set the value to the width of | ||
stdout. | ||
Setting this value to 'auto' prevent TTY-imposed line-wrapping when | ||
lines exceed the screen width. | ||
#### Truncating Column Cells Instead of Wrapping | ||
You can disable wrapping and instead truncate content at the maximum | ||
column width. Truncation respects word boundaries. A truncation marker, | ||
`…` will appear next to the last word in any truncated line. | ||
column width by using the `truncate` option. Truncation respects word boundaries. A truncation marker, `…`, will appear next to the last word in any truncated line. | ||
@@ -162,39 +221,5 @@ ```javascript | ||
### Filtering & Ordering Columns | ||
By default, all properties are converted into columns, whether or not | ||
they exist on every object or not. | ||
To explicitly specify which columns to include, and in which order, | ||
supply a "columns" or "include" array ("include" is just an alias). | ||
```javascript | ||
var data = [{ | ||
name: 'module1', | ||
description: 'some description', | ||
version: '0.0.1', | ||
}, { | ||
name: 'module2', | ||
description: 'another description', | ||
version: '0.2.0', | ||
}] | ||
var columns = columnify(data, { | ||
columns: ['name', 'version'] // note description not included | ||
}) | ||
console.log(columns) | ||
``` | ||
#### Output: | ||
``` | ||
NAME VERSION | ||
module1 0.0.1 | ||
module2 0.2.0 | ||
``` | ||
## Other Configuration Options | ||
### Align Right/Center | ||
You can set the alignment of the column data by using the `align` option. | ||
@@ -219,7 +244,9 @@ ```js | ||
Align Center works in a similar way. | ||
`align: 'center'` works in a similar way. | ||
### Padding | ||
### Padding Character | ||
Set a character to fill whitespace within columns with the `paddingChr` option. | ||
```js | ||
@@ -241,3 +268,3 @@ var data = { | ||
### Preserve existing newlines | ||
### Preserve Existing Newlines | ||
@@ -294,3 +321,3 @@ By default, `columnify` sanitises text by replacing any occurance of 1 or more whitespace characters with a single space. | ||
You can change the truncation marker to something other than the default | ||
`…`. | ||
`…` by using the `truncateMarker` option. | ||
@@ -320,6 +347,5 @@ ```javascript | ||
If your columns need some bling, you can split columns with custom | ||
characters. | ||
characters by using the `columnSplitter` option. | ||
```javascript | ||
var columns = columnify(data, { | ||
@@ -338,2 +364,44 @@ columnSplitter: ' | ' | ||
### Control Header Display | ||
Control whether column headers are displayed by using the `showHeaders` option. | ||
```javascript | ||
var columns = columnify(data, { | ||
showHeaders: false | ||
}) | ||
``` | ||
### Transforming Column Data and Headers | ||
If you need to modify the presentation of column content or heading content there are two useful options for doing that: `dataTransform` and `headerTransform`. Both of these take a function and need to return a valid string. | ||
```javascript | ||
var columns = columnify([{ | ||
name: 'mod1', | ||
description: 'SOME DESCRIPTION TEXT.' | ||
}, { | ||
name: 'module-two', | ||
description: 'SOME SLIGHTLY LONGER DESCRIPTION TEXT.' | ||
}], { | ||
dataTransform: function(data) { | ||
return data.toLowerCase() | ||
}, | ||
config: { | ||
name: { | ||
headingTransform: function(heading) { | ||
heading = "module " + heading | ||
return "*" + heading.toUpperCase() + "*" | ||
} | ||
} | ||
} | ||
}) | ||
``` | ||
#### Output: | ||
``` | ||
*MODULE NAME* DESCRIPTION | ||
mod1 some description text. | ||
module-two some slightly longer description text. | ||
``` | ||
## Multibyte Character Support | ||
@@ -340,0 +408,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
36432
8
668
436
4