@swell/cli
Version:
Swell's command line interface/utility
129 lines (128 loc) • 4.33 kB
JavaScript
import { titleize } from 'inflection';
import { createStream, getBorderCharacters, } from 'table';
import { Output } from './output.js';
// the terminal width used in column display
// leave 5% for padding
const MAX_WIDTH = Math.floor((process.stdout.columns || 80) * 0.95);
// the default column width
const DEFAULT_COL_WIDTH = 10;
export class TableOutput extends Output {
stream;
constructor(columns, pretty) {
super(columns, pretty);
const extraWidths = this.computeExtraWidths();
const config = {
border: getBorderCharacters('void'),
columnCount: columns.length,
columnDefault: { width: DEFAULT_COL_WIDTH },
columns: columns.map((c) => this.prepareColumnDisplay(c, extraWidths[c])),
drawVerticalLine: () => false,
};
// create the output stream and write the header
this.stream = createStream(config);
this.writeHeader();
}
prepareData(log) {
return this.columns.map((c) => {
// for each column, get the value from the log item and prepare it for
// display in the table, i.e. go through TableLoggedItem before returning
const tableLog = new TableLoggedItem(log, this.pretty);
const preparedLog = tableLog.prepareData();
return preparedLog[c];
});
}
write(row) {
this.stream.write(row);
}
computeExtraWidths() {
const extraWidths = {};
// compute the initial width of the table
// eslint-disable-next-line unicorn/no-array-reduce
const initialWidth = this.columns.reduce((acc, col) => {
const display = this.displayOptions(col);
return acc + (display.width || display.minWidth || DEFAULT_COL_WIDTH);
}, 0);
const availableWidth = MAX_WIDTH - initialWidth;
const minWidthCols = this.columns.filter((col) => this.displayOptions(col).minWidth);
// if there is extra width available, split it across all columns that have
// minWidth
if (availableWidth > 0 && minWidthCols.length > 0) {
const extraWidthPerCol = Math.floor(availableWidth / minWidthCols.length);
for (const col of minWidthCols) {
extraWidths[col] = extraWidthPerCol;
}
}
return extraWidths;
}
displayOptions(column) {
// an extension of the column def in https://www.npmjs.com/package/table
// for config.columns
const columns = {
data: {
minWidth: 40,
wrapWord: !this.pretty,
},
date: {
title: 'Date UTC',
width: 24,
},
req: {
width: 24,
},
request: {
minWidth: 40,
},
status: {
width: 6,
},
time: {
width: 6,
},
};
return columns[column];
}
prepareColumnDisplay(column, extraWidth) {
const display = this.displayOptions(column);
const { minWidth, wrapWord } = display;
let { width } = display;
if (minWidth) {
width = minWidth;
}
if (width && extraWidth) {
width += extraWidth;
}
return { width, wrapWord };
}
writeHeader() {
this.stream.write(this.columns.map((c) => this.displayOptions(c)?.title || titleize(c)));
}
}
/**
* A layer between the log item and the table output that handles the mapping of
* log data to the columns specifically for the table output.
*/
class TableLoggedItem {
logItem;
pretty = false;
constructor(logItem, pretty) {
this.logItem = logItem;
this.pretty = pretty;
}
prepareData() {
this.logItem.data = this.prepareLogItemData();
return this.logItem;
}
prepareLogItemData() {
let logData = this.logItem.data;
try {
if (this.pretty && typeof logData === 'string') {
logData = JSON.parse(logData);
logData = JSON.stringify(logData, null, 2);
}
}
catch {
// do nothing, JSON parsing errors
}
return logData;
}
}