diff options
| author | Bobby <[email protected]> | 2022-01-26 13:57:40 -0500 |
|---|---|---|
| committer | Bobby <[email protected]> | 2022-01-26 13:57:40 -0500 |
| commit | 901901f0cc4795305d368f588a2d18fe2a8e28b2 (patch) | |
| tree | dbaf30463ef987e9d6727b3afe783e3d53021b50 /src | |
| parent | 235a6523cc3239193bb908ae91da7426929d79ab (diff) | |
| download | izuku.js-901901f0cc4795305d368f588a2d18fe2a8e28b2.tar.xz izuku.js-901901f0cc4795305d368f588a2d18fe2a8e28b2.zip | |
feat: inbuilt table functionality
Diffstat (limited to 'src')
| -rw-r--r-- | src/helpers/tableBuilder/index.ts | 9 | ||||
| -rw-r--r-- | src/helpers/tableBuilder/src/console-table-printer.ts | 47 | ||||
| -rw-r--r-- | src/helpers/tableBuilder/src/internalTable/input-converter.ts | 25 | ||||
| -rw-r--r-- | src/helpers/tableBuilder/src/internalTable/internal-table-printer.ts | 220 | ||||
| -rw-r--r-- | src/helpers/tableBuilder/src/internalTable/internal-table.ts | 141 | ||||
| -rw-r--r-- | src/helpers/tableBuilder/src/internalTable/table-pre-processors.ts | 53 | ||||
| -rw-r--r-- | src/helpers/tableBuilder/src/models/common.ts | 13 | ||||
| -rw-r--r-- | src/helpers/tableBuilder/src/models/external-table.ts | 34 | ||||
| -rw-r--r-- | src/helpers/tableBuilder/src/models/internal-table.ts | 31 | ||||
| -rw-r--r-- | src/helpers/tableBuilder/src/utils/colored-console-line.ts | 35 | ||||
| -rw-r--r-- | src/helpers/tableBuilder/src/utils/console-utils.ts | 9 | ||||
| -rw-r--r-- | src/helpers/tableBuilder/src/utils/string-utils.ts | 57 | ||||
| -rw-r--r-- | src/helpers/tableBuilder/src/utils/table-constants.ts | 62 | ||||
| -rw-r--r-- | src/helpers/tableBuilder/src/utils/table-helpers.ts | 154 | ||||
| -rw-r--r-- | src/lib/display.ts | 37 |
15 files changed, 926 insertions, 1 deletions
diff --git a/src/helpers/tableBuilder/index.ts b/src/helpers/tableBuilder/index.ts new file mode 100644 index 0000000..bf29228 --- /dev/null +++ b/src/helpers/tableBuilder/index.ts @@ -0,0 +1,9 @@ +import Table from './src/console-table-printer'; +import { + printSimpleTable as printTable, + renderSimpleTable as renderTable +} from './src/internalTable/internal-table-printer'; + +import { COLOR, ALIGNMENT } from './src/models/external-table'; + +export { Table, printTable, renderTable, COLOR, ALIGNMENT }; diff --git a/src/helpers/tableBuilder/src/console-table-printer.ts b/src/helpers/tableBuilder/src/console-table-printer.ts new file mode 100644 index 0000000..cc43b74 --- /dev/null +++ b/src/helpers/tableBuilder/src/console-table-printer.ts @@ -0,0 +1,47 @@ +import TableInternal from './internalTable/internal-table'; +import { Dictionary } from './models/common'; +import { ComplexOptions } from './models/external-table'; +import { + convertRawRowOptionsToStandard, + RowOptionsRaw +} from './utils/table-helpers'; + +export default class Table { + table: TableInternal; + + constructor(options?: ComplexOptions | string[]) { + this.table = new TableInternal(options); + } + + addColumn(column: string) { + this.table.addColumn(column); + return this; + } + + addColumns(columns: string[]) { + this.table.addColumns(columns); + return this; + } + + addRow(text: Dictionary, rowOptions?: RowOptionsRaw) { + this.table.addRow(text, convertRawRowOptionsToStandard(rowOptions)); + return this; + } + + addRows(toBeInsertedRows: any, rowOptions?: RowOptionsRaw) { + this.table.addRows( + toBeInsertedRows, + convertRawRowOptionsToStandard(rowOptions) + ); + return this; + } + + printTable() { + const tableRendered = this.table.renderTable(); + console.log(tableRendered); + } + + render() { + return this.table.renderTable(); + } +} diff --git a/src/helpers/tableBuilder/src/internalTable/input-converter.ts b/src/helpers/tableBuilder/src/internalTable/input-converter.ts new file mode 100644 index 0000000..c3800e6 --- /dev/null +++ b/src/helpers/tableBuilder/src/internalTable/input-converter.ts @@ -0,0 +1,25 @@ +import { COLOR } from '../models/common'; +import { ColumnOptionsRaw } from '../models/external-table'; +import { Column } from '../models/internal-table'; +import { DEFAULT_ROW_ALIGNMENT } from '../utils/table-constants'; + +export const objIfExists = (key: string, val: any) => { + if (!val) { + return {}; + } + + return { + [key]: val, + }; +}; + +export const rawColumnToInternalColumn = ( + column: ColumnOptionsRaw +): Column => ({ + name: column.name, + title: column.title || column.name, + ...objIfExists('color', column.color as COLOR), + ...objIfExists('maxLen', column.maxLen), + ...objIfExists('minLen', column.minLen), + alignment: column.alignment || DEFAULT_ROW_ALIGNMENT, +}); diff --git a/src/helpers/tableBuilder/src/internalTable/internal-table-printer.ts b/src/helpers/tableBuilder/src/internalTable/internal-table-printer.ts new file mode 100644 index 0000000..ffdeb59 --- /dev/null +++ b/src/helpers/tableBuilder/src/internalTable/internal-table-printer.ts @@ -0,0 +1,220 @@ +import { Row } from '../models/common'; +import { Column, TableStyleDetails } from '../models/internal-table'; +import ColoredConsoleLine from '../utils/colored-console-line'; +import { textWithPadding } from '../utils/string-utils'; +import { + DEFAULT_COLUMN_LEN, + DEFAULT_HEADER_ALIGNMENT, + DEFAULT_HEADER_FONT_COLOR, + DEFAULT_ROW_ALIGNMENT, + DEFAULT_ROW_FONT_COLOR, +} from '../utils/table-constants'; +import { + cellText, + createHeaderAsRow, + createRow, + getWidthLimitedColumnsArray, + renderTableHorizontalBorders, +} from '../utils/table-helpers'; +import TableInternal from './internal-table'; +import { preProcessColumns, preProcessRows } from './table-pre-processors'; + +// ║ Index ║ ║ ║ +const renderOneLine = ( + tableStyle: TableStyleDetails, + columns: Column[], + currentLineIndex: number, + widthLimitedColumnsArray: { [key: string]: string[] }, + isHeader: boolean | undefined, + row: Row +): string => { + const line = new ColoredConsoleLine(); + line.addCharsWithColor(DEFAULT_ROW_FONT_COLOR, tableStyle.vertical); + columns.forEach((column) => { + const thisLineHasText = + currentLineIndex < widthLimitedColumnsArray[column.name].length; + + const textForThisLine: string = thisLineHasText + ? cellText(widthLimitedColumnsArray[column.name][currentLineIndex]) + : ''; + + line.addCharsWithColor(DEFAULT_ROW_FONT_COLOR, ' '); + line.addCharsWithColor( + (isHeader && DEFAULT_HEADER_FONT_COLOR) || column.color || row.color, + textWithPadding( + textForThisLine, + column.alignment || DEFAULT_ROW_ALIGNMENT, + column.length || DEFAULT_COLUMN_LEN + ) + ); + line.addCharsWithColor(DEFAULT_ROW_FONT_COLOR, ` ${tableStyle.vertical}`); + }); + return line.renderConsole(); +}; + +// ║ Bold ║ text ║ value ║ +// ║ Index ║ ║ ║ +const renderWidthLimitedLines = ( + tableStyle: TableStyleDetails, + 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 ║ +const renderRow = (table: TableInternal, row: Row): string[] => { + let ret: string[] = []; + ret = ret.concat( + renderWidthLimitedLines(table.tableStyle, table.columns, row) + ); + return ret; +}; + +/* + The analysis Result + ╔═══════╦═══════════════════════════════════════╦════════╗ +*/ +const renderTableTitle = (table: TableInternal): string[] => { + const ret: string[] = []; + + if (table.title === undefined) { + return ret; + } + + const getTableWidth = () => { + 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.length || DEFAULT_COLUMN_LEN) + .reduce(reducer, 1); + }; + + const titleWithPadding = textWithPadding( + table.title as string, + DEFAULT_HEADER_ALIGNMENT, + getTableWidth() + ); + const styledText = new ColoredConsoleLine(); + styledText.addCharsWithColor(DEFAULT_HEADER_FONT_COLOR, titleWithPadding); + // The analysis Result + ret.push(styledText.renderConsole()); + return ret; +}; + +/* + ╔═══════╦═══════════════════════════════════════╦════════╗ + ║ index ║ text ║ value ║ + ╟═══════╬═══════════════════════════════════════╬════════╢ +*/ +const renderTableHeaders = (table: TableInternal): string[] => { + let ret: string[] = []; + + // ╔═══════╦═══════════════════════════════════════╦════════╗ + ret.push( + renderTableHorizontalBorders( + table.tableStyle.headerTop, + table.columns.map((m: Column) => m.length || DEFAULT_COLUMN_LEN) + ) + ); + + // ║ index ║ text ║ value ║ + const row = createHeaderAsRow(createRow, table.columns); + ret = ret.concat( + renderWidthLimitedLines(table.tableStyle, table.columns, row, true) + ); + + // ╟═══════╬═══════════════════════════════════════╬════════╢ + ret.push( + renderTableHorizontalBorders( + table.tableStyle.headerBottom, + table.columns.map((m) => m.length || DEFAULT_COLUMN_LEN) + ) + ); + + return ret; +}; + +const renderTableEnding = (table: TableInternal): string[] => { + const ret: string[] = []; + // ╚═══════╩═══════════════════════════════════════╩════════╝ + ret.push( + renderTableHorizontalBorders( + table.tableStyle.tableBottom, + table.columns.map((m) => m.length || DEFAULT_COLUMN_LEN) + ) + ); + return ret; +}; + +const renderRowSeparator = (table: TableInternal, row: Row): string[] => { + const ret: string[] = []; + const lastRowIndex = table.rows.length - 1; + const currentRowIndex = table.rows.indexOf(row); + + if (currentRowIndex !== lastRowIndex && row.separator) { + // ╟═══════╬═══════════════════════════════════════╬════════╢ + ret.push( + renderTableHorizontalBorders( + table.tableStyle.rowSeparator, + table.columns.map((m) => m.length || DEFAULT_COLUMN_LEN) + ) + ); + } + return ret; +}; + +export const renderTable = (table: TableInternal): string => { + preProcessColumns(table); // enable / disable cols, find maxLn of each col/ computed Columns + preProcessRows(table); // sort and filter + + const ret: string[] = []; + renderTableTitle(table).forEach((row) => ret.push(row)); + + renderTableHeaders(table).forEach((row) => ret.push(row)); + + table.rows.forEach((row) => { + renderRow(table, row).forEach((row_) => ret.push(row_)); + renderRowSeparator(table, row).forEach((row_) => ret.push(row_)); + }); + renderTableEnding(table).forEach((row) => ret.push(row)); + return ret.join('\n'); +}; + +export const renderSimpleTable = (rows: any[]) => { + const table = new TableInternal(); + table.addRows(rows); + return renderTable(table); +}; + +export const printSimpleTable = (rows: any[]) => { + console.log(renderSimpleTable(rows)); +}; diff --git a/src/helpers/tableBuilder/src/internalTable/internal-table.ts b/src/helpers/tableBuilder/src/internalTable/internal-table.ts new file mode 100644 index 0000000..a7b443f --- /dev/null +++ b/src/helpers/tableBuilder/src/internalTable/internal-table.ts @@ -0,0 +1,141 @@ +import { Dictionary, Row } from '../models/common'; +import { + ComplexOptions, + ComputedColumn, + RowFilterFunction, + RowSortFunction, +} from '../models/external-table'; +import { Column, TableStyleDetails } from '../models/internal-table'; +import { + DEFAULT_TABLE_STYLE, + DEFAULT_ROW_ALIGNMENT, + DEFAULT_ROW_FONT_COLOR, + DEFAULT_ROW_SEPARATOR, +} from '../utils/table-constants'; +import { + createColumFromComputedColumn, + createColumFromOnlyName, + createRow, + RowOptions, +} from '../utils/table-helpers'; +import { rawColumnToInternalColumn } from './input-converter'; +import { renderTable } from './internal-table-printer'; + +const DEFAULT_ROW_SORT_FUNC = () => 0; + +const DEFAULT_ROW_FILTER_FUNC = () => true; + +class TableInternal { + title?: string; + + tableStyle: TableStyleDetails; + + columns: Column[]; + + rows: Row[]; + + filterFunction: RowFilterFunction; + + sortFunction: RowSortFunction; + + enabledColumns: string[]; + + disabledColumns: string[]; + + computedColumns: any[]; + + rowSeparator: boolean; + + initSimple(columns: string[]) { + this.columns = columns.map((column) => ({ + name: column, + title: column, + alignment: DEFAULT_ROW_ALIGNMENT, + })); + } + + initDetailed(options: ComplexOptions) { + this.title = options?.title || this.title; + this.tableStyle = options?.style || this.tableStyle; + this.sortFunction = options?.sort || this.sortFunction; + this.filterFunction = options?.filter || this.filterFunction; + this.enabledColumns = options?.enabledColumns || this.enabledColumns; + this.disabledColumns = options?.disabledColumns || this.disabledColumns; + this.computedColumns = options?.computedColumns || this.computedColumns; + this.columns = + options?.columns?.map(rawColumnToInternalColumn) || this.columns; + this.rowSeparator = options?.rowSeparator || this.rowSeparator; + + if (options.rows !== undefined) { + this.addRows(options.rows); + } + } + + constructor(options?: ComplexOptions | string[]) { + // default construction + this.rows = []; + this.columns = []; + this.title = undefined; + this.tableStyle = DEFAULT_TABLE_STYLE; + this.filterFunction = DEFAULT_ROW_FILTER_FUNC; + this.sortFunction = DEFAULT_ROW_SORT_FUNC; + this.enabledColumns = []; + this.disabledColumns = []; + this.computedColumns = []; + this.rowSeparator = DEFAULT_ROW_SEPARATOR; + + if (options instanceof Array) { + this.initSimple(options); + } else if (typeof options === 'object') { + this.initDetailed(options); + } + } + + createColumnFromRow(text: Dictionary) { + const colNames = this.columns.map((col) => col.name); + Object.keys(text).forEach((key) => { + if (!colNames.includes(key)) { + this.columns.push(createColumFromOnlyName(key)); + } + }); + } + + addColumn(textOrObj: string | ComputedColumn) { + if (typeof textOrObj === 'string') { + this.columns.push(createColumFromOnlyName(textOrObj)); + } else { + this.columns.push(createColumFromComputedColumn(textOrObj)); + } + } + + addColumns(toBeInsertedColumns: string[]) { + toBeInsertedColumns.forEach((toBeInsertedColumn) => { + this.addColumn(toBeInsertedColumn); + }); + } + + addRow(text: Dictionary, options?: RowOptions) { + this.createColumnFromRow(text); + this.rows.push( + createRow( + options?.color || DEFAULT_ROW_FONT_COLOR, + text, + options?.separator !== undefined + ? options?.separator + : this.rowSeparator + ) + ); + } + + addRows(toBeInsertedRows: Dictionary[], options?: RowOptions) { + toBeInsertedRows.forEach((toBeInsertedRow) => { + this.addRow(toBeInsertedRow, options); + }); + } + + renderTable() { + return renderTable(this); + } +} + +export default TableInternal; diff --git a/src/helpers/tableBuilder/src/internalTable/table-pre-processors.ts b/src/helpers/tableBuilder/src/internalTable/table-pre-processors.ts new file mode 100644 index 0000000..1b8ef87 --- /dev/null +++ b/src/helpers/tableBuilder/src/internalTable/table-pre-processors.ts @@ -0,0 +1,53 @@ +/* eslint-disable no-param-reassign */ +import { Row } from '../models/common'; +import { ComputedColumn } from '../models/external-table'; +import { Column } from '../models/internal-table'; +import { findLenOfColumn } from '../utils/table-helpers'; +import TableInternal from './internal-table'; + +const createComputedColumnsIfNecessary = (table: TableInternal) => { + if (table.computedColumns.length) { + table.computedColumns.forEach((computedColumn: ComputedColumn) => { + table.addColumn(computedColumn); + table.rows.forEach((row: Row) => { + row.text[computedColumn.name] = computedColumn.function(row.text); + }); + }); + } +}; + +const disableColumnsIfNecessary = (table: TableInternal) => { + if (table.enabledColumns.length) { + table.columns = table.columns.filter((col: Column) => + table.enabledColumns.includes(col.name) + ); + } +}; + +const enableColumnsIfNecessary = (table: TableInternal) => { + if (table.disabledColumns.length) { + table.columns = table.columns.filter( + (col: Column) => !table.disabledColumns.includes(col.name) + ); + } +}; + +const findColumnWidth = (table: TableInternal) => { + table.columns.forEach((column) => { + column.length = findLenOfColumn(column, table.rows); + }); +}; + +export const preProcessColumns = (table: TableInternal) => { + createComputedColumnsIfNecessary(table); + enableColumnsIfNecessary(table); + disableColumnsIfNecessary(table); + findColumnWidth(table); +}; + +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; +}; diff --git a/src/helpers/tableBuilder/src/models/common.ts b/src/helpers/tableBuilder/src/models/common.ts new file mode 100644 index 0000000..6d2b9e6 --- /dev/null +++ b/src/helpers/tableBuilder/src/models/common.ts @@ -0,0 +1,13 @@ +import { ALIGNMENTS, COLORS } from '../utils/table-constants'; + +export type ALIGNMENT = typeof ALIGNMENTS[number]; + +export type COLOR = typeof COLORS[number]; +export interface Dictionary { + [key: string]: any; +} +export interface Row { + color: COLOR; + separator: boolean; + text: Dictionary; +} diff --git a/src/helpers/tableBuilder/src/models/external-table.ts b/src/helpers/tableBuilder/src/models/external-table.ts new file mode 100644 index 0000000..4c2df30 --- /dev/null +++ b/src/helpers/tableBuilder/src/models/external-table.ts @@ -0,0 +1,34 @@ +import { ALIGNMENT, COLOR, Dictionary } from './common'; +import { TableStyleDetails } from './internal-table'; + +export { ALIGNMENT, COLOR }; + +export interface ColumnOptionsRaw { + name: string; // unique id + title?: string; // the value that will be printed, if not present this will be 'name' + alignment?: ALIGNMENT; + color?: COLOR; + maxLen?: number; + minLen?: number; +} + +export interface ComputedColumn extends ColumnOptionsRaw { + function: (arg0: any) => any; +} + +export type RowSortFunction = (row1: any, row2: any) => number; + +export type RowFilterFunction = (row: any) => Boolean; + +export interface ComplexOptions { + style?: TableStyleDetails; + title?: string; + columns?: ColumnOptionsRaw[]; + rows?: Dictionary[]; + sort?: RowSortFunction; + filter?: RowFilterFunction; + enabledColumns?: string[]; + disabledColumns?: string[]; + computedColumns?: ComputedColumn[]; + rowSeparator?: boolean; +} diff --git a/src/helpers/tableBuilder/src/models/internal-table.ts b/src/helpers/tableBuilder/src/models/internal-table.ts new file mode 100644 index 0000000..50d048a --- /dev/null +++ b/src/helpers/tableBuilder/src/models/internal-table.ts @@ -0,0 +1,31 @@ +import { ALIGNMENT, COLOR } from './common'; + +/* +All the fields of Internal Table has to be mandatory +These fields are generated based on user input +and during generated is some input is missing it is filled by default value. +*/ + +export interface Column { + name: string; + title: string; + alignment?: ALIGNMENT; + color?: COLOR; + length?: number; + minLen?: number; + maxLen?: number; +} + +type TableLineDetailsKeys = 'left' | 'right' | 'mid' | 'other'; + +export type TableLineDetails = { + [key in TableLineDetailsKeys]: string; +}; + +export type TableStyleDetails = { + headerTop: TableLineDetails; + headerBottom: TableLineDetails; + tableBottom: TableLineDetails; + vertical: string; + rowSeparator?: TableLineDetails; +}; diff --git a/src/helpers/tableBuilder/src/utils/colored-console-line.ts b/src/helpers/tableBuilder/src/utils/colored-console-line.ts new file mode 100644 index 0000000..7de589d --- /dev/null +++ b/src/helpers/tableBuilder/src/utils/colored-console-line.ts @@ -0,0 +1,35 @@ +import { COLOR } from '../models/common'; + +const COLOR_MAP: { + [key in COLOR]?: string; +} = { + red: '\x1b[31m', + green: '\x1b[32m', + yellow: '\x1b[33m', + blue: '\x1b[34m', + magenta: '\x1b[35m', + cyan: '\x1b[36m', + white: '\x1b[37m', + crimson: '\x1b[38m', + white_bold: '\x1b[01m', + reset: '\x1b[0m' +}; + +export const colorString = (color: COLOR, text: string) => + `${color && COLOR_MAP[color]}${text}${COLOR_MAP.reset}`; + +export default class ColoredConsoleLine { + text: string; + + constructor() { + this.text = ''; + } + + addCharsWithColor(color: COLOR, text: string) { + this.text += colorString(color, text); + } + + renderConsole(): string { + return this.text; + } +} diff --git a/src/helpers/tableBuilder/src/utils/console-utils.ts b/src/helpers/tableBuilder/src/utils/console-utils.ts new file mode 100644 index 0000000..93620c8 --- /dev/null +++ b/src/helpers/tableBuilder/src/utils/console-utils.ts @@ -0,0 +1,9 @@ +import { wcswidth } from 'simple-wcswidth'; + +/* 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 default findWidthInConsole; diff --git a/src/helpers/tableBuilder/src/utils/string-utils.ts b/src/helpers/tableBuilder/src/utils/string-utils.ts new file mode 100644 index 0000000..5155c73 --- /dev/null +++ b/src/helpers/tableBuilder/src/utils/string-utils.ts @@ -0,0 +1,57 @@ +import { ALIGNMENT } from '../models/common'; +import findWidthInConsole from './console-utils'; + +// ("How are you?",center, 20) => " How are you? " +// ("How are you?",right, 20) => " How are you?" +export const textWithPadding = ( + text: string, + alignment: ALIGNMENT, + columnLen: number +): string => { + const curTextSize = findWidthInConsole(text); + // alignments for center padding case + const leftPadding = Math.floor((columnLen - curTextSize) / 2); + const rightPadding = columnLen - leftPadding - curTextSize; + + // console.log(text, columnLen, curTextSize); + switch (alignment) { + case 'left': + return text.concat(' '.repeat(columnLen - curTextSize)); + case 'center': + return ' ' + .repeat(leftPadding) + .concat(text) + .concat(' '.repeat(rightPadding)); + case 'right': + default: + return ' '.repeat(columnLen - 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); diff --git a/src/helpers/tableBuilder/src/utils/table-constants.ts b/src/helpers/tableBuilder/src/utils/table-constants.ts new file mode 100644 index 0000000..90d7bf9 --- /dev/null +++ b/src/helpers/tableBuilder/src/utils/table-constants.ts @@ -0,0 +1,62 @@ +import { ALIGNMENT, COLOR } from '../models/common'; +import { TableStyleDetails } from '../models/internal-table'; + +export const DEFAULT_COLUMN_LEN = 20; + +export const DEFAULT_ROW_SEPARATOR = false; + +export const DEFAULT_TABLE_STYLE: TableStyleDetails = { + /* + Default Style + ┌────────────┬─────┬──────┐ + │ foo │ bar │ baz │ + │ frobnicate │ bar │ quuz │ + └────────────┴─────┴──────┘ + */ + headerTop: { + left: '┌', + mid: '┬', + right: '┐', + other: '─', + }, + headerBottom: { + left: '├', + mid: '┼', + right: '┤', + other: '─', + }, + tableBottom: { + left: '└', + mid: '┴', + right: '┘', + other: '─', + }, + vertical: '│', + rowSeparator: { + left: '├', + mid: '┼', + right: '┤', + other: '─', + }, +}; + +export const ALIGNMENTS = ['right', 'left', 'center']; + +export const COLORS = [ + 'red', + 'green', + 'yellow', + 'white', + 'blue', + 'magenta', + 'cyan', + 'crimson', + 'white_bold', + 'reset', +]; + +export const DEFAULT_ROW_FONT_COLOR: COLOR = 'white'; +export const DEFAULT_HEADER_FONT_COLOR: COLOR = 'white_bold'; + +export const DEFAULT_ROW_ALIGNMENT: ALIGNMENT = 'right'; +export const DEFAULT_HEADER_ALIGNMENT: ALIGNMENT = 'center'; diff --git a/src/helpers/tableBuilder/src/utils/table-helpers.ts b/src/helpers/tableBuilder/src/utils/table-helpers.ts new file mode 100644 index 0000000..51478a3 --- /dev/null +++ b/src/helpers/tableBuilder/src/utils/table-helpers.ts @@ -0,0 +1,154 @@ +import { objIfExists } from '../internalTable/input-converter'; +import { COLOR, Dictionary, Row } from '../models/common'; +import { ComputedColumn } from '../models/external-table'; +import { Column } from '../models/internal-table'; +import findWidthInConsole from './console-utils'; +import { biggestWordInSentence, limitWidth } from './string-utils'; +import { + DEFAULT_COLUMN_LEN, + DEFAULT_ROW_ALIGNMENT, + DEFAULT_ROW_SEPARATOR, + DEFAULT_HEADER_FONT_COLOR, +} from './table-constants'; + +const max = (a: number, b: number) => Math.max(a, b); + +// takes any input that is given by user and converts to string +export const cellText = (text: string | number): string => + text === undefined || text === null ? '' : `${text}`; + +export interface RowOptionsRaw { + color?: string; + separator?: boolean; +} + +export interface RowOptions { + color: COLOR; + separator: boolean; +} + +export const convertRawRowOptionsToStandard = ( + options?: RowOptionsRaw +): RowOptions | undefined => { + if (options) { + return { + color: options.color as COLOR, + separator: options.separator || DEFAULT_ROW_SEPARATOR, + }; + } + return undefined; +}; + +export const createTableHorizontalBorders = ( + { + left, + mid, + right, + other, + }: { left: string; mid: string; right: string; other: string }, + column_lengths: number[] +) => { + // ╚ + let ret = left; + + // ╚═══════╩═══════════════════════════════════════╩════════╩ + column_lengths.forEach((len) => { + ret += other.repeat(len + 2); + ret += mid; + }); + + // ╚═══════╩═══════════════════════════════════════╩════════ + ret = ret.slice(0, -mid.length); + + // ╚═══════╩═══════════════════════════════════════╩════════╝ + ret += right; + return ret; +}; + +export const createColumFromOnlyName = (name: string): Column => ({ + name, + title: name, +}); +export const createColumFromComputedColumn = ( + column: ComputedColumn +): Column => ({ + name: column.name, + title: column.title || column.name, + ...objIfExists('color', column.color as COLOR), + ...objIfExists('maxLen', column.maxLen), + ...objIfExists('minLen', column.minLen), + alignment: column.alignment || DEFAULT_ROW_ALIGNMENT, +}); + +export const createRow = ( + color: COLOR, + text: Dictionary, + separator: boolean +): Row => ({ + color, + separator, + text, +}); + +export const findLenOfColumn = (column: Column, rows: Row[]): number => { + const columnId = column.name; + const columnTitle = column.title; + let length = max(0, column?.minLen || 0); + + if (column.maxLen) { + // if customer input is mentioned a max width, lets see if all other can fit here + // 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)) + ); + length = rows.reduce( + (acc, row) => + max(acc, biggestWordInSentence(cellText(row.text[columnId]))), + length + ); + return length; + } + + length = max(length, findWidthInConsole(columnTitle)); + + rows.forEach((row) => { + length = max(length, findWidthInConsole(cellText(row.text[columnId]))); + }); + + return length; +}; + +export const renderTableHorizontalBorders = ( + style: any, + column_lengths: number[] +): string => { + const str = createTableHorizontalBorders(style, column_lengths); + return str; +}; + +export const createHeaderAsRow = (createRowFn: any, columns: Column[]): Row => { + const headerColor: COLOR = DEFAULT_HEADER_FONT_COLOR; + const row: Row = createRowFn(headerColor, {}, false); + columns.forEach((column) => { + row.text[column.name] = column.title; + }); + return row; +}; + +// { col1: ['How', 'Is', 'Going'], col2: ['I am', 'Tom'], } +export const getWidthLimitedColumnsArray = ( + columns: Column[], + row: Row +): { [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 + ); + }); + + return ret; +}; diff --git a/src/lib/display.ts b/src/lib/display.ts index 4349aa5..2ec19d9 100644 --- a/src/lib/display.ts +++ b/src/lib/display.ts @@ -1,5 +1,6 @@ import { Frame } from '../index'; import { table } from 'table'; +import { Table } from '../helpers/tableBuilder'; /** * getTable returns the data compatible with table.table @@ -35,6 +36,40 @@ function getTable( } /** + * displayTable prints the table to the console + * @param rowdata the rowdata to be sent to the frame + */ +export function displayTable(rowdata: any[][]): void { + // Convert row data into object with keys and values, keys are the column names stored in rowdata[0] + const headerObject: any = []; + rowdata[0].forEach((column) => { + headerObject.push({ + name: column, + alignment: 'left', + paddingLeft: 2, + bold: true, + paddingRight: 2 + }); + }); + const table = new Table({ + columns: headerObject + }); + + const rowdataObject = rowdata.map((row) => { + const rowObject: any = {}; + row.forEach((value, index) => { + rowObject[rowdata[0][index]] = value; + }); + return rowObject; + }); + + // remove the first row from rowdataObject + const rowdataObjectWithoutHeader = rowdataObject.slice(1); + table.addRows(rowdataObjectWithoutHeader); + table.printTable(); +} + +/** * show prints the frame in console.table format * @returns the current frame * @throws Error if the frame is empty @@ -63,7 +98,7 @@ export function show(this: Frame): void { numberOfRows - 1 ]; const combinedRow = [...firstThreeRows, [...middleRow], ...lastThreeRows]; - console.log(table(getTable(combinedRow, this.columns, indexRow))); + displayTable(getTable(combinedRow, this.columns, indexRow)); } } } |
