Skip to content

Commit

Permalink
feat: Width Limit (#343)
Browse files Browse the repository at this point in the history
* feat: Width Limit of Column

* Updated dependencies
  • Loading branch information
ayonious committed Mar 6, 2021
1 parent 0023edb commit f5a541b
Show file tree
Hide file tree
Showing 17 changed files with 585 additions and 368 deletions.
126 changes: 88 additions & 38 deletions src/internalTable/internal-table-printer.ts
@@ -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
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
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
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
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
@@ -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);

0 comments on commit f5a541b

Please sign in to comment.