From 89f6db2cc455d46efecdf32a269c024fe99b5789 Mon Sep 17 00:00:00 2001 From: Marius <80470007+mkah91@users.noreply.github.com> Date: Wed, 24 Aug 2022 16:28:37 +0200 Subject: [PATCH] fix: add character length dictionary for string length computation in console (#431) --- src/internalTable/internal-table-printer.ts | 24 ++++++++++++------ src/internalTable/internal-table.ts | 13 ++++++---- src/internalTable/table-pre-processors.ts | 2 +- src/models/common.ts | 4 +++ src/models/external-table.ts | 3 ++- src/utils/console-utils.ts | 20 ++++++++++++++- src/utils/string-utils.ts | 24 ++++++++++++------ src/utils/table-helpers.ts | 28 +++++++++++++++------ test/utils/console-utils.test.ts | 8 ++++++ 9 files changed, 96 insertions(+), 30 deletions(-) diff --git a/src/internalTable/internal-table-printer.ts b/src/internalTable/internal-table-printer.ts index 696bd933..37f2b4cd 100644 --- a/src/internalTable/internal-table-printer.ts +++ b/src/internalTable/internal-table-printer.ts @@ -1,4 +1,4 @@ -import { Row } from '../models/common'; +import { CharLengthDict, Row } from '../models/common'; import { Column, TableStyleDetails } from '../models/internal-table'; import ColoredConsoleLine, { ColorMap } from '../utils/colored-console-line'; import { textWithPadding } from '../utils/string-utils'; @@ -27,7 +27,8 @@ const renderOneLine = ( widthLimitedColumnsArray: { [key: string]: string[] }, isHeader: boolean | undefined, row: Row, - colorMap: ColorMap + colorMap: ColorMap, + charLength?: CharLengthDict ): string => { const line = new ColoredConsoleLine(colorMap); line.addCharsWithColor('', tableStyle.vertical); // dont Color the Column borders @@ -45,7 +46,8 @@ const renderOneLine = ( textWithPadding( textForThisLine, column.alignment || DEFAULT_ROW_ALIGNMENT, - column.length || DEFAULT_COLUMN_LEN + column.length || DEFAULT_COLUMN_LEN, + charLength ) ); line.addCharsWithColor('', ` ${tableStyle.vertical}`); // dont Color the Column borders @@ -60,10 +62,15 @@ const renderWidthLimitedLines = ( columns: Column[], row: Row, colorMap: ColorMap, - isHeader?: boolean + isHeader?: boolean, + charLength?: CharLengthDict ): string[] => { // { col1: ['How', 'Is', 'Going'], col2: ['I am', 'Tom'], } - const widthLimitedColumnsArray = getWidthLimitedColumnsArray(columns, row); + const widthLimitedColumnsArray = getWidthLimitedColumnsArray( + columns, + row, + charLength + ); const totalLines = Object.values(widthLimitedColumnsArray).reduce( (a, b) => Math.max(a, b.length), @@ -83,7 +90,8 @@ const renderWidthLimitedLines = ( widthLimitedColumnsArray, isHeader, row, - colorMap + colorMap, + charLength ); ret.push(singleLine); @@ -100,7 +108,9 @@ const renderRow = (table: TableInternal, row: Row): string[] => { table.tableStyle, table.columns, row, - table.colorMap + table.colorMap, + undefined, + table.charLength ) ); return ret; diff --git a/src/internalTable/internal-table.ts b/src/internalTable/internal-table.ts index f43a5457..fa4f5ad2 100644 --- a/src/internalTable/internal-table.ts +++ b/src/internalTable/internal-table.ts @@ -1,4 +1,4 @@ -import { Dictionary, Row } from '../models/common'; +import { CharLengthDict, Dictionary, Row } from '../models/common'; import { ComplexOptions, ComputedColumn, @@ -49,6 +49,8 @@ class TableInternal { colorMap: ColorMap; + charLength: CharLengthDict; + initSimple(columns: string[]) { this.columns = columns.map((column) => ({ name: column, @@ -68,11 +70,11 @@ class TableInternal { this.columns = options?.columns?.map(rawColumnToInternalColumn) || this.columns; this.rowSeparator = options?.rowSeparator || this.rowSeparator; - - if(options?.colorMap) { - this.colorMap = { ...this.colorMap, ...options.colorMap }; + this.charLength = options?.charLength || this.charLength; + + if (options?.colorMap) { + this.colorMap = { ...this.colorMap, ...options.colorMap }; } - if (options.rows !== undefined) { this.addRows(options.rows); @@ -92,6 +94,7 @@ class TableInternal { this.computedColumns = []; this.rowSeparator = DEFAULT_ROW_SEPARATOR; this.colorMap = DEFAULT_COLOR_MAP; + this.charLength = {}; if (options instanceof Array) { this.initSimple(options); diff --git a/src/internalTable/table-pre-processors.ts b/src/internalTable/table-pre-processors.ts index 1b8ef879..54a01567 100644 --- a/src/internalTable/table-pre-processors.ts +++ b/src/internalTable/table-pre-processors.ts @@ -34,7 +34,7 @@ const enableColumnsIfNecessary = (table: TableInternal) => { const findColumnWidth = (table: TableInternal) => { table.columns.forEach((column) => { - column.length = findLenOfColumn(column, table.rows); + column.length = findLenOfColumn(column, table.rows, table.charLength); }); }; diff --git a/src/models/common.ts b/src/models/common.ts index 6d2b9e63..35966cc7 100644 --- a/src/models/common.ts +++ b/src/models/common.ts @@ -6,6 +6,10 @@ export type COLOR = typeof COLORS[number]; export interface Dictionary { [key: string]: any; } + +export interface CharLengthDict { + [key: string]: number; +} export interface Row { color: COLOR; separator: boolean; diff --git a/src/models/external-table.ts b/src/models/external-table.ts index 2c4ac4ef..b9d57188 100644 --- a/src/models/external-table.ts +++ b/src/models/external-table.ts @@ -1,5 +1,5 @@ import { ColorMap } from '../utils/colored-console-line'; -import { ALIGNMENT, COLOR, Dictionary } from './common'; +import { ALIGNMENT, CharLengthDict, COLOR, Dictionary } from './common'; import { TableStyleDetails } from './internal-table'; export { ALIGNMENT, COLOR }; @@ -33,4 +33,5 @@ export interface ComplexOptions { computedColumns?: ComputedColumn[]; rowSeparator?: boolean; colorMap?: ColorMap; + charLength?: CharLengthDict; } diff --git a/src/utils/console-utils.ts b/src/utils/console-utils.ts index 93620c86..5b39cb52 100644 --- a/src/utils/console-utils.ts +++ b/src/utils/console-utils.ts @@ -1,9 +1,27 @@ import { wcswidth } from 'simple-wcswidth'; +import { CharLengthDict } from '../models/common'; /* eslint-disable no-control-regex */ const colorRegex = /\x1b\[\d{1,3}m/g; // \x1b[30m \x1b[305m const stripAnsi = (str: string): string => str.replace(colorRegex, ''); -const findWidthInConsole = (str: string): number => wcswidth(stripAnsi(str)); + +export const findWidthInConsole = ( + str: string, + charLength?: CharLengthDict +): number => { + let strLen = 0; + str = stripAnsi(str); + if (charLength) { + Object.entries(charLength).forEach(([key, value]) => { + // count appearance of the key in the string and remove from original string + let regex = new RegExp(key, 'g'); + strLen += (str.match(regex) || []).length * value; + str = str.replace(key, ''); + }); + } + strLen += wcswidth(str); + return strLen; +}; export default findWidthInConsole; diff --git a/src/utils/string-utils.ts b/src/utils/string-utils.ts index d4ad6647..5ccc5692 100644 --- a/src/utils/string-utils.ts +++ b/src/utils/string-utils.ts @@ -1,4 +1,4 @@ -import { ALIGNMENT } from '../models/common'; +import { ALIGNMENT, CharLengthDict } from '../models/common'; import findWidthInConsole from './console-utils'; // ("How are you?",center, 20) => " How are you? " @@ -6,9 +6,10 @@ import findWidthInConsole from './console-utils'; export const textWithPadding = ( text: string, alignment: ALIGNMENT, - columnLen: number + columnLen: number, + charLength?: CharLengthDict ): string => { - const curTextSize = findWidthInConsole(text); + const curTextSize = findWidthInConsole(text, charLength); // alignments for center padding case const leftPadding = Math.floor((columnLen - curTextSize) / 2); const rightPadding = columnLen - leftPadding - curTextSize; @@ -34,7 +35,11 @@ export const textWithPadding = ( }; // ("How are you?",10) => ["How are ", "you?"] -export const limitWidth = (inpStr: string, width: number): string[] => { +export const limitWidth = ( + inpStr: string, + width: number, + charLength?: CharLengthDict +): string[] => { const ret: string[] = []; const spaceSeparatedStrings = inpStr.split(' '); @@ -42,7 +47,7 @@ export const limitWidth = (inpStr: string, width: number): string[] => { let now: string[] = []; let cnt = 0; spaceSeparatedStrings.forEach((strWithoutSpace) => { - const consoleWidth = findWidthInConsole(strWithoutSpace); + const consoleWidth = findWidthInConsole(strWithoutSpace, charLength); if (cnt + consoleWidth <= width) { cnt += consoleWidth + 1; // 1 for the space now.push(strWithoutSpace); @@ -58,5 +63,10 @@ export const limitWidth = (inpStr: string, width: number): string[] => { }; // ("How are you?",10) => ["How are ", "you?"] -export const biggestWordInSentence = (inpStr: string): number => - inpStr.split(' ').reduce((a, b) => Math.max(a, findWidthInConsole(b)), 0); +export const biggestWordInSentence = ( + inpStr: string, + charLength?: CharLengthDict +): number => + inpStr + .split(' ') + .reduce((a, b) => Math.max(a, findWidthInConsole(b, charLength)), 0); diff --git a/src/utils/table-helpers.ts b/src/utils/table-helpers.ts index 51478a38..3c0877ee 100644 --- a/src/utils/table-helpers.ts +++ b/src/utils/table-helpers.ts @@ -1,5 +1,5 @@ import { objIfExists } from '../internalTable/input-converter'; -import { COLOR, Dictionary, Row } from '../models/common'; +import { CharLengthDict, COLOR, Dictionary, Row } from '../models/common'; import { ComputedColumn } from '../models/external-table'; import { Column } from '../models/internal-table'; import findWidthInConsole from './console-utils'; @@ -90,7 +90,11 @@ export const createRow = ( text, }); -export const findLenOfColumn = (column: Column, rows: Row[]): number => { +export const findLenOfColumn = ( + column: Column, + rows: Row[], + charLength?: CharLengthDict +): number => { const columnId = column.name; const columnTitle = column.title; let length = max(0, column?.minLen || 0); @@ -100,20 +104,26 @@ export const findLenOfColumn = (column: Column, rows: Row[]): number => { // if others cant fit find the max word length so that at least the table can be printed length = max( length, - max(column.maxLen, biggestWordInSentence(columnTitle)) + max(column.maxLen, biggestWordInSentence(columnTitle, charLength)) ); length = rows.reduce( (acc, row) => - max(acc, biggestWordInSentence(cellText(row.text[columnId]))), + max( + acc, + biggestWordInSentence(cellText(row.text[columnId]), charLength) + ), length ); return length; } - length = max(length, findWidthInConsole(columnTitle)); + length = max(length, findWidthInConsole(columnTitle, charLength)); rows.forEach((row) => { - length = max(length, findWidthInConsole(cellText(row.text[columnId]))); + length = max( + length, + findWidthInConsole(cellText(row.text[columnId]), charLength) + ); }); return length; @@ -139,14 +149,16 @@ export const createHeaderAsRow = (createRowFn: any, columns: Column[]): Row => { // { col1: ['How', 'Is', 'Going'], col2: ['I am', 'Tom'], } export const getWidthLimitedColumnsArray = ( columns: Column[], - row: Row + row: Row, + charLength?: CharLengthDict ): { [key: string]: string[] } => { const ret: { [key: string]: string[] } = {}; columns.forEach((column) => { ret[column.name] = limitWidth( cellText(row.text[column.name]), - column.length || DEFAULT_COLUMN_LEN + column.length || DEFAULT_COLUMN_LEN, + charLength ); }); diff --git a/test/utils/console-utils.test.ts b/test/utils/console-utils.test.ts index 885b248d..3f664875 100644 --- a/test/utils/console-utils.test.ts +++ b/test/utils/console-utils.test.ts @@ -16,6 +16,14 @@ describe('Console Width Calculation', () => { expect(findWidthInConsole(line.renderConsole())).toBe(2); }); + it('Character length test: No character substitution', () => { + expect(findWidthInConsole('abc')).toBe(3); + }); + + it('Character length test: Character length substitution', () => { + expect(findWidthInConsole('abc', { a: 2 })).toBe(4); + }); + /* these fail on travis bcs travis has another kind of console it('Simplest test: chalk', () => { const testFunction = (Fn: any) => {