Comparing version 0.1.2 to 1.0.0
45
index.js
"use strict" | ||
var wcwidth = require('wcwidth.js')({ monkeypatch: false, control: 0 }) | ||
var utils = require('./utils') | ||
@@ -7,2 +8,3 @@ var padRight = utils.padRight | ||
var splitLongWords = utils.splitLongWords | ||
var truncateString = utils.truncateString | ||
@@ -15,2 +17,3 @@ var DEFAULTS = { | ||
truncateMarker: '…', | ||
preserveNewLines: false, | ||
headingTransform: function(key) { | ||
@@ -31,2 +34,5 @@ return key.toUpperCase() | ||
var maxLineWidth = options.maxLineWidth || Infinity | ||
delete options.maxLineWidth // this is a line control option, don't pass it to column | ||
// Option defaults inheritance: | ||
@@ -38,4 +44,7 @@ // options.config[columnName] => options => DEFAULTS | ||
options.spacing = options.spacing || '\n' // probably useless | ||
options.preserveNewLines = !!options.preserveNewLines | ||
options.columns = options.columns || options.include // alias include/columns, prefer columns if supplied | ||
var columnNames = options.columns || [] // optional user-supplied columns to include | ||
var columnNames = options.include || [] // optional user-supplied columns to include | ||
items = toArray(items, columnNames) | ||
@@ -74,4 +83,9 @@ // if not suppled column names, automatically determine columns from data keys | ||
result[columnName] = '' + result[columnName] | ||
// remove funky chars | ||
result[columnName] = result[columnName].replace(/\s+/g, " ") | ||
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, ' ') | ||
} | ||
}) | ||
@@ -105,3 +119,3 @@ return result | ||
}).reduce(function(min, cur) { | ||
return Math.max(min, Math.min(column.maxWidth, Math.max(column.minWidth, cur.length))) | ||
return Math.max(min, Math.min(column.maxWidth, Math.max(column.minWidth, wcwidth(cur)))) | ||
}, 0) | ||
@@ -128,3 +142,3 @@ }) | ||
if (column.truncate && item[columnName].length > 1) { | ||
item[columnName] = splitIntoLines(cell, column.width - column.truncateMarker.length) | ||
item[columnName] = splitIntoLines(cell, column.width - wcwidth(column.truncateMarker)) | ||
var firstLine = item[columnName][0] | ||
@@ -143,3 +157,3 @@ if (!endsWith(firstLine, column.truncateMarker)) item[columnName][0] += column.truncateMarker | ||
return item[columnName].reduce(function(min, cur) { | ||
return Math.max(min, Math.min(column.maxWidth, Math.max(column.minWidth, cur.length))) | ||
return Math.max(min, Math.min(column.maxWidth, Math.max(column.minWidth, wcwidth(cur)))) | ||
}, 0) | ||
@@ -151,4 +165,4 @@ }).reduce(function(min, cur) { | ||
var rows = createRows(items, columns, columnNames) // merge lines into rows | ||
// conceive output | ||
@@ -159,3 +173,5 @@ return rows.reduce(function(output, row) { | ||
}, [])) | ||
}, []).join(options.spacing) | ||
}, []).map(function(line) { | ||
return truncateString(line, maxLineWidth) | ||
}).join(options.spacing) | ||
} | ||
@@ -221,1 +237,14 @@ | ||
} | ||
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 | ||
} |
{ | ||
"name": "columnify", | ||
"version": "0.1.2", | ||
"version": "1.0.0", | ||
"description": "Render data in text columns, supports in-column text-wrap.", | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "tap test" | ||
"test": "faucet" | ||
}, | ||
@@ -12,4 +12,4 @@ "author": "Tim Oxley", | ||
"devDependencies": { | ||
"tape": "~2.3.0", | ||
"tap": "~0.4.6" | ||
"faucet": "0.0.1", | ||
"tape": "~2.12.3" | ||
}, | ||
@@ -32,3 +32,9 @@ "repository": { | ||
}, | ||
"homepage": "https://github.com/timoxley/columnify" | ||
"homepage": "https://github.com/timoxley/columnify", | ||
"dependencies": { | ||
"wcwidth.js": "~0.0.4" | ||
}, | ||
"directories": { | ||
"test": "test" | ||
} | ||
} |
209
Readme.md
@@ -5,5 +5,9 @@ # columnify | ||
Create text-based columns suitable for console output. | ||
Supports minimum and maximum column widths via truncation and text wrapping. | ||
Create text-based columns suitable for console output from objects or | ||
arrays of objects. | ||
Columns are automatically resized to fit the content of the largest | ||
cell. Each cell will be padded with spaces to fill the available space | ||
and ensure column contents are left-aligned. | ||
Designed to [handle sensible wrapping in npm search results](https://github.com/isaacs/npm/pull/2328). | ||
@@ -23,3 +27,3 @@ | ||
```js | ||
```javascript | ||
var columnify = require('columnify') | ||
@@ -32,10 +36,52 @@ var columns = columnify(data, options) | ||
### Simple Columns | ||
### Columnify Objects | ||
Text is aligned under column headings. Columns are automatically resized | ||
to fit the content of the largest cell. Each cell will be padded with | ||
spaces to fill the available space and ensure column contents are | ||
left-aligned. | ||
Objects are converted to a list of key/value pairs: | ||
```js | ||
```javascript | ||
var data = { | ||
"commander@0.6.1": 1, | ||
"minimatch@0.2.14": 3, | ||
"mkdirp@0.3.5": 2, | ||
"sigmund@1.0.0": 3 | ||
} | ||
console.log(columnify(data)) | ||
``` | ||
#### Output: | ||
``` | ||
KEY VALUE | ||
commander@0.6.1 1 | ||
minimatch@0.2.14 3 | ||
mkdirp@0.3.5 2 | ||
sigmund@1.0.0 3 | ||
``` | ||
### Custom Column Names | ||
```javascript | ||
var data = { | ||
"commander@0.6.1": 1, | ||
"minimatch@0.2.14": 3, | ||
"mkdirp@0.3.5": 2, | ||
"sigmund@1.0.0": 3 | ||
} | ||
console.log(columnify(data, {columns: ['MODULE', 'COUNT']})) | ||
``` | ||
#### Output: | ||
``` | ||
MODULE COUNT | ||
commander@0.6.1 1 | ||
minimatch@0.2.14 3 | ||
mkdirp@0.3.5 2 | ||
sigmund@1.0.0 3 | ||
``` | ||
### Columnify Arrays of Objects | ||
Column headings are extracted from the keys in supplied objects. | ||
```javascript | ||
var columnify = require('columnify') | ||
@@ -53,2 +99,3 @@ | ||
``` | ||
#### Output: | ||
``` | ||
@@ -67,4 +114,3 @@ NAME VERSION | ||
```js | ||
var columnify = require('columnify') | ||
```javascript | ||
@@ -83,2 +129,3 @@ var columns = columnify([{ | ||
``` | ||
#### Output: | ||
``` | ||
@@ -92,3 +139,3 @@ NAME DESCRIPTION VERSION | ||
### Truncated Columns | ||
### Truncating Column Cells | ||
@@ -99,3 +146,3 @@ You can disable wrapping and instead truncate content at the maximum | ||
```js | ||
```javascript | ||
var columns = columnify(data, { | ||
@@ -112,3 +159,3 @@ truncate: true, | ||
``` | ||
#### Output: | ||
``` | ||
@@ -120,3 +167,87 @@ NAME DESCRIPTION VERSION | ||
### 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 | ||
### Preserve existing newlines | ||
By default, `columnify` sanitises text by replacing any occurance of 1 or more whitespace characters with a single space. | ||
`columnify` can be configured to respect existing new line characters using the `preserveNewLines` option. Note this will still collapse all other whitespace. | ||
```javascript | ||
var data = [{ | ||
name: "glob@3.2.9", | ||
paths: [ | ||
"node_modules/tap/node_modules/glob", | ||
"node_modules/tape/node_modules/glob" | ||
].join('\n') | ||
}, { | ||
name: "nopt@2.2.1", | ||
paths: [ | ||
"node_modules/tap/node_modules/nopt" | ||
] | ||
}, { | ||
name: "runforcover@0.0.2", | ||
paths: "node_modules/tap/node_modules/runforcover" | ||
}] | ||
console.log(columnify(data, {preserveNewLines: true})) | ||
``` | ||
#### Output: | ||
``` | ||
NAME PATHS | ||
glob@3.2.9 node_modules/tap/node_modules/glob | ||
node_modules/tape/node_modules/glob | ||
nopt@2.2.1 node_modules/tap/node_modules/nopt | ||
runforcover@0.0.2 node_modules/tap/node_modules/runforcover | ||
``` | ||
Compare this with output without `preserveNewLines`: | ||
```javascript | ||
console.log(columnify(data, {preserveNewLines: false})) | ||
// or just | ||
console.log(columnify(data)) | ||
``` | ||
``` | ||
NAME PATHS | ||
glob@3.2.9 node_modules/tap/node_modules/glob node_modules/tape/node_modules/glob | ||
nopt@2.2.1 node_modules/tap/node_modules/nopt | ||
runforcover@0.0.2 node_modules/tap/node_modules/runforcover | ||
``` | ||
### Custom Truncation Marker | ||
@@ -127,3 +258,3 @@ | ||
```js | ||
```javascript | ||
var columns = columnify(data, { | ||
@@ -141,3 +272,3 @@ truncate: true, | ||
``` | ||
#### Output: | ||
``` | ||
@@ -154,3 +285,3 @@ NAME DESCRIPTION VERSION | ||
```js | ||
```javascript | ||
@@ -163,2 +294,3 @@ var columns = columnify(data, { | ||
``` | ||
#### Output: | ||
``` | ||
@@ -170,35 +302,42 @@ NAME | DESCRIPTION | VERSION | ||
### Filtering & Ordering Columns | ||
## Multibyte Character Support | ||
By default, all properties are converted into columns, whether or not | ||
they exist on every object or not. | ||
`columnify` uses [mycoboco/wcwidth.js](https://github.com/mycoboco/wcwidth.js) to calculate length of multibyte characters: | ||
To explicitly specify which columns to include, and in which order, | ||
supply an "include" array: | ||
```js | ||
```javascript | ||
var data = [{ | ||
name: 'module1', | ||
name: 'module-one', | ||
description: 'some description', | ||
version: '0.0.1', | ||
}, { | ||
name: 'module2', | ||
description: 'another description', | ||
version: '0.2.0', | ||
name: '这是一个很长的名字的模块', | ||
description: '这真的是一个描述的内容这个描述很长', | ||
version: "0.3.3" | ||
}] | ||
var columns = columnify(data, { | ||
include: ['name', 'version'] // note description not included | ||
}) | ||
console.log(columnify(data)) | ||
``` | ||
console.log(columns) | ||
#### Without multibyte handling: | ||
i.e. before columnify added this feature | ||
``` | ||
NAME DESCRIPTION VERSION | ||
module-one some description 0.0.1 | ||
这是一个很长的名字的模块 这真的是一个描述的内容这个描述很长 0.3.3 | ||
``` | ||
#### With multibyte handling: | ||
``` | ||
NAME VERSION | ||
module1 0.0.1 | ||
module2 0.2.0 | ||
NAME DESCRIPTION VERSION | ||
module-one some description 0.0.1 | ||
这是一个很长的名字的模块 这真的是一个描述的内容这个描述很长 0.3.3 | ||
``` | ||
## License | ||
MIT | ||
85
utils.js
@@ -0,1 +1,5 @@ | ||
"use strict" | ||
var wcwidth = require('wcwidth.js')({ monkeypatch: false, control: 0 }) | ||
/** | ||
@@ -14,3 +18,3 @@ * Pad `str` up to total length `max` with `chr`. | ||
str = String(str) | ||
var length = 1 + max - str.length | ||
var length = 1 + max - wcwidth(str) | ||
if (length <= 0) return str | ||
@@ -23,3 +27,3 @@ return str + Array.apply(null, {length: length}) | ||
* Split a String `str` into lines of maxiumum length `max`. | ||
* Splits on word boundaries. | ||
* Splits on word boundaries. Preserves existing new lines. | ||
* | ||
@@ -32,12 +36,19 @@ * @param String str string to split | ||
function splitIntoLines(str, max) { | ||
return str.trim().split(' ').reduce(function(lines, word) { | ||
var line = lines[lines.length - 1] | ||
if (line && line.join(' ').length + word.length < max) { | ||
lines[lines.length - 1].push(word) // add to line | ||
} | ||
else lines.push([word]) // new line | ||
return lines | ||
}, []).map(function(l) { | ||
return l.join(' ') | ||
}) | ||
function _splitIntoLines(str, max) { | ||
return str.trim().split(' ').reduce(function(lines, word) { | ||
var line = lines[lines.length - 1] | ||
if (line && wcwidth(line.join(' ')) + wcwidth(word) < max) { | ||
lines[lines.length - 1].push(word) // add to line | ||
} | ||
else lines.push([word]) // new line | ||
return lines | ||
}, []).map(function(l) { | ||
return l.join(' ') | ||
}) | ||
} | ||
return str.split('\n').map(function(str) { | ||
return _splitIntoLines(str, max) | ||
}).reduce(function(lines, line) { | ||
return lines.concat(line) | ||
}, []) | ||
} | ||
@@ -61,8 +72,19 @@ | ||
var word = words.shift() || str | ||
if (wcwidth(word) > max) { | ||
// slice is based on length no wcwidth | ||
var i = 0 | ||
var wwidth = 0 | ||
var limit = max - wcwidth(truncationChar) | ||
while (i < word.length) { | ||
var w = wcwidth(word.charAt(i)) | ||
if(w + wwidth > limit) | ||
break | ||
wwidth += w | ||
++i | ||
} | ||
if (word.length > max) { | ||
var remainder = word.slice(max - truncationChar.length) // get remainder | ||
var remainder = word.slice(i) // get remainder | ||
words.unshift(remainder) // save remainder for next loop | ||
word = word.slice(0, max - truncationChar.length) // grab truncated word | ||
word = word.slice(0, i) // grab truncated word | ||
word += truncationChar // add trailing … or whatever | ||
@@ -74,3 +96,34 @@ } | ||
/** | ||
* Truncate `str` into total width `max` | ||
* If `str` is shorter than `max`, will return `str` unaltered. | ||
* | ||
* @param String str string to truncated | ||
* @param Number max total wcwidth of output string | ||
* @return String truncated str | ||
*/ | ||
function truncateString(str, max) { | ||
str = str != null ? str : '' | ||
str = String(str) | ||
if(max == Infinity) return str | ||
var i = 0 | ||
var wwidth = 0 | ||
while (i < str.length) { | ||
var w = wcwidth(str.charAt(i)) | ||
if(w + wwidth > max) | ||
break | ||
wwidth += w | ||
++i | ||
} | ||
return str.slice(0, i) | ||
} | ||
/** | ||
* Exports | ||
@@ -82,1 +135,3 @@ */ | ||
module.exports.splitLongWords = splitLongWords | ||
module.exports.truncateString = truncateString | ||
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
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
20063
5
320
0
329
1
+ Addedwcwidth.js@~0.0.4
+ Addedunderscore@1.13.7(transitive)
+ Addedwcwidth.js@0.0.4(transitive)