Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

multi-progress-bars

Package Overview
Dependencies
Maintainers
1
Versions
36
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

multi-progress-bars - npm Package Compare versions

Comparing version 3.2.4 to 4.0.0-alpha.0

16

CHANGELOG.md

@@ -5,2 +5,18 @@ # Changelog

## [4.0.0-alpha.0](https://github.com/kamiyo/multi-progress-bars/compare/v3.2.4...v4.0.0-alpha.0) (2021-05-18)
### ⚠ BREAKING CHANGES
* **core:** When number of bars overflow console height, only the last N are shown. Upon closing mpb, it will dump the entire progress buffer, so everything will be visible in scrollback.
### Features
* **core:** When number of bars overflow console height, only the last N are shown. Upon closing mpb, it will dump the entire progress buffer, so everything will be visible in scrollback. ([619132a](https://github.com/kamiyo/multi-progress-bars/commit/619132a6e553330b93087ae5d9dd615e7a8d806f))
### Bug Fixes
* **core:** Fix issue [#11](https://github.com/kamiyo/multi-progress-bars/issues/11) ([5cdac93](https://github.com/kamiyo/multi-progress-bars/commit/5cdac935f16be1249ed00c43255acf14693207d7))
### [3.2.4](https://github.com/kamiyo/multi-progress-bars/compare/v3.2.3...v3.2.4) (2021-04-16)

@@ -7,0 +23,0 @@

470

dist/multi-progress-bars.cjs.js

@@ -84,2 +84,19 @@ 'use strict';

};
// Split by newlines, and then split the resulting lines if they run longer than width.
const splitLinesAndClamp = (writeString, maxWidth) => {
return writeString.split('\n').reduce((prev, curr) => {
const clamped = [];
do {
let width = curr.length;
let front = curr;
while (stringWidth__default['default'](front) > maxWidth) {
front = curr.slice(0, width);
width--;
}
curr = curr.slice(width);
clamped.push(front);
} while (curr.length > 0);
return [...prev, ...clamped];
}, []);
};

@@ -90,30 +107,13 @@ class VirtualConsole {

this.stream = options.stream;
this.resize();
this.anchor = options.anchor;
// These members are only needed for top-anchored progresses
if (this.anchor === 'top') {
this.consoleBuffer = [];
this.consoleHeight = this.height;
}
this.width = this.stream.columns;
this.height = this.stream.rows;
this.stream.on('resize', this.resize);
this.progressHeight = 0;
this.progressBuffer = [];
if (this.anchor === 'top') {
this.upsertProgress = this.upsertProgressTop;
this.writeLines = this.writeLinesTop;
this.refresh = this.refreshTop;
this.log = this.logTop;
this.done = this.cleanup;
if (!process.stdout.isTTY) {
this.log = console.log;
}
else {
this.upsertProgress = this.upsertProgressBottom;
this.writeLines = this.writeLinesBottom;
this.refresh = this.refreshBottom;
this.log = this.logBottom;
this.done = this.gotoBottom;
}
this.warn = this.log;
this.error = this.log;
console = this;
this.init();
}

@@ -126,27 +126,79 @@ checkConsoleIntercept() {

}
// height is one less than rows, because if you print to the last line, the console usually adds a newline
resize() {
// see https://github.com/kamiyo/multi-progress-bars/issues/7
const stdout = process.stdout.isTTY ? process.stdout : process.stderr;
this.width = stdout.columns;
this.height = stdout.rows - 1;
var _a;
this.width = this.stream.columns;
this.height = this.stream.rows;
(_a = this.refresh) === null || _a === void 0 ? void 0 : _a.call(this);
}
gotoBottom() {
done() {
throw new Error('Must Implement in Derived Class!');
}
refresh() {
throw new Error('Must Implement in Derived Class');
}
log(..._) {
throw new Error('Must Implement in Derived Class');
}
upsertProgress(_) {
throw new Error('Must Implement in Dervied Class');
}
init() {
var _a;
(_a = this.stream) === null || _a === void 0 ? void 0 : _a.write(CUP(this.height + 1) + '\x1b[0m');
console = this.originalConsole;
this.originalConsole = null;
const blank = '\n'.repeat(this.stream.rows) + CUP(0) + ED(ED_MODE.TO_END);
(_a = this.stream) === null || _a === void 0 ? void 0 : _a.write(blank);
}
cleanup() {
setTopBorder(border) {
this.topBorder = border;
this.progressHeight += 1;
}
setBottomBorder(border) {
this.bottomBorder = border;
this.progressHeight += 1;
}
currentHeightMinusBorders() {
return this.progressHeight - (this.topBorder === undefined ? 0 : 1) - (this.bottomBorder === undefined ? 0 : 1);
}
dumpBuffer() {
var _a;
(_a = this.stream) === null || _a === void 0 ? void 0 : _a.write('\x1b[0m');
console = this.originalConsole;
this.originalConsole = null;
const outString = ''
+ CUP(0)
+ ED(0)
+ '\x1b[0m'
+ ((this.topBorder === undefined) ? '' : (this.topBorder + '\n'))
+ this.progressBuffer
.join('\n')
+ ((this.bottomBorder === undefined) ? '' : ('\n' + this.bottomBorder));
(_a = this.stream) === null || _a === void 0 ? void 0 : _a.write(outString);
}
init() {
getBuffer() {
return this.progressBuffer;
}
}
class VirtualConsoleTop extends VirtualConsole {
constructor(options) {
var _a;
if (this.anchor === 'top') {
const blank = '\n'.repeat(this.stream.rows) + CUP(0) + ED(ED_MODE.TO_END);
(_a = this.stream) === null || _a === void 0 ? void 0 : _a.write(blank);
super(options);
this.consoleBuffer = [];
this.consoleHeight = this.height;
this.init();
(_a = this.refresh) === null || _a === void 0 ? void 0 : _a.call(this);
}
setTopBorder(border) {
super.setTopBorder(border);
this.consoleHeight -= 1;
}
setBottomBorder(border) {
super.setBottomBorder(border);
this.consoleHeight -= 1;
}
done() {
var _a;
if (this.progressBuffer.length > this.height) {
this.dumpBuffer();
}
else {
(_a = this.stream) === null || _a === void 0 ? void 0 : _a.write('\x1b[0m\n');
}
console = this.originalConsole;
this.originalConsole = null;
}

@@ -159,128 +211,154 @@ /** Add or Update Progress Entry

*/
upsertProgressTop(options) {
upsertProgress(options) {
// Reactivate console intercepting
this.checkConsoleIntercept();
// If the progress we're upserting exists already, just update.
if (options.index < this.progressHeight) {
this.progressBuffer[options.index] = clampString(options.data, this.width);
if (options.refresh === undefined || options.refresh)
this.refresh();
const numToExtend = 1 + options.index - this.progressBuffer.length;
// Truncate progress line to console width.
this.progressBuffer[options.index] = clampString(options.data, this.width);
// If we're not increasing the progress bars section, we're done.
if (numToExtend <= 0) {
return;
}
// Truncate progress line to console width.
this.progressBuffer[options.index] = clampString(options.data, this.width);
// Extend the progress bars section, and reduce the corresponding console buffer height.
const numToExtend = 1 + options.index - this.progressHeight;
this.progressHeight = Math.max(options.index + 1, this.progressHeight);
if (numToExtend > 0) {
this.consoleHeight -= numToExtend;
const topLines = this.consoleBuffer.splice(0, numToExtend);
if (topLines.length) {
this.log(...topLines);
}
else {
this.refresh();
}
this.progressHeight = Math.min(this.progressHeight + numToExtend, this.height);
this.consoleHeight = Math.max(this.consoleHeight - numToExtend, 0);
}
getOutString(bufferStartIndex, topLines) {
return [
topLines.map((val) => val + EL(EL_MODE.TO_END)).join('\n'),
((this.topBorder === undefined) ? // Top border or null
null
: (this.topBorder + EL(EL_MODE.TO_END))),
this.progressBuffer // Progress bars
.slice(bufferStartIndex)
.map((val) => val + EL(EL_MODE.TO_END))
.join('\n'),
((this.bottomBorder === undefined) ? // Bottom border or null
null
: (this.bottomBorder + EL(EL_MODE.TO_END))),
this.consoleBuffer.map((val) => val + EL(EL_MODE.TO_END)).join('\n') // Logs
].filter((v) => {
return (v !== undefined) && (v !== '') && (v !== null); // Remove falsey/empty values
}).join('\n'); // Join with newlines
}
/* Prints out the buffers as they are */
refresh() {
var _a;
// pop top of consoleBuffer if longer than consoleHeight
const topLines = (this.consoleBuffer.length > this.consoleHeight) ?
this.consoleBuffer.splice(0, this.consoleBuffer.length - this.consoleHeight) : [];
const bufferStartIndex = Math.max(this.progressBuffer.length - this.currentHeightMinusBorders(), 0);
(_a = this.stream) === null || _a === void 0 ? void 0 : _a.write(CUP(0) + this.getOutString(bufferStartIndex, topLines));
}
log(...data) {
var _a;
if (data.length !== 0) {
const writeString = util.format.apply(null, data);
const clampedLines = splitLinesAndClamp(writeString, this.width);
this.consoleBuffer.push(...clampedLines);
}
// If the console buffer is higher than console height, remove the top, and print them first.
const topLines = (this.consoleBuffer.length > this.consoleHeight) ?
this.consoleBuffer.splice(0, this.consoleBuffer.length - this.consoleHeight)
: [];
const bufferStartIndex = Math.max(this.progressBuffer.length - this.currentHeightMinusBorders(), 0);
(_a = this.stream) === null || _a === void 0 ? void 0 : _a.write(CUP(0) + this.getOutString(bufferStartIndex, topLines));
}
/** STUB
*
*/
removeProgressSlot() {
this.progressHeight = Math.max(0, this.progressHeight - 1);
// this.consoleHeight = Math.min(this.height, this.consoleHeight + 1);
// KEEP DOING
}
}
class VirtualConsoleBottom extends VirtualConsole {
constructor(options) {
super(options);
this.init();
}
init() {
var _a, _b;
super.init();
(_a = this.stream) === null || _a === void 0 ? void 0 : _a.write(CUP(this.height - 1));
(_b = this.refresh) === null || _b === void 0 ? void 0 : _b.call(this);
}
done() {
var _a;
if (this.progressBuffer.length > this.height) {
this.dumpBuffer();
}
else {
this.refresh();
(_a = this.stream) === null || _a === void 0 ? void 0 : _a.write(CUP(this.height, this.width) + '\x1b[0m\n');
}
console = this.originalConsole;
this.originalConsole = null;
}
upsertProgressBottom(options) {
/** Add or Update Progress Entry
*
* @param options
* index: number
* data: string
*/
upsertProgress(options) {
// Reactivate console intercepting
this.checkConsoleIntercept();
// If the progress we're upserting exists already, just update.
if (options.index < this.progressHeight) {
this.progressBuffer[options.index] = clampString(options.data, this.width);
if (options.refresh === undefined || options.refresh)
this.refresh();
return;
}
const numToExtend = 1 + options.index - this.progressBuffer.length;
// Truncate progress line to console width.
this.progressBuffer[options.index] = clampString(options.data, this.width);
// Extend the progress bars section.
this.progressHeight = Math.max(options.index + 1, this.progressHeight);
this.refresh();
}
updateProgress(options) {
this.checkConsoleIntercept();
this.progressBuffer[options.index] = clampString(options.data, this.width);
if (options.refresh) {
this.refresh();
// If we're not increasing the progress bars section, we're done.
if (numToExtend <= 0) {
return;
}
// Extend the progress bars section
this.progressHeight = Math.min(this.progressHeight + numToExtend, this.height);
}
writeLinesTop(...indexes) {
var _a;
let writeString = indexes.reduce((prev, index) => {
return prev += CUP(index) + this.progressBuffer[index];
}, '');
(_a = this.stream) === null || _a === void 0 ? void 0 : _a.write(writeString);
}
writeLinesBottom(...indexes) {
var _a;
const firstProgressLine = this.height - this.progressHeight;
let writeString = indexes.reduce((prev, index) => {
return prev += CUP(firstProgressLine + index) + this.progressBuffer[index];
}, '');
(_a = this.stream) === null || _a === void 0 ? void 0 : _a.write(writeString);
}
/* Prints out the buffers as they are */
refreshTop() {
refresh() {
var _a;
const outString = CUP(0)
+ this.progressBuffer.map((val) => val + EL(EL_MODE.TO_END)).join('\n')
+ (this.progressBuffer.length ? '\n' : '')
+ this.consoleBuffer.map((val) => val + EL(EL_MODE.TO_END)).join('\n')
+ '\n';
(_a = this.stream) === null || _a === void 0 ? void 0 : _a.write(outString);
}
refreshBottom() {
var _a;
const bufferStartIndex = Math.max(this.progressBuffer.length - this.currentHeightMinusBorders(), 0);
const firstProgressLine = this.height - this.progressHeight;
const outString = this.progressBuffer.map((val) => val + EL(EL_MODE.TO_END)).join('\n')
+ (this.progressBuffer.length ? '\n' : '')
+ CUP(firstProgressLine);
(_a = this.stream) === null || _a === void 0 ? void 0 : _a.write(outString);
const outString = [
((this.topBorder === undefined) ? // Top border or null
null
: (this.topBorder + EL(EL_MODE.TO_END))),
this.progressBuffer // Progress bars or []
.slice(bufferStartIndex)
.map((val) => val + EL(EL_MODE.TO_END))
.join('\n'),
((this.bottomBorder === undefined) ? // Bottom border or null
null
: (this.bottomBorder + EL(EL_MODE.TO_END))),
].filter((v) => {
return (v !== undefined) && (v !== '') && (v !== null); // Remove falsey/empty values
}).join('\n');
(_a = this.stream) === null || _a === void 0 ? void 0 : _a.write(outString + CUP(firstProgressLine));
}
logTop(...data) {
log(...data) {
var _a;
const writeString = util.format.apply(null, data);
// Split by newlines, and then split the resulting lines if they run longer than width.
const clampedLines = writeString.split('\n').reduce((prev, curr) => {
const clamped = [];
do {
let width = curr.length;
let front = curr;
while (stringWidth__default['default'](front) > this.width) {
front = curr.slice(0, width);
width--;
}
curr = curr.slice(width);
clamped.push(front);
} while (curr.length > 0);
return [...prev, ...clamped];
}, []);
this.consoleBuffer.push(...clampedLines);
// If the console buffer is higher than console height, remove the top, and print them first.
const topLines = (this.consoleBuffer.length > this.consoleHeight) ?
this.consoleBuffer.splice(0, this.consoleBuffer.length - this.consoleHeight) : [];
const outString = CUP(0)
+ topLines.map((val) => val + EL(EL_MODE.TO_END)).join('\n') // print the previously removed top lines
+ (topLines.length ? '\n' : '') // separator
+ this.progressBuffer.map((val) => val + EL(EL_MODE.TO_END)).join('\n') // progress bars
+ (this.progressBuffer.length ? '\n' : '')
+ this.consoleBuffer.map((val) => val + EL(EL_MODE.TO_END)).join('\n') // rest of the console log.
+ '\n';
(_a = this.stream) === null || _a === void 0 ? void 0 : _a.write(outString);
}
logBottom(...data) {
var _a;
const writeString = util.format.apply(null, data);
let clampedLines = [];
if (data.length !== 0) {
const writeString = util.format.apply(null, data);
// Split by newlines, and then split the resulting lines if they run longer than width.
clampedLines = splitLinesAndClamp(writeString, this.width);
}
const firstProgressLine = this.height - this.progressHeight;
const outString = this.progressBuffer.map((_) => EL(EL_MODE.ENTIRE_LINE)).join('\n')
+ CUP(firstProgressLine)
+ writeString
+ '\n'
+ this.progressBuffer.map((val) => val + EL(EL_MODE.TO_END)).join('\n')
+ (this.progressBuffer.length ? '\n' : '')
+ CUP(firstProgressLine);
(_a = this.stream) === null || _a === void 0 ? void 0 : _a.write(outString);
const bufferStartIndex = Math.max(this.progressBuffer.length - this.currentHeightMinusBorders(), 0);
const outString = [
clampedLines.map((val) => val + EL(EL_MODE.TO_END)).join('\n'),
((this.topBorder === undefined) ? // Top border or null
null
: (this.topBorder + EL(EL_MODE.TO_END))),
this.progressBuffer // Progress bars or []
.slice(bufferStartIndex)
.map((val) => val + EL(EL_MODE.TO_END))
.join('\n'),
((this.bottomBorder === undefined) ? // Bottom border or null
null
: (this.bottomBorder + EL(EL_MODE.TO_END))),
].filter((v) => {
return (v !== undefined) && (v !== '') && (v !== null); // Remove falsey/empty values
}).join('\n');
(_a = this.stream) === null || _a === void 0 ? void 0 : _a.write(outString + CUP(firstProgressLine));
}

@@ -307,2 +385,3 @@ }

this.endIdx = 0; // 1 past the last index
this.allFinished = false;
this.cleanup = () => {

@@ -318,5 +397,9 @@ var _a;

};
const { stream = process.stdout, spinnerFPS = 10, spinnerGenerator = this.hilbertSpinner, anchor = 'bottom', persist = false, border = false, } = options || {};
const {
// see https://github.com/kamiyo/multi-progress-bars/issues/7
stream = process.stdout.isTTY ? process.stdout : process.stderr, spinnerFPS = 10, spinnerGenerator = this.hilbertSpinner, anchor = 'bottom', persist = false, border = false, } = options || {};
let { progressWidth = 40, numCrawlers = 4, initMessage, } = options || {};
this.logger = new VirtualConsole({ stream, anchor });
this.logger = (anchor === 'top') ?
new VirtualConsoleTop({ stream })
: new VirtualConsoleBottom({ stream });
this.persist = persist;

@@ -326,3 +409,4 @@ this.spinnerFPS = Math.min(spinnerFPS, 60);

this.border = (typeof border === 'boolean')
? (!border) ? null : '\u2500'
? (!border)
? null : '\u2500'
: border;

@@ -363,3 +447,3 @@ if (progressWidth % 2 !== 0) {

}
if (this.border && this.logger.anchor === 'top') {
if (this.border && anchor === 'top') {
this.bottomBorder =

@@ -373,10 +457,9 @@ clampString(this.border.repeat(Math.ceil(this.logger.width / this.border.length)), this.logger.width);

process.on('SIGINT', this.cleanup);
this.logger.upsertProgress({
index: 0,
data: message,
});
this.initialLines = 1;
message && this.logger.setTopBorder(message);
(this.bottomBorder !== undefined) && this.logger.setBottomBorder(this.bottomBorder);
this.promise = new Promise((res, _) => this.resolve = res);
}
addTask(name, _a) {
var { index } = _a, options = __rest(_a, ["index"]);
this.restartPromiseIfNeeded();
// if task exists, update fields

@@ -418,12 +501,39 @@ if (this.tasks[name] !== undefined) {

this.longestNameLength = Math.max(this.longestNameLength, stringWidth__default['default'](name));
// Reset promise for end hook
this.promise = new Promise((res, _) => this.resolve = res);
// Rerender previously finished tasks so that the task names are padded correctly.
// Do this by calling done() again.
Object.entries(this.tasks).forEach(([name, { done, type, message }]) => {
if (done && type === 'indefinite') {
this.done(name, { message });
}
// Rerender other tasks so that task names are padded correctly.
Object.values(this.tasks).forEach((task) => {
this.writeTask(task);
});
this.logger.refresh();
}
// Call this BEFORE you add a new task to the list
restartPromiseIfNeeded() {
// check if allFinished previously
if (this.allFinished) {
this.allFinished = false;
this.promise = new Promise((res) => this.resolve = res);
}
}
isDone(name) {
return this.tasks[name].done;
}
// public removeTask(name: string) {
// const idxToRemove = this.tasks[name].index;
// delete this.tasks[name];
// this.longestNameLength = Object.entries(this.tasks).reduce((prev, [taskName, { index }]) => {
// // What?! Side-effects in reduce?!
// // Don't worry, we're not functional purists here.
// // Decrement all indexes after the one to remove.
// if (index > idxToRemove) {
// this.tasks[taskName].index--;
// }
// return Math.max(prev, stringWidth(taskName));
// }, 0);
// // Rerender previously finished tasks so that the task names are padded correctly.
// // Do this by calling done() again.
// Object.entries(this.tasks).forEach(([name, { done, message }]) => {
// if (done) {
// this.done(name, { message });
// }
// });
// }
progressString(task) {

@@ -467,11 +577,5 @@ const { name, barColorFn, message, percentage, } = task;

this.logger.upsertProgress({
index: task.index + this.initialLines,
index: task.index,
data: this.progressString(task),
});
if (this.bottomBorder) {
this.logger.upsertProgress({
index: Object.keys(this.tasks).length + this.initialLines,
data: this.bottomBorder,
});
}
}

@@ -493,4 +597,6 @@ incrementTask(name, _a = {}) {

updateTask(name, options = {}) {
if (this.tasks[name] === undefined)
if (this.tasks[name] === undefined) {
throw new ReferenceError('Task does not exist.');
}
this.restartPromiseIfNeeded();
const task = this.tasks[name];

@@ -507,3 +613,2 @@ // Going over 1(00%) calls done

this.intervalID = setInterval(() => this.renderIndefinite(), 1000 / this.spinnerFPS);
this.promise = new Promise((res, _) => this.resolve = res);
}

@@ -513,2 +618,3 @@ return;

this.writeTask(this.tasks[name]);
this.logger.refresh();
}

@@ -521,9 +627,6 @@ done(name, _a = {}) {

const task = this.tasks[name];
const bar = task.barColorFn(this.FULL_CHAR.repeat(this.progressWidth));
this.logger.upsertProgress({
index: task.index + this.initialLines,
data: name.padStart(this.longestNameLength) + ': ' + bar + ' ' + message,
});
this.writeTask(task);
this.logger.refresh();
// Stop animation if all tasks are done, and resolve the promise.
if (Object.entries(this.tasks).reduce((prev, [_, curr]) => {
if (Object.values(this.tasks).reduce((prev, curr) => {
return prev && curr.done;

@@ -533,2 +636,3 @@ }, true)) {

this.intervalID = null;
this.allFinished = true;
this.resolve();

@@ -541,4 +645,6 @@ if (!this.persist) {

restart(name, options) {
if (this.tasks[name] === undefined)
this.restartPromiseIfNeeded();
if (this.tasks[name] === undefined) {
throw new ReferenceError('Task does not exist.');
}
this.tasks[name] = Object.assign(Object.assign(Object.assign({}, this.tasks[name]), options), { percentage: 0, done: false });

@@ -552,7 +658,8 @@ if (this.tasks[name].type === 'indefinite' && !this.intervalID) {

this.writeTask(this.tasks[name]);
this.logger.refresh();
}
this.promise = new Promise((res, _) => this.resolve = res);
}
close() {
this.logger.done();
var _a;
(_a = this.logger) === null || _a === void 0 ? void 0 : _a.done();
}

@@ -602,5 +709,4 @@ // Returns the index of task with supplied name. Returns undefined if name not found.

this.logger.upsertProgress({
index: task.index + this.initialLines,
index: task.index,
data: progressString,
refresh: false,
});

@@ -607,0 +713,0 @@ }

@@ -57,3 +57,2 @@ /// <reference types="node" />

private spinnerFPS;
private initialLines;
private progressWidth;

@@ -76,2 +75,3 @@ private CHARS;

private endIdx;
private allFinished;
promise: Promise<void>;

@@ -86,2 +86,4 @@ /**

addTask(name: string, { index, ...options }: AddOptions): void;
private restartPromiseIfNeeded;
isDone(name: string): boolean;
private progressString;

@@ -88,0 +90,0 @@ private indefiniteString;

@@ -76,2 +76,19 @@ import { green } from 'chalk';

};
// Split by newlines, and then split the resulting lines if they run longer than width.
const splitLinesAndClamp = (writeString, maxWidth) => {
return writeString.split('\n').reduce((prev, curr) => {
const clamped = [];
do {
let width = curr.length;
let front = curr;
while (stringWidth(front) > maxWidth) {
front = curr.slice(0, width);
width--;
}
curr = curr.slice(width);
clamped.push(front);
} while (curr.length > 0);
return [...prev, ...clamped];
}, []);
};

@@ -82,30 +99,13 @@ class VirtualConsole {

this.stream = options.stream;
this.resize();
this.anchor = options.anchor;
// These members are only needed for top-anchored progresses
if (this.anchor === 'top') {
this.consoleBuffer = [];
this.consoleHeight = this.height;
}
this.width = this.stream.columns;
this.height = this.stream.rows;
this.stream.on('resize', this.resize);
this.progressHeight = 0;
this.progressBuffer = [];
if (this.anchor === 'top') {
this.upsertProgress = this.upsertProgressTop;
this.writeLines = this.writeLinesTop;
this.refresh = this.refreshTop;
this.log = this.logTop;
this.done = this.cleanup;
if (!process.stdout.isTTY) {
this.log = console.log;
}
else {
this.upsertProgress = this.upsertProgressBottom;
this.writeLines = this.writeLinesBottom;
this.refresh = this.refreshBottom;
this.log = this.logBottom;
this.done = this.gotoBottom;
}
this.warn = this.log;
this.error = this.log;
console = this;
this.init();
}

@@ -118,27 +118,79 @@ checkConsoleIntercept() {

}
// height is one less than rows, because if you print to the last line, the console usually adds a newline
resize() {
// see https://github.com/kamiyo/multi-progress-bars/issues/7
const stdout = process.stdout.isTTY ? process.stdout : process.stderr;
this.width = stdout.columns;
this.height = stdout.rows - 1;
var _a;
this.width = this.stream.columns;
this.height = this.stream.rows;
(_a = this.refresh) === null || _a === void 0 ? void 0 : _a.call(this);
}
gotoBottom() {
done() {
throw new Error('Must Implement in Derived Class!');
}
refresh() {
throw new Error('Must Implement in Derived Class');
}
log(..._) {
throw new Error('Must Implement in Derived Class');
}
upsertProgress(_) {
throw new Error('Must Implement in Dervied Class');
}
init() {
var _a;
(_a = this.stream) === null || _a === void 0 ? void 0 : _a.write(CUP(this.height + 1) + '\x1b[0m');
console = this.originalConsole;
this.originalConsole = null;
const blank = '\n'.repeat(this.stream.rows) + CUP(0) + ED(ED_MODE.TO_END);
(_a = this.stream) === null || _a === void 0 ? void 0 : _a.write(blank);
}
cleanup() {
setTopBorder(border) {
this.topBorder = border;
this.progressHeight += 1;
}
setBottomBorder(border) {
this.bottomBorder = border;
this.progressHeight += 1;
}
currentHeightMinusBorders() {
return this.progressHeight - (this.topBorder === undefined ? 0 : 1) - (this.bottomBorder === undefined ? 0 : 1);
}
dumpBuffer() {
var _a;
(_a = this.stream) === null || _a === void 0 ? void 0 : _a.write('\x1b[0m');
console = this.originalConsole;
this.originalConsole = null;
const outString = ''
+ CUP(0)
+ ED(0)
+ '\x1b[0m'
+ ((this.topBorder === undefined) ? '' : (this.topBorder + '\n'))
+ this.progressBuffer
.join('\n')
+ ((this.bottomBorder === undefined) ? '' : ('\n' + this.bottomBorder));
(_a = this.stream) === null || _a === void 0 ? void 0 : _a.write(outString);
}
init() {
getBuffer() {
return this.progressBuffer;
}
}
class VirtualConsoleTop extends VirtualConsole {
constructor(options) {
var _a;
if (this.anchor === 'top') {
const blank = '\n'.repeat(this.stream.rows) + CUP(0) + ED(ED_MODE.TO_END);
(_a = this.stream) === null || _a === void 0 ? void 0 : _a.write(blank);
super(options);
this.consoleBuffer = [];
this.consoleHeight = this.height;
this.init();
(_a = this.refresh) === null || _a === void 0 ? void 0 : _a.call(this);
}
setTopBorder(border) {
super.setTopBorder(border);
this.consoleHeight -= 1;
}
setBottomBorder(border) {
super.setBottomBorder(border);
this.consoleHeight -= 1;
}
done() {
var _a;
if (this.progressBuffer.length > this.height) {
this.dumpBuffer();
}
else {
(_a = this.stream) === null || _a === void 0 ? void 0 : _a.write('\x1b[0m\n');
}
console = this.originalConsole;
this.originalConsole = null;
}

@@ -151,128 +203,154 @@ /** Add or Update Progress Entry

*/
upsertProgressTop(options) {
upsertProgress(options) {
// Reactivate console intercepting
this.checkConsoleIntercept();
// If the progress we're upserting exists already, just update.
if (options.index < this.progressHeight) {
this.progressBuffer[options.index] = clampString(options.data, this.width);
if (options.refresh === undefined || options.refresh)
this.refresh();
const numToExtend = 1 + options.index - this.progressBuffer.length;
// Truncate progress line to console width.
this.progressBuffer[options.index] = clampString(options.data, this.width);
// If we're not increasing the progress bars section, we're done.
if (numToExtend <= 0) {
return;
}
// Truncate progress line to console width.
this.progressBuffer[options.index] = clampString(options.data, this.width);
// Extend the progress bars section, and reduce the corresponding console buffer height.
const numToExtend = 1 + options.index - this.progressHeight;
this.progressHeight = Math.max(options.index + 1, this.progressHeight);
if (numToExtend > 0) {
this.consoleHeight -= numToExtend;
const topLines = this.consoleBuffer.splice(0, numToExtend);
if (topLines.length) {
this.log(...topLines);
}
else {
this.refresh();
}
this.progressHeight = Math.min(this.progressHeight + numToExtend, this.height);
this.consoleHeight = Math.max(this.consoleHeight - numToExtend, 0);
}
getOutString(bufferStartIndex, topLines) {
return [
topLines.map((val) => val + EL(EL_MODE.TO_END)).join('\n'),
((this.topBorder === undefined) ? // Top border or null
null
: (this.topBorder + EL(EL_MODE.TO_END))),
this.progressBuffer // Progress bars
.slice(bufferStartIndex)
.map((val) => val + EL(EL_MODE.TO_END))
.join('\n'),
((this.bottomBorder === undefined) ? // Bottom border or null
null
: (this.bottomBorder + EL(EL_MODE.TO_END))),
this.consoleBuffer.map((val) => val + EL(EL_MODE.TO_END)).join('\n') // Logs
].filter((v) => {
return (v !== undefined) && (v !== '') && (v !== null); // Remove falsey/empty values
}).join('\n'); // Join with newlines
}
/* Prints out the buffers as they are */
refresh() {
var _a;
// pop top of consoleBuffer if longer than consoleHeight
const topLines = (this.consoleBuffer.length > this.consoleHeight) ?
this.consoleBuffer.splice(0, this.consoleBuffer.length - this.consoleHeight) : [];
const bufferStartIndex = Math.max(this.progressBuffer.length - this.currentHeightMinusBorders(), 0);
(_a = this.stream) === null || _a === void 0 ? void 0 : _a.write(CUP(0) + this.getOutString(bufferStartIndex, topLines));
}
log(...data) {
var _a;
if (data.length !== 0) {
const writeString = format.apply(null, data);
const clampedLines = splitLinesAndClamp(writeString, this.width);
this.consoleBuffer.push(...clampedLines);
}
// If the console buffer is higher than console height, remove the top, and print them first.
const topLines = (this.consoleBuffer.length > this.consoleHeight) ?
this.consoleBuffer.splice(0, this.consoleBuffer.length - this.consoleHeight)
: [];
const bufferStartIndex = Math.max(this.progressBuffer.length - this.currentHeightMinusBorders(), 0);
(_a = this.stream) === null || _a === void 0 ? void 0 : _a.write(CUP(0) + this.getOutString(bufferStartIndex, topLines));
}
/** STUB
*
*/
removeProgressSlot() {
this.progressHeight = Math.max(0, this.progressHeight - 1);
// this.consoleHeight = Math.min(this.height, this.consoleHeight + 1);
// KEEP DOING
}
}
class VirtualConsoleBottom extends VirtualConsole {
constructor(options) {
super(options);
this.init();
}
init() {
var _a, _b;
super.init();
(_a = this.stream) === null || _a === void 0 ? void 0 : _a.write(CUP(this.height - 1));
(_b = this.refresh) === null || _b === void 0 ? void 0 : _b.call(this);
}
done() {
var _a;
if (this.progressBuffer.length > this.height) {
this.dumpBuffer();
}
else {
this.refresh();
(_a = this.stream) === null || _a === void 0 ? void 0 : _a.write(CUP(this.height, this.width) + '\x1b[0m\n');
}
console = this.originalConsole;
this.originalConsole = null;
}
upsertProgressBottom(options) {
/** Add or Update Progress Entry
*
* @param options
* index: number
* data: string
*/
upsertProgress(options) {
// Reactivate console intercepting
this.checkConsoleIntercept();
// If the progress we're upserting exists already, just update.
if (options.index < this.progressHeight) {
this.progressBuffer[options.index] = clampString(options.data, this.width);
if (options.refresh === undefined || options.refresh)
this.refresh();
return;
}
const numToExtend = 1 + options.index - this.progressBuffer.length;
// Truncate progress line to console width.
this.progressBuffer[options.index] = clampString(options.data, this.width);
// Extend the progress bars section.
this.progressHeight = Math.max(options.index + 1, this.progressHeight);
this.refresh();
}
updateProgress(options) {
this.checkConsoleIntercept();
this.progressBuffer[options.index] = clampString(options.data, this.width);
if (options.refresh) {
this.refresh();
// If we're not increasing the progress bars section, we're done.
if (numToExtend <= 0) {
return;
}
// Extend the progress bars section
this.progressHeight = Math.min(this.progressHeight + numToExtend, this.height);
}
writeLinesTop(...indexes) {
var _a;
let writeString = indexes.reduce((prev, index) => {
return prev += CUP(index) + this.progressBuffer[index];
}, '');
(_a = this.stream) === null || _a === void 0 ? void 0 : _a.write(writeString);
}
writeLinesBottom(...indexes) {
var _a;
const firstProgressLine = this.height - this.progressHeight;
let writeString = indexes.reduce((prev, index) => {
return prev += CUP(firstProgressLine + index) + this.progressBuffer[index];
}, '');
(_a = this.stream) === null || _a === void 0 ? void 0 : _a.write(writeString);
}
/* Prints out the buffers as they are */
refreshTop() {
refresh() {
var _a;
const outString = CUP(0)
+ this.progressBuffer.map((val) => val + EL(EL_MODE.TO_END)).join('\n')
+ (this.progressBuffer.length ? '\n' : '')
+ this.consoleBuffer.map((val) => val + EL(EL_MODE.TO_END)).join('\n')
+ '\n';
(_a = this.stream) === null || _a === void 0 ? void 0 : _a.write(outString);
}
refreshBottom() {
var _a;
const bufferStartIndex = Math.max(this.progressBuffer.length - this.currentHeightMinusBorders(), 0);
const firstProgressLine = this.height - this.progressHeight;
const outString = this.progressBuffer.map((val) => val + EL(EL_MODE.TO_END)).join('\n')
+ (this.progressBuffer.length ? '\n' : '')
+ CUP(firstProgressLine);
(_a = this.stream) === null || _a === void 0 ? void 0 : _a.write(outString);
const outString = [
((this.topBorder === undefined) ? // Top border or null
null
: (this.topBorder + EL(EL_MODE.TO_END))),
this.progressBuffer // Progress bars or []
.slice(bufferStartIndex)
.map((val) => val + EL(EL_MODE.TO_END))
.join('\n'),
((this.bottomBorder === undefined) ? // Bottom border or null
null
: (this.bottomBorder + EL(EL_MODE.TO_END))),
].filter((v) => {
return (v !== undefined) && (v !== '') && (v !== null); // Remove falsey/empty values
}).join('\n');
(_a = this.stream) === null || _a === void 0 ? void 0 : _a.write(outString + CUP(firstProgressLine));
}
logTop(...data) {
log(...data) {
var _a;
const writeString = format.apply(null, data);
// Split by newlines, and then split the resulting lines if they run longer than width.
const clampedLines = writeString.split('\n').reduce((prev, curr) => {
const clamped = [];
do {
let width = curr.length;
let front = curr;
while (stringWidth(front) > this.width) {
front = curr.slice(0, width);
width--;
}
curr = curr.slice(width);
clamped.push(front);
} while (curr.length > 0);
return [...prev, ...clamped];
}, []);
this.consoleBuffer.push(...clampedLines);
// If the console buffer is higher than console height, remove the top, and print them first.
const topLines = (this.consoleBuffer.length > this.consoleHeight) ?
this.consoleBuffer.splice(0, this.consoleBuffer.length - this.consoleHeight) : [];
const outString = CUP(0)
+ topLines.map((val) => val + EL(EL_MODE.TO_END)).join('\n') // print the previously removed top lines
+ (topLines.length ? '\n' : '') // separator
+ this.progressBuffer.map((val) => val + EL(EL_MODE.TO_END)).join('\n') // progress bars
+ (this.progressBuffer.length ? '\n' : '')
+ this.consoleBuffer.map((val) => val + EL(EL_MODE.TO_END)).join('\n') // rest of the console log.
+ '\n';
(_a = this.stream) === null || _a === void 0 ? void 0 : _a.write(outString);
}
logBottom(...data) {
var _a;
const writeString = format.apply(null, data);
let clampedLines = [];
if (data.length !== 0) {
const writeString = format.apply(null, data);
// Split by newlines, and then split the resulting lines if they run longer than width.
clampedLines = splitLinesAndClamp(writeString, this.width);
}
const firstProgressLine = this.height - this.progressHeight;
const outString = this.progressBuffer.map((_) => EL(EL_MODE.ENTIRE_LINE)).join('\n')
+ CUP(firstProgressLine)
+ writeString
+ '\n'
+ this.progressBuffer.map((val) => val + EL(EL_MODE.TO_END)).join('\n')
+ (this.progressBuffer.length ? '\n' : '')
+ CUP(firstProgressLine);
(_a = this.stream) === null || _a === void 0 ? void 0 : _a.write(outString);
const bufferStartIndex = Math.max(this.progressBuffer.length - this.currentHeightMinusBorders(), 0);
const outString = [
clampedLines.map((val) => val + EL(EL_MODE.TO_END)).join('\n'),
((this.topBorder === undefined) ? // Top border or null
null
: (this.topBorder + EL(EL_MODE.TO_END))),
this.progressBuffer // Progress bars or []
.slice(bufferStartIndex)
.map((val) => val + EL(EL_MODE.TO_END))
.join('\n'),
((this.bottomBorder === undefined) ? // Bottom border or null
null
: (this.bottomBorder + EL(EL_MODE.TO_END))),
].filter((v) => {
return (v !== undefined) && (v !== '') && (v !== null); // Remove falsey/empty values
}).join('\n');
(_a = this.stream) === null || _a === void 0 ? void 0 : _a.write(outString + CUP(firstProgressLine));
}

@@ -299,2 +377,3 @@ }

this.endIdx = 0; // 1 past the last index
this.allFinished = false;
this.cleanup = () => {

@@ -310,5 +389,9 @@ var _a;

};
const { stream = process.stdout, spinnerFPS = 10, spinnerGenerator = this.hilbertSpinner, anchor = 'bottom', persist = false, border = false, } = options || {};
const {
// see https://github.com/kamiyo/multi-progress-bars/issues/7
stream = process.stdout.isTTY ? process.stdout : process.stderr, spinnerFPS = 10, spinnerGenerator = this.hilbertSpinner, anchor = 'bottom', persist = false, border = false, } = options || {};
let { progressWidth = 40, numCrawlers = 4, initMessage, } = options || {};
this.logger = new VirtualConsole({ stream, anchor });
this.logger = (anchor === 'top') ?
new VirtualConsoleTop({ stream })
: new VirtualConsoleBottom({ stream });
this.persist = persist;

@@ -318,3 +401,4 @@ this.spinnerFPS = Math.min(spinnerFPS, 60);

this.border = (typeof border === 'boolean')
? (!border) ? null : '\u2500'
? (!border)
? null : '\u2500'
: border;

@@ -355,3 +439,3 @@ if (progressWidth % 2 !== 0) {

}
if (this.border && this.logger.anchor === 'top') {
if (this.border && anchor === 'top') {
this.bottomBorder =

@@ -365,10 +449,9 @@ clampString(this.border.repeat(Math.ceil(this.logger.width / this.border.length)), this.logger.width);

process.on('SIGINT', this.cleanup);
this.logger.upsertProgress({
index: 0,
data: message,
});
this.initialLines = 1;
message && this.logger.setTopBorder(message);
(this.bottomBorder !== undefined) && this.logger.setBottomBorder(this.bottomBorder);
this.promise = new Promise((res, _) => this.resolve = res);
}
addTask(name, _a) {
var { index } = _a, options = __rest(_a, ["index"]);
this.restartPromiseIfNeeded();
// if task exists, update fields

@@ -410,12 +493,39 @@ if (this.tasks[name] !== undefined) {

this.longestNameLength = Math.max(this.longestNameLength, stringWidth(name));
// Reset promise for end hook
this.promise = new Promise((res, _) => this.resolve = res);
// Rerender previously finished tasks so that the task names are padded correctly.
// Do this by calling done() again.
Object.entries(this.tasks).forEach(([name, { done, type, message }]) => {
if (done && type === 'indefinite') {
this.done(name, { message });
}
// Rerender other tasks so that task names are padded correctly.
Object.values(this.tasks).forEach((task) => {
this.writeTask(task);
});
this.logger.refresh();
}
// Call this BEFORE you add a new task to the list
restartPromiseIfNeeded() {
// check if allFinished previously
if (this.allFinished) {
this.allFinished = false;
this.promise = new Promise((res) => this.resolve = res);
}
}
isDone(name) {
return this.tasks[name].done;
}
// public removeTask(name: string) {
// const idxToRemove = this.tasks[name].index;
// delete this.tasks[name];
// this.longestNameLength = Object.entries(this.tasks).reduce((prev, [taskName, { index }]) => {
// // What?! Side-effects in reduce?!
// // Don't worry, we're not functional purists here.
// // Decrement all indexes after the one to remove.
// if (index > idxToRemove) {
// this.tasks[taskName].index--;
// }
// return Math.max(prev, stringWidth(taskName));
// }, 0);
// // Rerender previously finished tasks so that the task names are padded correctly.
// // Do this by calling done() again.
// Object.entries(this.tasks).forEach(([name, { done, message }]) => {
// if (done) {
// this.done(name, { message });
// }
// });
// }
progressString(task) {

@@ -459,11 +569,5 @@ const { name, barColorFn, message, percentage, } = task;

this.logger.upsertProgress({
index: task.index + this.initialLines,
index: task.index,
data: this.progressString(task),
});
if (this.bottomBorder) {
this.logger.upsertProgress({
index: Object.keys(this.tasks).length + this.initialLines,
data: this.bottomBorder,
});
}
}

@@ -485,4 +589,6 @@ incrementTask(name, _a = {}) {

updateTask(name, options = {}) {
if (this.tasks[name] === undefined)
if (this.tasks[name] === undefined) {
throw new ReferenceError('Task does not exist.');
}
this.restartPromiseIfNeeded();
const task = this.tasks[name];

@@ -499,3 +605,2 @@ // Going over 1(00%) calls done

this.intervalID = setInterval(() => this.renderIndefinite(), 1000 / this.spinnerFPS);
this.promise = new Promise((res, _) => this.resolve = res);
}

@@ -505,2 +610,3 @@ return;

this.writeTask(this.tasks[name]);
this.logger.refresh();
}

@@ -513,9 +619,6 @@ done(name, _a = {}) {

const task = this.tasks[name];
const bar = task.barColorFn(this.FULL_CHAR.repeat(this.progressWidth));
this.logger.upsertProgress({
index: task.index + this.initialLines,
data: name.padStart(this.longestNameLength) + ': ' + bar + ' ' + message,
});
this.writeTask(task);
this.logger.refresh();
// Stop animation if all tasks are done, and resolve the promise.
if (Object.entries(this.tasks).reduce((prev, [_, curr]) => {
if (Object.values(this.tasks).reduce((prev, curr) => {
return prev && curr.done;

@@ -525,2 +628,3 @@ }, true)) {

this.intervalID = null;
this.allFinished = true;
this.resolve();

@@ -533,4 +637,6 @@ if (!this.persist) {

restart(name, options) {
if (this.tasks[name] === undefined)
this.restartPromiseIfNeeded();
if (this.tasks[name] === undefined) {
throw new ReferenceError('Task does not exist.');
}
this.tasks[name] = Object.assign(Object.assign(Object.assign({}, this.tasks[name]), options), { percentage: 0, done: false });

@@ -544,7 +650,8 @@ if (this.tasks[name].type === 'indefinite' && !this.intervalID) {

this.writeTask(this.tasks[name]);
this.logger.refresh();
}
this.promise = new Promise((res, _) => this.resolve = res);
}
close() {
this.logger.done();
var _a;
(_a = this.logger) === null || _a === void 0 ? void 0 : _a.done();
}

@@ -594,5 +701,4 @@ // Returns the index of task with supplied name. Returns undefined if name not found.

this.logger.upsertProgress({
index: task.index + this.initialLines,
index: task.index,
data: progressString,
refresh: false,
});

@@ -599,0 +705,0 @@ }

@@ -53,1 +53,2 @@ export declare const RESET: string;

export declare const clampString: (message: string, width: number) => string;
export declare const splitLinesAndClamp: (writeString: string, maxWidth: number) => string[];

@@ -5,3 +5,2 @@ /// <reference types="node" />

stream: WriteStream;
anchor: 'top' | 'bottom';
}

@@ -16,24 +15,36 @@ export interface AddProgressOptions {

export declare class VirtualConsole {
private progressBuffer;
private consoleBuffer;
private height;
private progressHeight;
private consoleHeight;
private originalConsole;
private stream;
protected progressBuffer: string[];
protected height: number;
/** Progress section height, will not exceed total terminal height
* As opposed to progressBuffer.length, which is unbounded
*/
protected progressHeight: number;
protected topBorder: string;
protected bottomBorder: string;
protected originalConsole: Console;
protected stream: WriteStream;
width: number;
anchor: 'top' | 'bottom';
done: () => void;
warn: Console['warn'];
error: Console['error'];
upsertProgress: (options: UpsertProgressOptions) => void;
writeLines: (...indexes: number[]) => void;
refresh: () => void;
log: (...data: any[]) => void;
constructor(options: VirtualConsoleCtorOptions);
checkConsoleIntercept(): void;
resize(): void;
gotoBottom(): void;
cleanup(): void;
done(): void;
refresh(): void;
log(..._: any[]): void;
upsertProgress(_: AddProgressOptions): void;
init(): void;
setTopBorder(border: string): void;
setBottomBorder(border: string): void;
protected currentHeightMinusBorders(): number;
dumpBuffer(): void;
getBuffer(): string[];
}
export declare class VirtualConsoleTop extends VirtualConsole {
private consoleBuffer;
private consoleHeight;
constructor(options: VirtualConsoleCtorOptions);
setTopBorder(border: string): void;
setBottomBorder(border: string): void;
done(): void;
/** Add or Update Progress Entry

@@ -45,11 +56,24 @@ *

*/
upsertProgressTop(options: UpsertProgressOptions): void;
upsertProgressBottom(options: UpsertProgressOptions): void;
updateProgress(options: UpsertProgressOptions): void;
writeLinesTop(...indexes: number[]): void;
writeLinesBottom(...indexes: number[]): void;
refreshTop(): void;
refreshBottom(): void;
logTop(...data: any[]): void;
logBottom(...data: any[]): void;
upsertProgress(options: AddProgressOptions): void;
getOutString(bufferStartIndex: number, topLines: string[]): string;
refresh(): void;
log(...data: any[]): void;
/** STUB
*
*/
removeProgressSlot(): void;
}
export declare class VirtualConsoleBottom extends VirtualConsole {
constructor(options: VirtualConsoleCtorOptions);
init(): void;
done(): void;
/** Add or Update Progress Entry
*
* @param options
* index: number
* data: string
*/
upsertProgress(options: AddProgressOptions): void;
refresh(): void;
log(...data: any[]): void;
}
{
"name": "multi-progress-bars",
"version": "3.2.4",
"version": "4.0.0-alpha.0",
"description": "Multiple progress bars with option for indefinite spinners",

@@ -5,0 +5,0 @@ "main": "dist/multi-progress-bars.cjs.js",

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc