@rawcmd/typewriters
Version:
Collection of typewriters for styling command-line messages for Rawcmd.
99 lines (86 loc) • 3.31 kB
text/typescript
import { EOL, alignText, wrapText, trucateText, repairAnsi, TextAlign } from '@rawcmd/text';
import { toArray, toString } from '@rawcmd/utils';
/**
* Row typewriter configuration options.
*/
export interface RowTypewriterOptions {
separatorSymbol?: string;
truncationSymbol?: string;
}
/**
* Row column configuration options.
*/
export interface ColumnRowTypewriterOptions {
index: number;
width?: number;
textLength?: number;
textWrap?: boolean;
textAlign?: TextAlign;
}
/**
* Row typewriter data type.
*/
export type RowTypewriterData = any[];
/**
* Returns a typewriter function which converts the provided `data` into a
* string representing a row with one or more columns.
* NOTE: This method supports multibyte characters.
* @param columns List of column related configuration options.
* @param options Row related configuration options.
*/
export function rowTypewriter(columns?: ColumnRowTypewriterOptions[], options?: RowTypewriterOptions) {
columns = columns || [];
options = {
separatorSymbol: ' ',
truncationSymbol: '…',
...options,
};
return (data: RowTypewriterData) => {
data = (data || []).map((value) => { // make sure every data item is a string
return toString(value) || '';
});
const config = data.map((value, index) => { // build configuration for each column
return {
index,
width: Infinity,
textLength: Infinity,
textWrap: true,
textAlign: TextAlign.LEFT,
...columns.find((column) => column.index === index),
};
});
const grid = data.map((text, index) => { // build data rows for each column
const { truncationSymbol } = options;
const { textLength } = config[index];
return trucateText(text, textLength, truncationSymbol) || ''; // shorten text to allowed length
}).map((text, index) => {
const { width, textWrap } = config[index];
return toArray(textWrap ? wrapText(text, width) : text); // split column text into rows based on allowed column width
}).map((rows, index) => {
const { truncationSymbol } = options;
const { width } = config[index];
return rows.map((row) => {
return trucateText(row, width, truncationSymbol); // shorten text of each column line (could be a word which is longer then allowed column width)
});
});
const size = [ // calculate grid dimension
data.length,
Math.max(...grid.map((rows) => rows.length)),
];
const output = grid.map((rows) => { // build final output structure
return repairAnsi(...rows);
}).map((rows, index) => { // build final output structure
return rows.concat(
...Array(size[1] - rows.length).fill(' '), // make sure all columns have the same number of rows
).map((row) => {
const { width, textAlign } = config[index];
return alignText(row, width, textAlign); // align each column data by adding spaces
});
}).reduce((r, a) => { // transpose data by columns to rows and rows to collumns
return a.map((v, i) => [...(r[i] || []), v]) as any;
}, []).map((rows) => { // merge parts into string
return rows.join(options.separatorSymbol || '');
});
return output.length ? output.join(EOL) : ''; // always return value
};
}