UNPKG

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
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