console-toolkit
Version:
Toolkit to produce a fancy console output (boxes, tables, charts, colors).
254 lines (222 loc) • 6.1 kB
JavaScript
import {addAlias} from './meta.js';
import {getLength, clipStrings, toStrings} from './strings.js';
export class Box {
constructor(s, normalized) {
if (s instanceof Box) {
this.box = [...s.box]; // copy
} else {
this.box = Array.isArray(s) && normalized ? s : Box.make(s).box;
}
}
get width() {
return this.box.length ? getLength(this.box[0]) : 0;
}
get height() {
return this.box.length;
}
static make(s, options) {
main: for (;;) {
switch (typeof s) {
case 'function':
for (let i = 0; i < 10 && typeof s == 'function'; ++i) s = s();
if (typeof s == 'function') s = String(s);
continue main;
case 'object':
if (s instanceof Box) return s.clone();
if (typeof s?.toBox == 'function') return s.toBox(options);
if (typeof s?.toPanel == 'function') return s.toPanel().toBox(options);
break;
}
s = toStrings(s);
break main;
}
if (s.length <= 1) return new Box(s, true);
const {symbol = ' ', align = 'left'} = options || {},
widths = s.map(s => getLength(s)),
width = Math.max(0, ...widths);
switch (align) {
case 'left':
case 'l':
return new Box(
s.map((s, i) => s + symbol.repeat(width - widths[i])),
true
);
case 'right':
case 'r':
return new Box(
s.map((s, i) => symbol.repeat(width - widths[i]) + s),
true
);
}
// center
return new Box(
s.map((s, i) => {
const n = width - widths[i];
if (!n) return s;
const half = n >> 1,
padding = symbol.repeat(half);
return padding + s + padding + (n & 1 ? symbol : '');
}),
true
);
}
static makeBlank(width, height, symbol = ' ') {
return new Box(height <= 0 ? [] : new Array(height).fill(symbol.repeat(width)), true);
}
toStrings() {
return this.box;
}
clone() {
return new Box(this);
}
clip(width, options) {
return Box.make(clipStrings(this.box, width, options));
}
// padding
padLeftRight(left, right, symbol = ' ') {
const paddingSmall = symbol.repeat(Math.min(left, right)),
paddingLarge = paddingSmall + symbol.repeat(Math.max(left - right, right - left));
return new Box(
left < right
? this.box.map(s => paddingSmall + s + paddingLarge)
: this.box.map(s => paddingLarge + s + paddingSmall),
true
);
}
padTopBottom(top, bottom, symbol = ' ') {
const padding = symbol.repeat(this.width);
return new Box([...new Array(top).fill(padding), ...this.box, ...new Array(bottom).fill(padding)], true);
}
padRight(n, symbol = ' ') {
const padding = symbol.repeat(n);
return new Box(
this.box.map(s => s + padding),
true
);
}
padLeft(n, symbol = ' ') {
const padding = symbol.repeat(n);
return new Box(
this.box.map(s => padding + s),
true
);
}
padTop(n, symbol = ' ') {
const padding = symbol.repeat(this.width);
return new Box([...new Array(n).fill(padding), ...this.box], true);
}
padBottom(n, symbol = ' ') {
const padding = symbol.repeat(this.width);
return new Box([...this.box, ...new Array(n).fill(padding)], true);
}
pad(t, r, b, l, symbol = ' ') {
// implemented the CSS padding order
if (typeof r != 'number') {
symbol = r;
r = b = l = t;
} else if (typeof b != 'number') {
symbol = b;
l = r;
b = t;
} else if (typeof l != 'number') {
symbol = l;
l = r;
}
return this.padLeftRight(l, r, symbol).padTopBottom(t, b, symbol);
}
// removing
removeRows(y, n) {
const result = [...this.box];
result.splice(y, n);
return new Box(result, true);
}
// stack
addBottom(box, {symbol = ' ', align = 'left'} = {}) {
let a = this,
b = toBox(box);
const diff = a.width - b.width;
if (diff) {
const d = Math.abs(diff);
let x = diff < 0 ? a : b;
switch (align) {
case 'left':
case 'l':
x = x.padRight(d, symbol);
break;
case 'right':
case 'r':
x = x.padLeft(d, symbol);
break;
default: // center
const half = d >> 1;
x = x.padLeftRight(half, half + (d & 1 ? 1 : 0));
break;
}
a = diff < 0 ? x : a;
b = diff < 0 ? b : x;
}
return new Box([...a.box, ...b.box], true);
}
addRight(box, {symbol = ' ', align = 'top'} = {}) {
const ah = this.height,
bh = box.height;
if (ah == bh)
return new Box(
this.box.map((s, i) => s + box.box[i]),
true
);
let t = 0,
b = 0;
const diff = Math.abs(ah - bh);
switch (align) {
case 'top':
case 't':
t = 0;
b = diff;
break;
case 'bottom':
case 'b':
t = diff;
b = 0;
break;
default: // center
const half = diff >> 1;
t = half;
b = half + (diff & 1 ? 1 : 0);
break;
}
const result = [];
if (ah < bh) {
const padding = symbol.repeat(this.width);
for (let i = 0; i < t; ++i) {
result.push(padding + box.box[i]);
}
for (let i = 0; i < ah; ++i) {
result.push(this.box[i] + box.box[i + t]);
}
for (let i = ah + t; i < bh; ++i) {
result.push(padding + box.box[i]);
}
} else {
const padding = symbol.repeat(box.width);
for (let i = 0; i < t; ++i) {
result.push(this.box[i] + padding);
}
for (let i = 0; i < bh; ++i) {
result.push(this.box[i + t] + box.box[i]);
}
for (let i = bh + t; i < ah; ++i) {
result.push(this.box[i] + padding);
}
}
return new Box(result, true);
}
// flipping
flipV() {
// return new Box(this.box.toReversed(), true);
return new Box([...this.box].reverse(), true);
}
}
addAlias(Box, 'toBox', 'clone');
export const toBox = Box.make;
export default Box;