Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Width Limit #343

Merged
merged 16 commits into from
Mar 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
126 changes: 88 additions & 38 deletions src/internalTable/internal-table-printer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Column, Row } from '../models/internal-table';
import ColoredConsoleLine from '../utils/colored-console-line';
import { textWithPadding, limitWidth } from '../utils/string-utils';
import {
defaultHeaderAlignment,
defaultHeaderFontColor,
Expand All @@ -12,48 +13,95 @@ import {
createHeaderAsRow,
createRow,
renderTableHorizontalBorders,
textWithPadding,
getWidthLimitedColumnsArray,
} from '../utils/table-helpers';
import { TableInternal } from './internal-table';
import { preProcessColumns, preProcessRows } from './table-pre-processors';

function renderLine(
// ║ Index ║ ║ ║
const renderOneLine = (
tableStyle: TABLE_STYLE_DETAILS,
columns: Column[],
row: Row,
isHeader?: boolean
): string {
currentLineIndex: number,
widthLimitedColumnsArray: { [key: string]: string[] },
isHeader: boolean | undefined,
row: Row
): string => {
const line = new ColoredConsoleLine();
line.addWithColor(defaultRowFontColor, tableStyle.vertical);

line.addCharsWithColor(defaultRowFontColor, tableStyle.vertical);
columns.forEach((column) => {
line.addWithColor(defaultRowFontColor, ' ');
line.addWithColor(
(isHeader && defaultHeaderFontColor) || column.color || row.color, // column color is prioritized as row color
const thisLineHasText =
currentLineIndex < widthLimitedColumnsArray[column.name].length;

const textForThisLine: string = thisLineHasText
? cellText(widthLimitedColumnsArray[column.name][currentLineIndex])
: '';

line.addCharsWithColor(defaultRowFontColor, ' ');
line.addCharsWithColor(
(isHeader && defaultHeaderFontColor) || column.color || row.color,
textWithPadding(
`${cellText(row.text[column.name])}`,
textForThisLine,
column.alignment || defaultRowAlignment,
column.max_ln || 20
column.maxLen || 20
)
);
line.addWithColor(defaultRowFontColor, ` ${tableStyle.vertical}`);
line.addCharsWithColor(defaultRowFontColor, ` ${tableStyle.vertical}`);
});

return line.renderConsole();
}
};

// ║ Bold ║ text ║ value ║
// ║ Index ║ ║ ║
const renderWidthLimitedLines = (
tableStyle: TABLE_STYLE_DETAILS,
columns: Column[],
row: Row,
isHeader?: boolean
): string[] => {
// { col1: ['How', 'Is', 'Going'], col2: ['I am', 'Tom'], }
const widthLimitedColumnsArray = getWidthLimitedColumnsArray(columns, row);

const totalLines = Object.values(widthLimitedColumnsArray).reduce(
(a, b) => Math.max(a, b.length),
0
);

const ret = [];
for (
let currentLineIndex = 0;
currentLineIndex < totalLines;
currentLineIndex += 1
) {
const singleLine = renderOneLine(
tableStyle,
columns,
currentLineIndex,
widthLimitedColumnsArray,
isHeader,
row
);

ret.push(singleLine);
}

return ret;
};

// ║ 1 ║ I would like some red wine please ║ 10.212 ║
function renderRow(table: TableInternal, row: Row): string[] {
const ret: string[] = [];
ret.push(renderLine(table.tableStyle, table.columns, row));
const renderRow = (table: TableInternal, row: Row): string[] => {
let ret: string[] = [];
ret = ret.concat(
renderWidthLimitedLines(table.tableStyle, table.columns, row)
);
return ret;
}
};

/*
The analysis Result
╔═══════╦═══════════════════════════════════════╦════════╗
*/
function renderTableTitle(table: TableInternal): string[] {
const renderTableTitle = (table: TableInternal): string[] => {
const ret: string[] = [];

if (table.title === undefined) {
Expand All @@ -64,7 +112,7 @@ function renderTableTitle(table: TableInternal): string[] {
const reducer = (accumulator: number, currentValue: number) =>
// ║ cell ║, 2 spaces + cellTextSize + one border on the left
accumulator + currentValue + 2 + 1;
return table.columns.map((m) => m.max_ln || 20).reduce(reducer, 1);
return table.columns.map((m) => m.maxLen || 20).reduce(reducer, 1);
};

const titleWithPadding = textWithPadding(
Expand All @@ -73,56 +121,58 @@ function renderTableTitle(table: TableInternal): string[] {
getTableWidth()
);
const styledText = new ColoredConsoleLine();
styledText.addWithColor(defaultHeaderFontColor, titleWithPadding);
styledText.addCharsWithColor(defaultHeaderFontColor, titleWithPadding);
// The analysis Result
ret.push(styledText.renderConsole());
return ret;
}
};

/*
╔═══════╦═══════════════════════════════════════╦════════╗
║ index ║ text ║ value ║
╟═══════╬═══════════════════════════════════════╬════════╢
*/
function renderTableHeaders(table: TableInternal): string[] {
const ret: string[] = [];
const renderTableHeaders = (table: TableInternal): string[] => {
let ret: string[] = [];

// ╔═══════╦═══════════════════════════════════════╦════════╗
ret.push(
renderTableHorizontalBorders(
table.tableStyle.headerTop,
table.columns.map((m) => m.max_ln || 20)
table.columns.map((m) => m.maxLen || 20)
)
);

// ║ index ║ text ║ value ║
const row = createHeaderAsRow(createRow, table.columns);
ret.push(renderLine(table.tableStyle, table.columns, row, true));
ret = ret.concat(
renderWidthLimitedLines(table.tableStyle, table.columns, row, true)
);

// ╟═══════╬═══════════════════════════════════════╬════════╢
ret.push(
renderTableHorizontalBorders(
table.tableStyle.headerBottom,
table.columns.map((m) => m.max_ln || 20)
table.columns.map((m) => m.maxLen || 20)
)
);

return ret;
}
};

function renderTableEnding(table: TableInternal): string[] {
const renderTableEnding = (table: TableInternal): string[] => {
const ret: string[] = [];
// ╚═══════╩═══════════════════════════════════════╩════════╝
ret.push(
renderTableHorizontalBorders(
table.tableStyle.tableBottom,
table.columns.map((m) => m.max_ln || 20)
table.columns.map((m) => m.maxLen || 20)
)
);
return ret;
}
};

export function renderTable(table: TableInternal): string {
export const renderTable = (table: TableInternal): string => {
preProcessColumns(table); // enable / disable cols, find maxLn of each col/ computed Columns
preProcessRows(table); // sort and filter

Expand All @@ -136,14 +186,14 @@ export function renderTable(table: TableInternal): string {
});
renderTableEnding(table).forEach((row) => ret.push(row));
return ret.join('\n');
}
};

export function renderSimpleTable(rows: any[]) {
export const renderSimpleTable = (rows: any[]) => {
const table = new TableInternal();
table.addRows(rows);
return renderTable(table);
}
};

export function printSimpleTable(rows: any[]) {
export const printSimpleTable = (rows: any[]) => {
console.log(renderSimpleTable(rows));
}
};
2 changes: 2 additions & 0 deletions src/internalTable/internal-table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ interface ColumnOptionsRaw {
title?: string; // the value that will be printed, if not present this will be 'name'
alignment?: ALIGNMENT;
color?: COLOR;
maxLen?: number;
}

export interface ComputedColumn extends ColumnOptionsRaw {
Expand Down Expand Up @@ -89,6 +90,7 @@ export class TableInternal {
name: column.name,
title: column.title || column.name,
...objIfExists('color', column.color as COLOR),
...objIfExists('maxLen', column.maxLen),
alignment: column.alignment || defaultRowAlignment,
})) || [];
}
Expand Down
26 changes: 13 additions & 13 deletions src/internalTable/table-pre-processors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Column, Row } from '../models/internal-table';
import { findMaxLenOfColumn } from '../utils/table-helpers';
import { ComputedColumn, TableInternal } from './internal-table';

function createComputedColumnsIfNecessary(table: TableInternal) {
const createComputedColumnsIfNecessary = (table: TableInternal) => {
if (table.computedColumns.length) {
table.computedColumns.forEach((computedColumn: ComputedColumn) => {
table.addColumn(computedColumn.name);
Expand All @@ -12,40 +12,40 @@ function createComputedColumnsIfNecessary(table: TableInternal) {
});
});
}
}
};

function disableColumnsIfNecessary(table: TableInternal) {
const disableColumnsIfNecessary = (table: TableInternal) => {
if (table.enabledColumns.length) {
table.columns = table.columns.filter((col: Column) =>
table.enabledColumns.includes(col.name)
);
}
}
};

function enableColumnsIfNecessary(table: TableInternal) {
const enableColumnsIfNecessary = (table: TableInternal) => {
if (table.disabledColumns.length) {
table.columns = table.columns.filter(
(col: Column) => !table.disabledColumns.includes(col.name)
);
}
}
};

function findMaxColumnLength(table: TableInternal) {
const findMaxColumnLength = (table: TableInternal) => {
table.columns.forEach((column) => {
column.max_ln = findMaxLenOfColumn(column, table.rows);
column.maxLen = findMaxLenOfColumn(column, table.rows);
});
}
};

export function preProcessColumns(table: TableInternal) {
export const preProcessColumns = (table: TableInternal) => {
createComputedColumnsIfNecessary(table);
enableColumnsIfNecessary(table);
disableColumnsIfNecessary(table);
findMaxColumnLength(table);
}
};

export function preProcessRows(table: TableInternal) {
export const preProcessRows = (table: TableInternal) => {
const newRows = table.rows
.filter((r) => table.filterFunction(r.text))
.sort((r1, r2) => table.sortFunction(r1.text, r2.text));
table.rows = newRows;
}
};
2 changes: 1 addition & 1 deletion src/models/internal-table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export interface Column {
title: string;
alignment?: ALIGNMENT;
color?: COLOR;
max_ln?: number;
maxLen?: number;
}

export interface Row {
Expand Down
2 changes: 1 addition & 1 deletion src/utils/colored-console-line.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export default class ColoredConsoleLine {
this.text = '';
}

addWithColor(color: COLOR, text: string) {
addCharsWithColor(color: COLOR, text: string) {
this.text += colorString(color, text);
}

Expand Down
56 changes: 56 additions & 0 deletions src/utils/string-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import findWidthInConsole from './console-utils';
import { ALIGNMENT } from './table-constants';
import { cellText } from './table-helpers';

// ("How are you?",center, 20) => " How are you? "
// ("How are you?",right, 20) => " How are you?"
export const textWithPadding = (
text: string,
alignment: ALIGNMENT,
mxColumnLen: number
): string => {
const curTextSize = findWidthInConsole(text);
// alignments for center padding case
const leftPadding = Math.floor((mxColumnLen - curTextSize) / 2);
const rightPadding = mxColumnLen - leftPadding - curTextSize;
switch (alignment) {
case 'left':
return text.concat(' '.repeat(mxColumnLen - curTextSize));
case 'center':
return ' '
.repeat(leftPadding)
.concat(text)
.concat(' '.repeat(rightPadding));
case 'right':
default:
return ' '.repeat(mxColumnLen - curTextSize).concat(text);
}
};

// ("How are you?",10) => ["How are ", "you?"]
export const limitWidth = (inpStr: string, width: number): string[] => {
const ret: string[] = [];

const spaceSeparatedStrings = inpStr.split(' ');

let now: string[] = [];
let cnt = 0;
spaceSeparatedStrings.forEach((strWithoutSpace) => {
const consoleWidth = findWidthInConsole(strWithoutSpace);
if (cnt + consoleWidth <= width) {
cnt += consoleWidth + 1; // 1 for the space
now.push(strWithoutSpace);
} else {
ret.push(now.join(' '));
now = [strWithoutSpace];
cnt = consoleWidth + 1;
}
});
ret.push(now.join(' '));

return ret;
};

// ("How are you?",10) => ["How are ", "you?"]
export const biggestWordInSentence = (inpStr: string): number =>
inpStr.split(' ').reduce((a, b) => Math.max(a, findWidthInConsole(b)), 0);