Comparing version 2.8.12 to 3.0.0
{ | ||
"name": "tty-table", | ||
"version": "2.8.12", | ||
"version": "3.0.0", | ||
"description": "Node cli table", | ||
"main": "src/main.js", | ||
"engines": { | ||
"node": ">=8.16.0" | ||
"node": ">=12.0.0" | ||
}, | ||
@@ -22,3 +22,5 @@ "bin": { | ||
"test": "npx mocha", | ||
"save-tests": "npx grunt st", | ||
"report-to-coveralls": "npx nyc report --reporter=text-lcov | npx coveralls", | ||
"report-to-cover-io": "npx nyc report --reporter=text-lcov > coverage.lcov && ./node_modules/.bin/codecov -t ffe0f46d-c939-4302-b199-0f2de3e8c18a", | ||
"save-tests": "node npm_scripts/save-tests.js", | ||
"lint": "npx eslint adapters/* src/*", | ||
@@ -55,3 +57,3 @@ "lint-fix": "npx eslint adapters/* src/* --fix", | ||
"kleur": "^3.0.3", | ||
"smartwrap": "^1.2.3", | ||
"smartwrap": "^1.2.5", | ||
"strip-ansi": "^6.0.0", | ||
@@ -70,6 +72,6 @@ "wcwidth": "^1.0.1", | ||
"browserify": "^16.5.0", | ||
"browserify-banner": "^1.0.14", | ||
"chai": "^4.2.0", | ||
"codecov": "^3.6.5", | ||
"commander": "^4.1.1", | ||
"doctoc": "^1.4.0", | ||
"coveralls": "^3.0.9", | ||
"eslint": "^6.8.0", | ||
@@ -81,11 +83,18 @@ "glob": "^7.1.4", | ||
"grunt-contrib-watch": "^1.1.0", | ||
"grunt-jsdoc": "^2.4.0", | ||
"grunt-shell": "^3.0.1", | ||
"husky": "^4.2.1", | ||
"jsdoc-to-markdown": "^5.0.0", | ||
"mocha": "^6.1.4", | ||
"mocha-lcov-reporter": "^1.3.0", | ||
"nyc": "^15.0.0", | ||
"orgy": "^2.2.1", | ||
"rollup": "^1.31.1" | ||
} | ||
}, | ||
"nyc": { | ||
"all": false, | ||
"include": [ | ||
"src/*.js", | ||
"adapters/*.js" | ||
] | ||
}, | ||
"defaultTestColumns": 90 | ||
} |
@@ -1,5 +0,4 @@ | ||
# tty-table 电传打字台 | ||
# tty-table 端子台 | ||
[![Build Status](https://travis-ci.org/tecfu/tty-table.svg?branch=master)](https://travis-ci.org/tecfu/tty-table) [![NPM version](https://badge.fury.io/js/tty-table.svg)](http://badge.fury.io/js/tty-table) | ||
[![Build Status](https://travis-ci.org/tecfu/tty-table.svg?branch=master)](https://travis-ci.org/tecfu/tty-table) [![NPM version](https://badge.fury.io/js/tty-table.svg)](http://badge.fury.io/js/tty-table) [![Coverage Status](https://coveralls.io/repos/github/tecfu/tty-table/badge.svg?branch=master)](https://coveralls.io/github/tecfu/tty-table?branch=master) | ||
--- | ||
@@ -37,8 +36,8 @@ | ||
- [examples/browser-example.html](examples/browser-example.html) | ||
- [demo: jsfiddle](https://jsfiddle.net/6hz1a9cs/3/) | ||
- [demo: plinkr](https://plnkr.co/edit/iQn9xn5yCY4NUkXRF87o?p=preview) | ||
- [source: examples/browser-example.html](examples/browser-example.html) | ||
![Browser Console Example](https://user-images.githubusercontent.com/7478359/74614563-cbcaff00-50e6-11ea-9101-5457497696b8.jpg "tty-table in the browser console") | ||
[Working Example in Browser](https://cdn.rawgit.com/tecfu/tty-table/master/examples/browser-example.html) | ||
<br/> | ||
@@ -216,8 +215,6 @@ <br/> | ||
```html | ||
import Table from './dist/tty-table.esm.js' | ||
// other options: | ||
// let Table = require('tty-table') // dist/tty-table.cjs.js | ||
// let Table = TTY_Table; // dist/tty-table.umd.js | ||
```js | ||
import Table from 'https://cdn.jsdelivr.net/gh/tecfu/tty-table/dist/tty-table.esm.js' | ||
let Table = require('tty-table') // https://cdn.jsdelivr.net/gh/tecfu/tty-table/dist/tty-table.cjs.js | ||
let Table = TTY_Table; // https://cdn.jsdelivr.net/gh/tecfu/tty-table/dist/tty-table.umd.js | ||
``` | ||
@@ -232,2 +229,6 @@ | ||
```sh | ||
$ npm run coverage | ||
``` | ||
## Saving the output of new unit tests | ||
@@ -234,0 +235,0 @@ |
@@ -0,1 +1,2 @@ | ||
// @TODO split defaults into table and cell settings | ||
const defaults = { | ||
@@ -38,2 +39,3 @@ borderCharacters: { | ||
headerColor: "yellow", | ||
isNull: false, // undocumented cell setting | ||
marginLeft: 2, | ||
@@ -40,0 +42,0 @@ marginTop: 1, |
@@ -6,54 +6,2 @@ const defaults = require("./defaults.js") | ||
/** | ||
* @class Table | ||
* @param {array} header - [See example](#example-usage) | ||
* @param {object} header.column - Column options | ||
* @param {string} header.column.alias - Alternate header column name | ||
* @param {string} header.column.align - default: "center" | ||
* @param {string} header.column.color - default: terminal default color | ||
* @param {string} header.column.footerAlign - default: "center" | ||
* @param {string} header.column.footerColor - default: terminal default color | ||
* @param {function(cellValue, columnIndex, rowIndex, rowData, inputData)</code} header.column.formatter - Runs a callback on each cell value in the parent column | ||
* @param {string} header.column.headerAlign - default: "center" | ||
* @param {string} header.column.headerColor - default: terminal's default color | ||
* @param {number} header.column.marginLeft - default: 0 | ||
* @param {number} header.column.marginTop - default: 0 | ||
* @param {string|number} header.column.width - default: "auto" | ||
* @param {number} header.column.paddingBottom - default: 0 | ||
* @param {number} header.column.paddingLeft - default: 1 | ||
* @param {number} header.column.paddingRight - default: 1 | ||
* @param {number} header.column.paddingTop - default: 0 | ||
* | ||
* @param {array} rows - [See example](#example-usage) | ||
* | ||
* @param {object} options - Table options | ||
* @param {string} options.borderStyle - default: "solid". options: "solid", "dashed", "none" | ||
* @param {object} options.borderCharacters - [See @note](#note) | ||
* @param {string} options.borderColor - default: terminal's default color | ||
* @param {boolean} options.compact - default: false | ||
* Removes horizontal lines when true. | ||
* @param {mixed} options.defaultErrorValue - default: '�' | ||
* @param {mixed} options.defaultValue - default: '?' | ||
* @param {boolean} options.errorOnNull - default: false | ||
* @param {mixed} options.truncate - default: false | ||
* <br/> | ||
* When this property is set to a string, cell contents will be truncated by that string instead of wrapped when they extend beyond of the width of the cell. | ||
* <br/> | ||
* For example if: | ||
* <br/> | ||
* <code>"truncate":"..."</code> | ||
* <br/> | ||
* the cell will be truncated with "..." | ||
* @returns {Table} | ||
* @example | ||
* ```js | ||
* let Table = require('tty-table'); | ||
* let t1 = Table(header,rows,options); | ||
* console.log(t1.render()); | ||
* ``` | ||
* | ||
*/ | ||
const Factory = function(paramsArr) { | ||
@@ -118,3 +66,3 @@ | ||
// For "deep" copy, use JSON.parse | ||
// for "deep" copy, use JSON.parse | ||
const cloneddefaults = JSON.parse(JSON.stringify(defaults)) | ||
@@ -135,3 +83,3 @@ let config = Object.assign({}, cloneddefaults, options) | ||
Object.keys(obj).forEach(function(key) { | ||
obj[key] = Style.color(obj[key], config.borderColor) | ||
obj[key] = Style.style(obj[key], config.borderColor) | ||
}) | ||
@@ -187,4 +135,9 @@ return obj | ||
module.exports = function() { | ||
let Table = function() { | ||
return new Factory(arguments) | ||
} | ||
Table.resetStyle = Style.resetStyle | ||
Table.style = Style.styleEachChar | ||
module.exports = Table |
@@ -6,9 +6,85 @@ const stripAnsi = require("strip-ansi") | ||
module.exports.calculateLength = line => { | ||
// return stripAnsi(line.replace(/[^\x00-\xff]/g,'XX')).length | ||
return wcwidth(stripAnsi(line)) | ||
const addPadding = (config, width) => { | ||
return width + config.paddingLeft + config.paddingRight | ||
} | ||
module.exports.wrapCellContent = ( | ||
/** | ||
* Returns the widest cell give a collection of rows | ||
* | ||
* @param object columnOptions | ||
* @param array rows | ||
* @param integer columnIndex | ||
* @returns string | ||
*/ | ||
const getMaxLength = (columnOptions, rows, columnIndex) => { | ||
let iterable | ||
// add a row that contains the header value, so we use that width too | ||
if(typeof columnOptions === "object" && columnOptions.value) { | ||
iterable = rows.slice() | ||
let z = new Array(iterable[0].length) // create a new empty row | ||
z[columnIndex] = columnOptions.value.toString() | ||
iterable.push(z) | ||
} else { | ||
// no header value, just use rows to derive max width | ||
iterable = rows | ||
} | ||
// iterable.forEach( row => { | ||
// if(row[columnIndex] && stripAnsi(row[columnIndex].toString()).length > widest) { | ||
// widest = wcwidth(row[columnIndex].toString()) | ||
// } | ||
// }) | ||
let widest = iterable.reduce((prev, row) => { | ||
if(row[columnIndex]) { | ||
let width = wcwidth(stripAnsi(row[columnIndex].toString())) | ||
return ( width > prev) ? width : prev | ||
} | ||
return prev | ||
}, 0) | ||
return widest | ||
} | ||
/** | ||
* Get total width available to this table instance | ||
* | ||
* | ||
*/ | ||
const getAvailableWidth = config => { | ||
if (process && ((process.stdout && process.stdout.columns) || (process.env && process.env.COLUMNS))) { | ||
// forked calls that do not inherit process.stdout must use process.env | ||
let viewport = (process.stdout && process.stdout.columns) ? process.stdout.columns : process.env.COLUMNS | ||
viewport = viewport - config.marginLeft | ||
// table width fixed | ||
if (config.width !== "auto" && /^\d{1,2}$/.test(config.width)) return config.width | ||
// table width percentage of (viewport less margin) | ||
if (config.width !== "auto" && /^\d{1,2}%$/.test(config.width)) { | ||
return (config.width.slice(0, -1) * .01) * viewport | ||
} | ||
// table width equals viewport less margin | ||
return viewport | ||
} | ||
// browser | ||
if (typeof window !== "undefined") return window.innerWidth // eslint-disable-line | ||
throw new Error ("Cannot auto discover available table width. Set table `width` option or export the environment variable COLUMNS") | ||
} | ||
module.exports.getStringLength = string => { | ||
// stripAnsi(string.replace(/[^\x00-\xff]/g,'XX')).length | ||
return wcwidth(stripAnsi(string)) | ||
} | ||
module.exports.wrapCellText = ( | ||
config, | ||
@@ -80,3 +156,3 @@ cellValue, | ||
const lineLength = exports.calculateLength(line) | ||
const lineLength = exports.getStringLength(line) | ||
@@ -125,3 +201,3 @@ // alignment | ||
if(maxWidth < stringWidth) { | ||
// @todo give use option to decide if they want to break words on wrapping | ||
// @TODO give user option to decide if they want to break words on wrapping | ||
string = smartwrap(string, { | ||
@@ -150,37 +226,9 @@ width: maxWidth - cellOptions.truncate.length, | ||
/** | ||
* Returns the widest cell give a collection of rows | ||
* | ||
* @param array rows | ||
* @param integer columnIndex | ||
* @returns integer | ||
*/ | ||
module.exports.inferColumnWidth = (columnOptions, rows, columnIndex) => { | ||
module.exports.getColumnWidths = (config, rows) => { | ||
let iterable | ||
let widest = 0 | ||
const availableWidth = getAvailableWidth(config) | ||
// add a row that contains the header value, so we use that width too | ||
if(typeof columnOptions === "object" && columnOptions.value) { | ||
iterable = rows.slice() | ||
let z = new Array(iterable[0].length) // create a new empty row | ||
z[columnIndex] = columnOptions.value.toString() | ||
iterable.push(z) | ||
} else { | ||
// no header value, just use rows to derive max width | ||
iterable = rows | ||
} | ||
// array of spaces that cant be proportionately reduced for each row | ||
const nonResizableRowSpaces = [] | ||
iterable.forEach( row => { | ||
if(row[columnIndex] && row[columnIndex].toString().length > widest) { | ||
widest = wcwidth(row[columnIndex].toString()) | ||
} | ||
}) | ||
return widest | ||
} | ||
module.exports.getColumnWidths = (config, rows) => { | ||
// iterate over the header if we have it, iterate over the first row | ||
@@ -193,23 +241,27 @@ // if we do not (to step through the correct number of columns) | ||
let result | ||
nonResizableRowSpaces[columnIndex] = 0 // track non resizable space for row | ||
switch(true) { | ||
// column width specified in header | ||
case(typeof column === "object" && typeof column.width === "number"): | ||
// column width is specified in column header | ||
case(typeof column === "object" && (/^\d{1,2}$/.test(column.width))): | ||
result = column.width | ||
break | ||
// global column width set in config | ||
case(config.width && config.width !== "auto"): | ||
result = config.width | ||
// column width is a percentage of table width specified in column header | ||
case(typeof column === "object" && (/^\d{1,2}%$/.test(column.width))): | ||
result = (column.width.slice(0, -1) * .01) * availableWidth | ||
result = addPadding(config, result) | ||
break | ||
// 'auto' sets column width to its longest value in the initial data set | ||
default: | ||
// 'auto' sets column width to longest value in initial data set | ||
let columnOptions = (config.table.header[0][columnIndex]) | ||
? config.table.header[0][columnIndex] : {} | ||
let measurableRows = (rows.length) ? rows : config.table.header[0] | ||
result = exports.inferColumnWidth(columnOptions, measurableRows, columnIndex) | ||
result = getMaxLength(columnOptions, measurableRows, columnIndex) | ||
// add spaces for padding if not centered | ||
result = result + config.paddingLeft + config.paddingRight | ||
// @TODO test with if not centered conditional | ||
result = addPadding(config, result) | ||
} | ||
@@ -224,14 +276,17 @@ | ||
// calculate sum of all column widths (including marginLeft) | ||
let totalWidth = widths.reduce((prev, curr) => prev + curr) | ||
let totalWidth = widths.reduce((prev, current) => prev + current) | ||
// if sum of all widths exceeds viewport, resize proportionately to fit | ||
if(process && process.stdout && totalWidth > process.stdout.columns) { | ||
// recalculate proportionately to fit | ||
let prop = (process.stdout.columns - config.marginLeft) / totalWidth | ||
if(totalWidth > availableWidth) { | ||
let prop = availableWidth / totalWidth | ||
let relativeWidths | ||
let totalRelativeWidths | ||
prop = prop.toFixed(2) - 0.01 | ||
prop = prop.toFixed(2) - 0.01 // this wont be exact fit, but keeps us safe | ||
// when process.stdout.columns is 0, width will be negative | ||
// when prop < 0 column cant be resized and totalWidth must overflow viewport | ||
if (prop > 0) { | ||
widths = widths.map(value => Math.floor(prop * value)) | ||
relativeWidths = widths.map(value => Math.floor(prop * value)) | ||
totalRelativeWidths = relativeWidths.reduce((prev, current) => prev + current) | ||
widths = (totalRelativeWidths < totalWidth) ? relativeWidths : widths | ||
} | ||
@@ -238,0 +293,0 @@ } |
@@ -21,3 +21,3 @@ const Style = require("./style.js") | ||
const constructorType = exports.getConstructorGeometry(inputData[0] || [], config) | ||
const rows = exports.coerceConstructor(config, inputData, constructorType) | ||
const rows = exports.coerceConstructorGeometry(config, inputData, constructorType) | ||
@@ -44,3 +44,3 @@ // when streaming values to tty-table, we don't want column widths to change | ||
case (config.showHeader === true): // explicitly true, show | ||
case (!!config.table.header[0].find(obj => obj.value)): // atleast one named column, show | ||
case (!!config.table.header[0].find(obj => obj.value || obj.alias)): // atleast one named column, show | ||
sections.header = config.table.header.map(row => { | ||
@@ -153,4 +153,3 @@ return exports.buildRow(config, row, "header", null, rows, inputData) | ||
// tag row as empty if empty | ||
// (used) for compact tables | ||
// tag row as empty if empty, used for `compact` option | ||
if(row.length === 0 && config.compact) { | ||
@@ -243,2 +242,4 @@ row.empty = true | ||
cellValue = (config.errorOnNull) ? config.defaultErrorValue : config.defaultValue | ||
// @TODO add to cell defaults | ||
cellOptions.isNull = true | ||
break | ||
@@ -251,4 +252,4 @@ | ||
case(typeof cell === "function"): | ||
cellValue = cell.bind({ style: Style.color })( | ||
cellValue, | ||
cellValue = cell.bind({ style: Style.style })( | ||
(!cellOptions.isNull) ? cellValue : "", | ||
columnIndex, | ||
@@ -269,4 +270,4 @@ rowIndex, | ||
cellValue = cellOptions.formatter | ||
.bind({ style: Style.color })( | ||
cellValue, | ||
.bind({ style: Style.style })( | ||
(!cellOptions.isNull) ? cellValue : "", | ||
columnIndex, | ||
@@ -284,3 +285,3 @@ rowIndex, | ||
// textwrap cellValue | ||
let wrapObj = Format.wrapCellContent(config, cellValue, columnIndex, cellOptions, rowType) | ||
let wrapObj = Format.wrapCellText(config, cellValue, columnIndex, cellOptions, rowType) | ||
@@ -331,3 +332,3 @@ // return as array of lines | ||
*/ | ||
module.exports.coerceConstructor = (config, rows, constructorType) => { | ||
module.exports.coerceConstructorGeometry = (config, rows, constructorType) => { | ||
@@ -386,17 +387,17 @@ let output = [] | ||
// assumes all rows are same length | ||
module.exports.verticalizeMatrix = (config, inputArray) => { | ||
// grow to # arrays equal to number of columns in input array | ||
let outputArray = [] | ||
let headers = config.table.columns | ||
// create a row for each heading, and prepend the row | ||
// with the heading name | ||
headers.forEach(name => outputArray.push([name])) | ||
inputArray.forEach(row => { | ||
row.forEach((element, index) => outputArray[index].push(element)) | ||
}) | ||
return outputArray | ||
} | ||
// module.exports.verticalizeMatrix = (config, inputArray) => { | ||
// | ||
// // grow to # arrays equal to number of columns in input array | ||
// let outputArray = [] | ||
// let headers = config.table.columns | ||
// | ||
// // create a row for each heading, and prepend the row | ||
// // with the heading name | ||
// headers.forEach(name => outputArray.push([name])) | ||
// | ||
// inputArray.forEach(row => { | ||
// row.forEach((element, index) => outputArray[index].push(element)) | ||
// }) | ||
// | ||
// return outputArray | ||
// } |
const kleur = require("kleur") | ||
const stripAnsi = require("strip-ansi") | ||
module.exports.color = (str, ...colors) => { | ||
return colors.reduce(function(input, color) { | ||
module.exports.style = (str, ...colors) => { | ||
let out = colors.reduce(function(input, color) { | ||
return kleur[color](input) | ||
}, str) | ||
return out | ||
} | ||
module.exports.styleEachChar = (str, ...colors) => { | ||
// strip existing ansi chars so we dont loop them | ||
// @ TODO create a really clever workaround so that you can accrete styles | ||
let chars = [...stripAnsi(str)] | ||
// style each character | ||
let out = chars.reduce((prev, current) => { | ||
let coded = colors.reduce((input, color) => { | ||
return kleur[color](input) | ||
}, current) | ||
return prev + coded | ||
}, "") | ||
return out | ||
} | ||
module.exports.resetStyle = (str) => { | ||
return stripAnsi(str) | ||
} | ||
module.exports.colorizeCell = (str, cellOptions, rowType) => { | ||
@@ -28,3 +53,3 @@ | ||
if (color) { | ||
str = exports.color(str, color) | ||
str = exports.style(str, color) | ||
} | ||
@@ -31,0 +56,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
Deprecated
MaintenanceThe maintainer of the package marked it as deprecated. This could indicate that a single version should not be used, or that the package is no longer maintained and any new vulnerabilities will not be fixed.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 2 instances 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
Deprecated
MaintenanceThe maintainer of the package marked it as deprecated. This could indicate that a single version should not be used, or that the package is no longer maintained and any new vulnerabilities will not be fixed.
Found 1 instance in 1 package
44011
25
975
280
6
Updatedsmartwrap@^1.2.5