symfony-style-console
Version:
Use the style and utilities of the Symfony Console in Node.js
653 lines (652 loc) • 23.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var Helper_1 = require("./Helper");
var TableCell_1 = require("./TableCell");
var TableSeparator_1 = require("./TableSeparator");
var TableStyle_1 = require("./TableStyle");
/**
* Provides helpers to display a table.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* Original PHP Class
*
* @author Саша Стаменковић <umpirsky@gmail.com>
* @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
* @author Max Grigorian <maxakawizard@gmail.com>
*
* @author Florian Reuschel <florian@loilo.de>
*
* Port to TypeScript
*/
var Table = /** @class */ (function () {
function Table(output) {
/**
* Table headers.
*/
this.headers = [];
/**
* Table rows.
*/
this.rows = [];
/**
* Column widths cache.
*/
this.effectiveColumnWidths = [];
/**
* Column Styles
*/
this.columnStyles = [];
/**
* User set column widths
*/
this.columnWidths = [];
this.output = output;
if (!Table.styles) {
Table.styles = Table.initStyles();
}
}
/**
* Sets a style definition.
*
* @param name The style name
* @param style A TableStyle instance
*/
Table.setStyleDefinition = function (name, style) {
if (!Table.styles) {
Table.styles = Table.initStyles();
}
Table.styles[name] = style;
};
/**
* Gets a style definition by name.
*
* @param name The style name
* @return TableStyle
*/
Table.getStyleDefinition = function (name) {
if (!Table.styles) {
Table.styles = Table.initStyles();
}
if (Table.styles[name]) {
return Table.styles[name];
}
throw new Error("Style \"" + name + "\" is not defined.");
};
Table.initStyles = function () {
var borderless = new TableStyle_1.default();
borderless
.setHorizontalBorderChar('=')
.setVerticalBorderChar(' ')
.setCrossingChar(' ');
var compact = new TableStyle_1.default();
compact
.setHorizontalBorderChar('')
.setVerticalBorderChar(' ')
.setCrossingChar('')
.setCellRowContentFormat('%s');
var styleGuide = new TableStyle_1.default();
styleGuide
.setHorizontalBorderChar('-')
.setVerticalBorderChar(' ')
.setCrossingChar(' ')
.setCellHeaderFormat('%s');
return {
default: new TableStyle_1.default(),
borderless: borderless,
compact: compact,
'symfony-style-guide': styleGuide
};
};
/**
* Sets table style.
*
* @param name The style name or a TableStyle instance
* @return this
*/
Table.prototype.setStyle = function (name) {
this.style = this.resolveStyle(name);
return this;
};
/**
* Gets the current table style.
*
* @return TableStyle
*/
Table.prototype.getStyle = function () {
return this.style;
};
/**
* Sets table column style.
*
* @param columnIndex Column index
* @param name The style name or a TableStyle instance
*
* @return this
*/
Table.prototype.setColumnStyle = function (columnIndex, name) {
columnIndex = Math.round(columnIndex);
this.columnStyles[columnIndex] = this.resolveStyle(name);
return this;
};
/**
* Gets the current style for a column.
*
* If style was not set, it returns the global table style.
*
* @param columnIndex Column index
*
* @return TableStyle
*/
Table.prototype.getColumnStyle = function (columnIndex) {
if (this.columnStyles[columnIndex]) {
return this.columnStyles[columnIndex];
}
return this.getStyle();
};
/**
* Sets the minimum width of a column.
*
* @param columnIndex Column index
* @param width Minimum column width in characters
*
* @return this
*/
Table.prototype.setColumnWidth = function (columnIndex, width) {
this.columnWidths[Math.round(columnIndex)] = Math.round(width);
return this;
};
/**
* Sets the minimum width of all columns.
*
* @param widths
*
* @return this
*/
Table.prototype.setColumnWidths = function (widths) {
this.columnWidths = [];
for (var index = 0; index < widths.length; index++) {
if (typeof widths[index] === 'undefined')
continue;
var width = widths[index];
this.setColumnWidth(index, width);
}
return this;
};
Table.prototype.setHeaders = function (headers) {
var isNestedRows = function (headers) {
return !(headers.length && !Array.isArray(headers[0]));
};
if (!isNestedRows(headers)) {
headers = [headers];
}
this.headers = headers;
return this;
};
Table.prototype.setRows = function (rows) {
this.rows = [];
return this.addRows(rows);
};
Table.prototype.addRows = function (rows) {
for (var _i = 0, rows_1 = rows; _i < rows_1.length; _i++) {
var row = rows_1[_i];
this.addRow(row);
}
return this;
};
Table.prototype.addRow = function (row) {
if (row instanceof TableSeparator_1.default) {
this.rows.push(row);
return this;
}
if (!Array.isArray(row)) {
throw new Error('A row must be an array or a TableSeparator instance.');
}
this.rows.push(row);
return this;
};
Table.prototype.setRow = function (column, row) {
this.rows[column] = row;
return this;
};
/**
* Renders table to output.
*
* Example:
* +---------------+-----------------------+------------------+
* | ISBN | Title | Author |
* +---------------+-----------------------+------------------+
* | 99921-58-10-7 | Divine Comedy | Dante Alighieri |
* | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |
* | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien |
* +---------------+-----------------------+------------------+
*/
Table.prototype.render = function () {
this.calculateNumberOfColumns();
var rows = this.buildTableRows(this.rows);
var headers = this.buildTableRows(this.headers);
this.calculateColumnsWidth(headers.concat(rows));
this.renderRowSeparator();
if (headers.length) {
for (var _i = 0, headers_1 = headers; _i < headers_1.length; _i++) {
var header = headers_1[_i];
this.renderRow(header, this.style.getCellHeaderFormat());
this.renderRowSeparator();
}
}
for (var _a = 0, rows_2 = rows; _a < rows_2.length; _a++) {
var row = rows_2[_a];
if (row instanceof TableSeparator_1.default) {
this.renderRowSeparator();
}
else {
this.renderRow(row, this.style.getCellRowFormat());
}
}
if (rows.length) {
this.renderRowSeparator();
}
this.cleanup();
};
/**
* Gets number of columns by row.
*
* @param row
*
* @return int
*/
Table.prototype.getNumberOfColumns = function (row) {
var columns = row.filter(function (cell) { return typeof cell !== 'undefined'; }).length;
for (var _i = 0, row_1 = row; _i < row_1.length; _i++) {
var column = row_1[_i];
columns += column instanceof TableCell_1.default ? column.getColspan() - 1 : 0;
}
return columns;
};
/**
* Gets list of columns for the given row.
*
* @param array row
*
* @return array
*/
Table.prototype.getRowColumns = function (row) {
var columns = Helper_1.range(0, this.numberOfColumns - 1);
var _loop_1 = function (cellKey) {
if (typeof row[cellKey] === 'undefined')
return "continue";
var cell = row[cellKey];
if (cell instanceof TableCell_1.default && cell.getColspan() > 1) {
// exclude grouped columns.
var diffRange_1 = Helper_1.range(cellKey + 1, cellKey + cell.getColspan() - 1);
columns = columns.filter(function (column) { return !Helper_1.arrContains(diffRange_1, column); });
}
};
for (var cellKey = 0; cellKey < row.length; cellKey++) {
_loop_1(cellKey);
}
return columns;
};
/**
* Gets column width.
*
* @return int
*/
Table.prototype.getColumnSeparatorWidth = function () {
return Helper_1.sprintf(this.style.getBorderFormat(), this.style.getVerticalBorderChar()).length;
};
/**
* Gets cell width.
*
* @param row
* @param column
*
* @return int
*/
Table.prototype.getCellWidth = function (row, column) {
var cellWidth = 0;
if (row[column]) {
var cell = row[column];
var cellStr = String(cell);
cellWidth = Helper_1.lengthWithoutDecoration(this.output.getFormatter(), cellStr);
}
var columnWidth = this.columnWidths[column] || 0;
return Math.max(cellWidth, columnWidth);
};
/**
* Renders horizontal header separator.
*
* Example: +-----+-----------+-------+
*/
Table.prototype.renderRowSeparator = function () {
var count = this.numberOfColumns;
if (!count) {
return;
}
if (!this.style.getHorizontalBorderChar() &&
!this.style.getCrossingChar()) {
return;
}
var markup = this.style.getCrossingChar();
for (var column = 0; column < count; ++column) {
markup +=
this.style
.getHorizontalBorderChar()
.repeat(this.effectiveColumnWidths[column]) +
this.style.getCrossingChar();
}
this.output.writeln(Helper_1.sprintf(this.style.getBorderFormat(), markup));
};
/**
* Renders vertical column separator.
*/
Table.prototype.renderColumnSeparator = function () {
return Helper_1.sprintf(this.style.getBorderFormat(), this.style.getVerticalBorderChar());
};
/**
* Renders table row.
*
* Example: | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |
*
* @param row
* @param cellFormat
*/
Table.prototype.renderRow = function (row, cellFormat) {
if (!row.length) {
return;
}
var rowContent = this.renderColumnSeparator();
for (var _i = 0, _a = this.getRowColumns(row); _i < _a.length; _i++) {
var column = _a[_i];
rowContent += this.renderCell(row, column, cellFormat);
rowContent += this.renderColumnSeparator();
}
this.output.writeln(rowContent);
};
/**
* Renders table cell with padding.
*
* @param row
* @param column
* @param cellFormat
*/
Table.prototype.renderCell = function (row, column, cellFormat) {
var cell = row[column] || '';
var cellStr = String(cell);
var width = this.effectiveColumnWidths[column];
if (cell instanceof TableCell_1.default && cell.getColspan() > 1) {
// add the width of the following columns(numbers of colspan).
for (var _i = 0, _a = Helper_1.range(column + 1, column + cell.getColspan() - 1); _i < _a.length; _i++) {
var nextColumn = _a[_i];
width +=
this.getColumnSeparatorWidth() +
this.effectiveColumnWidths[nextColumn];
}
}
var style = this.getColumnStyle(column);
if (cell instanceof TableSeparator_1.default) {
return Helper_1.sprintf(style.getBorderFormat(), style.getHorizontalBorderChar().repeat(width));
}
width +=
cellStr.length -
Helper_1.lengthWithoutDecoration(this.output.getFormatter(), cellStr);
var content = Helper_1.sprintf(style.getCellRowContentFormat(), cell);
return Helper_1.sprintf(cellFormat, Helper_1.strPad(content, width, style.getPaddingChar(), style.getPadType()));
};
/**
* Calculate number of columns for this table.
*/
Table.prototype.calculateNumberOfColumns = function () {
if (null != this.numberOfColumns) {
return;
}
var columns = [0];
for (var _i = 0, _a = this.headers.concat(this.rows); _i < _a.length; _i++) {
var row = _a[_i];
if (row instanceof TableSeparator_1.default) {
continue;
}
columns.push(this.getNumberOfColumns(row));
}
this.numberOfColumns = Math.max.apply(Math, columns);
};
Table.prototype.buildTableRows = function (rows) {
var unmergedRows = [];
rows = rows.slice(0);
for (var rowKey = 0; rowKey < rows.length; ++rowKey) {
if (typeof rows[rowKey] === 'undefined')
continue;
if (rows[rowKey] instanceof TableSeparator_1.default)
continue;
rows = this.fillNextRows(rows, rowKey);
var row = rows[rowKey];
// Remove any new line breaks and replace it with a new line
for (var column = 0; column < row.length; column++) {
if (typeof row[column] === 'undefined')
continue;
var cell = row[column];
var cellStr = String(cell);
if (cellStr.includes('\n')) {
continue;
}
var lines = cellStr.split('\n');
for (var lineKey = 0; lineKey < lines.length; lineKey++) {
var line = lines[lineKey];
if (cell instanceof TableCell_1.default) {
line = new TableCell_1.default(line, {
colspan: cell.getColspan()
});
}
if (0 === lineKey) {
row[column] = line;
}
else {
if (!Array.isArray(unmergedRows[rowKey]))
unmergedRows[rowKey] = [];
if (!Array.isArray(unmergedRows[rowKey][lineKey]))
unmergedRows[rowKey][lineKey] = [];
unmergedRows[rowKey][lineKey][column] = line;
}
}
}
}
var tableRows = [];
for (var rowKey = 0; rowKey < rows.length; rowKey++) {
if (typeof rows[rowKey] === 'undefined')
continue;
if (rows[rowKey] instanceof TableSeparator_1.default)
continue;
var row = rows[rowKey];
tableRows.push(this.fillCells(row));
if (unmergedRows[rowKey]) {
tableRows = tableRows.concat(unmergedRows[rowKey]);
}
}
return tableRows;
};
/**
* fill rows that contains rowspan > 1.
*
* @param inputRows
* @param line
*
* @return array
*/
Table.prototype.fillNextRows = function (inputRows, line) {
var unmergedRows = ([]);
if (inputRows[line] instanceof TableSeparator_1.default)
return inputRows.slice(0);
var rows = inputRows.slice(0);
for (var column = 0; column < rows[line].length; column++) {
var cell = rows[line][column];
if (cell instanceof TableCell_1.default && cell.getRowspan() > 1) {
var cellStr = String(cell);
var nbLines = cell.getRowspan() - 1;
var lines = [cellStr];
if (cellStr.includes('\n')) {
lines = cellStr.split('\n');
nbLines =
lines.length > nbLines
? Helper_1.countOccurences(cellStr, '\n')
: nbLines;
rows[line][column] = new TableCell_1.default(lines[0], {
colspan: cell.getColspan()
});
delete lines[0];
}
// create a two dimensional array (rowspan x colspan)
unmergedRows = Helper_1.arrayReplaceRecursive(Helper_1.arrayFill(line + 1, nbLines, []), unmergedRows);
for (var unmergedRowKey = 0; unmergedRowKey < unmergedRows.length; unmergedRowKey++) {
if (typeof unmergedRows[unmergedRowKey] === 'undefined')
continue;
var unmergedRow = unmergedRows[unmergedRowKey];
var value = lines[unmergedRowKey - line] || '';
unmergedRows[unmergedRowKey][column] = new TableCell_1.default(value, {
colspan: cell.getColspan()
});
if (nbLines === unmergedRowKey - line) {
break;
}
}
}
}
for (var unmergedRowKey = 0; unmergedRowKey < unmergedRows.length; unmergedRowKey++) {
if (typeof unmergedRows[unmergedRowKey] === 'undefined')
continue;
var unmergedRow = unmergedRows[unmergedRowKey];
// we need to know if unmergedRow will be merged or inserted into rows
if (typeof rows[unmergedRowKey] !== 'undefined' &&
Array.isArray(rows[unmergedRowKey]) &&
this.getNumberOfColumns(rows[unmergedRowKey]) +
this.getNumberOfColumns(unmergedRows[unmergedRowKey]) <=
this.numberOfColumns) {
for (var cellKey = 0; cellKey < unmergedRow.length; cellKey++) {
if (typeof unmergedRow[cellKey] === 'undefined')
continue;
var cell = unmergedRow[cellKey];
// insert cell into row at cellKey position
rows[unmergedRowKey].splice(cellKey, 0, cell);
}
}
else {
var row = this.copyRow(rows, unmergedRowKey - 1);
for (var column = 0; column < unmergedRow.length; column++) {
if (typeof unmergedRow[column] === 'undefined')
continue;
var cell = unmergedRow[column];
var cellStr = String(cell);
if (cellStr.length) {
row[column] = unmergedRow[column];
}
}
rows.splice(unmergedRowKey, 0, row);
}
}
return rows;
};
/**
* fill cells for a row that contains colspan > 1.
*
* @param row
*
* @return array
*/
Table.prototype.fillCells = function (row) {
var newRow = [];
for (var column = 0; column < row.length; column++) {
if (typeof row[column] === 'undefined')
continue;
var cell = row[column];
newRow.push(cell);
if (cell instanceof TableCell_1.default && cell.getColspan() > 1) {
for (var _i = 0, _a = Helper_1.range(column + 1, column + cell.getColspan() - 1); _i < _a.length; _i++) {
var position = _a[_i];
// insert empty value at column position
newRow.push('');
}
}
}
return newRow || row;
};
/**
* @param rows
* @param line
*
* @return array
*/
Table.prototype.copyRow = function (rows, line) {
var row = rows[line].slice(0);
for (var cellKey = 0; cellKey < row.length; cellKey++) {
if (typeof row[cellKey] === 'undefined')
continue;
var cellValue = row[cellKey];
row[cellKey] = '';
if (cellValue instanceof TableCell_1.default) {
row[cellKey] = new TableCell_1.default('', {
colspan: cellValue.getColspan()
});
}
}
return row;
};
/**
* Calculates columns widths.
*
* @param array rows
*/
Table.prototype.calculateColumnsWidth = function (rows) {
rows = rows.slice(0);
for (var column = 0; column < this.numberOfColumns; ++column) {
var lengths = [];
for (var _i = 0, rows_3 = rows; _i < rows_3.length; _i++) {
var row = rows_3[_i];
if (row instanceof TableSeparator_1.default) {
continue;
}
row = row.slice(0);
for (var i = 0; i < row.length; i++) {
if (typeof row[i] === 'undefined')
continue;
var cell = row[i];
if (cell instanceof TableCell_1.default) {
var textContent = Helper_1.removeDecoration(this.output.getFormatter(), String(cell));
var textLength = textContent.length;
if (textLength > 0) {
var contentColumns = Helper_1.chunkString(textContent, Math.ceil(textLength / cell.getColspan()));
if (contentColumns === false) {
throw new Error("Could not chunk string: " + textContent);
}
for (var position = 0; position < contentColumns.length; position++) {
if (typeof contentColumns[position] === 'undefined')
continue;
var content = contentColumns[position];
row[i + position] = content;
}
}
}
}
lengths.push(this.getCellWidth(row, column));
}
this.effectiveColumnWidths[column] =
Math.max.apply(Math, lengths) + this.style.getCellRowContentFormat().length - 2;
}
};
/**
* Called after rendering to cleanup cache data.
*/
Table.prototype.cleanup = function () {
this.effectiveColumnWidths = [];
this.numberOfColumns = null;
};
Table.prototype.resolveStyle = function (name) {
if (name instanceof TableStyle_1.default) {
return name;
}
if (Table.styles[name]) {
return Table.styles[name];
}
throw new Error("Style \"" + name + "\" is not defined.");
};
return Table;
}());
exports.default = Table;