node-console-progress-bar-tqdm
Version:
Progress bar in console for Node.js in the style of TQDM Python library
268 lines • 8.82 kB
JavaScript
import { TqdmSyncResultIterator, TqdmAsyncResultIterator, TqdmNumericIterator, TqdmWriteStream, } from './supply.js';
import { getTermColor, getTermColorReset } from './term.js';
import { formatTimeDelta, handleUnit, hasLength, isAsyncIterable, isIterable, isIterator, pluralService, scaleUnit, } from './utils.js';
export { TqdmAsyncResultIterator, TqdmSyncResultIterator, } from './supply.js';
const defaultOptions = {
description: '',
maxColWidth: -1,
progressBraces: ['|', '|'],
progressSymbol: '█',
progressColor: '',
unit: 'it',
unitScale: false,
initial: 0,
total: -1,
step: 1,
stream: process.stderr,
minInterval: 50,
forceTerminal: false,
};
export function tqdm(input, opts = {}) {
return new Tqdm(input, opts);
}
export class Tqdm {
input;
iterator;
progress;
constructor(input, options = {}) {
this.input = input;
if (typeof this.input == 'number') {
this.iterator = new TqdmNumericIterator(this.input);
}
else if (isIterable(this.input)) {
this.iterator = this.input[Symbol.iterator]();
}
else if (isAsyncIterable(this.input)) {
this.iterator = this.input[Symbol.asyncIterator]();
}
else if (isIterator(this.input)) {
this.iterator = this.input;
}
else {
throw new Error('Unknown TQDM input type');
}
if (options.total === undefined || options.total < 0) {
if (hasLength(this.input)) {
options.total = this.input.length;
}
else if (typeof this.input == 'number') {
options.total = this.input;
}
}
this.progress = new TqdmProgress(options);
this.progress.render();
}
[Symbol.iterator]() {
return new TqdmSyncResultIterator(this);
}
[Symbol.asyncIterator]() {
return new TqdmAsyncResultIterator(this);
}
nextSync() {
const res = this.iterator.next();
if (res instanceof Promise) {
throw new Error('Async value in sync iterator');
}
if (res.done) {
this.progress.close();
}
else {
this.progress.update();
}
return res;
}
nextAsync() {
const pRes = this.iterator.next();
if (!(pRes instanceof Promise)) {
throw new Error('Sync value in async iterator');
}
return new Promise((resolve, reject) => {
pRes.then((res) => {
if (res.done) {
this.progress.close();
}
else {
this.progress.update();
}
resolve(res);
}).catch(reject);
});
}
}
export class TqdmProgress {
description;
unit;
maxColWidth;
progressLeftBrace;
progressRightBrace;
progressSymbol;
progressColor = '';
progressColorReset = '';
stream;
minInterval;
total;
totalDigits = 0;
initial;
step;
unitScale;
unitDelimiter = '';
totalScaled = '';
haveCorrectTotal;
startTime = Date.now();
lastRenderTime = 0;
timeSpent = 0;
position;
itCounter = 0;
num;
static withProgress(fn, options = {}) {
const progressBar = new TqdmProgress(options);
try {
progressBar.render();
return fn(progressBar);
}
finally {
progressBar.close();
}
}
static async withAsyncProgress(fn, options = {}) {
const progressBar = new TqdmProgress(options);
try {
progressBar.render();
return await fn(progressBar);
}
finally {
progressBar.close();
}
}
constructor(options) {
const fullOptions = {
...defaultOptions,
...options,
};
this.description = fullOptions.description;
this.unit = handleUnit(fullOptions.unit);
this.maxColWidth = fullOptions.maxColWidth;
[this.progressLeftBrace, this.progressRightBrace] = fullOptions.progressBraces;
this.progressSymbol = fullOptions.progressSymbol;
this.stream = new TqdmWriteStream(fullOptions.stream, fullOptions.forceTerminal);
this.minInterval = fullOptions.minInterval;
if (this.stream.isTty) {
this.progressColor = getTermColor(fullOptions.progressColor);
if (this.progressColor) {
this.progressColorReset = getTermColorReset();
}
}
this.initial = this.position = fullOptions.initial;
this.total = fullOptions.total;
this.haveCorrectTotal = isFinite(this.total) && !isNaN(this.total) && this.total > 0;
if (this.haveCorrectTotal) {
this.totalDigits = Math.trunc(Math.log10(this.total)) + 1;
}
this.step = fullOptions.step;
this.unitScale = fullOptions.unitScale;
if (fullOptions.unitScale) {
this.num = this.numScale;
this.unitDelimiter = ' ';
if (this.haveCorrectTotal) {
this.totalScaled = scaleUnit(this.total);
}
}
else {
this.num = this.numNoScale;
}
}
update(by = this.step) {
this.position += by;
++this.itCounter;
this.timeSpent = Date.now() - this.startTime;
this.render();
}
render(force = false) {
const now = Date.now();
if (!force && now - this.lastRenderTime < this.minInterval) {
return;
}
this.lastRenderTime = now;
const left = this.generateLeft();
const right = this.generateRight();
const progressBar = this.generateProgressBar(left.length + right.length);
this.stream.resetLine();
this.stream.write(`${left}${progressBar}${right}`);
}
close() {
this.render(true);
this.stream.finalize();
}
numScale(x) {
return scaleUnit(x);
}
numNoScale(x) {
return x.toString();
}
doesPositionFitTotal() {
return this.haveCorrectTotal && this.position <= this.total;
}
generateLeft() {
let countStr;
if (this.doesPositionFitTotal()) {
const percent = Math.round(Math.min(this.position, this.total) * 100 / this.total);
countStr = percent == -1 ? '' : `${String(percent).padStart(3, ' ')}% `;
}
else {
const unitKey = pluralService.select(this.position);
countStr = `${this.num(this.position)}${this.unitDelimiter}${this.unit[unitKey]}`;
}
const descStr = this.description ? `${this.description}: ` : '';
return ` ${descStr}${countStr}`;
}
generateRight() {
const res = [''];
if (this.doesPositionFitTotal()) {
if (this.unitScale) {
res.push(`${this.num(this.position)}/${this.totalScaled}`);
}
else {
const countStr = String(this.position).padStart(this.totalDigits, ' ');
res.push(`${countStr}/${this.total}`);
}
}
if (this.timeSpent) {
const timePerIt = this.timeSpent / this.itCounter;
const timePerItStr = (timePerIt / 1000).toFixed(3);
const timeSpentStr = formatTimeDelta(this.timeSpent, true);
let elapsedTime = '';
if (this.doesPositionFitTotal()) {
const elapsedItems = this.step > 0 ?
Math.max(0, this.total - this.position) :
Math.max(0, this.total - (this.initial - this.position));
elapsedTime = '<' + formatTimeDelta(timePerIt * elapsedItems, true);
}
res.push(`[${timeSpentStr}${elapsedTime}, ${timePerItStr}s/${this.unit['one']}]`);
}
if (res.length > 1) {
res.push('');
}
return res.join(' ');
}
generateProgressBar(reduceBy) {
if (!this.doesPositionFitTotal()) {
return '';
}
const ttyColumns = this.stream.columns;
const baseColumns = this.maxColWidth > 0 ? Math.min(this.maxColWidth, ttyColumns) : ttyColumns;
const columns = baseColumns - reduceBy - 2;
if (columns < 4) {
return '';
}
const cnt = Math.trunc(columns * Math.min(this.position, this.total) / this.total);
return [
this.progressLeftBrace,
this.progressColor,
this.progressSymbol.repeat(cnt),
' '.repeat(columns - cnt),
this.progressColorReset,
this.progressRightBrace,
].join('');
}
}
//# sourceMappingURL=index.js.map