multi-progress-bars
Advanced tools
Comparing version 2.0.7 to 3.1.0
@@ -5,2 +5,25 @@ # Changelog | ||
## [3.1.0](https://github.com/kamiyo/multi-progress-bars/compare/v3.0.0...v3.1.0) (2020-12-31) | ||
### Features | ||
* **borders:** Added border functionality. ([c6f49f6](https://github.com/kamiyo/multi-progress-bars/commit/c6f49f610d576e8edee9d28fb28b443d35603c31)) | ||
### Bug Fixes | ||
* **README:** Updated README with v3 info. ([f6675fd](https://github.com/kamiyo/multi-progress-bars/commit/f6675fdafc903d628c5ba5546d3eef827338f081)) | ||
## [3.0.0](https://github.com/kamiyo/multi-progress-bars/compare/v2.0.7...v3.0.0) (2020-12-31) | ||
### ⚠ BREAKING CHANGES | ||
* Though not technically breaking (defaults behave as previously), added the option for bottom positioning of progress bars. As a result, the logic is easier. Pass 'bottom' to options.anchor in the constructor to switch. There is also now a options.persist setting. If set to false or left blank, the virtual console will reconnect the original console after all tasks are finished. However, if you call updateTask or similar, it will re-intercept console. If persist is true, it will not reconnect original; intended for use with a perpetual watcher. | ||
### Features | ||
* Option for bottom positioning of progress bars. ([d9379c4](https://github.com/kamiyo/multi-progress-bars/commit/d9379c4abbb15309809802027351a5db6dac3528)) | ||
### [2.0.7](https://github.com/kamiyo/multi-progress-bars/compare/v2.0.6...v2.0.7) (2020-12-31) | ||
@@ -7,0 +30,0 @@ |
@@ -89,13 +89,48 @@ 'use strict'; | ||
this.stream = options.stream; | ||
this.width = process.stdout.columns; | ||
this.height = process.stdout.rows - 1; | ||
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.stream.on('resize', this.resize); | ||
this.progressHeight = 0; | ||
this.consoleHeight = this.height; | ||
this.progressBuffer = []; | ||
this.consoleBuffer = []; | ||
this.warn = this.log; | ||
this.error = this.log; | ||
console = this; | ||
if (this.anchor === 'top') { | ||
this.upsertProgress = this.upsertProgressTop; | ||
this.writeLines = this.writeLinesTop; | ||
this.refresh = this.refreshTop; | ||
this.log = this.logTop; | ||
this.done = this.cleanup; | ||
} | ||
else { | ||
this.upsertProgress = this.upsertProgressBottom; | ||
this.writeLines = this.writeLinesBottom; | ||
this.refresh = this.refreshBottom; | ||
this.log = this.logBottom; | ||
this.done = this.gotoBottom; | ||
} | ||
this.init(); | ||
} | ||
checkConsoleIntercept() { | ||
if (!this.originalConsole) { | ||
this.originalConsole = console; | ||
console = this; | ||
} | ||
} | ||
// height is one less than rows, because if you print to the last line, the console usually adds a newline | ||
resize() { | ||
this.width = process.stdout.columns; | ||
this.height = process.stdout.rows - 1; | ||
} | ||
gotoBottom() { | ||
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; | ||
} | ||
cleanup() { | ||
@@ -105,8 +140,10 @@ var _a; | ||
console = this.originalConsole; | ||
this.originalConsole = null; | ||
} | ||
init() { | ||
var _a; | ||
process.on('SIGINT', this.cleanup); | ||
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); | ||
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); | ||
} | ||
} | ||
@@ -119,3 +156,4 @@ /** Add or Update Progress Entry | ||
*/ | ||
upsertProgress(options) { | ||
upsertProgressTop(options) { | ||
this.checkConsoleIntercept(); | ||
// If the progress we're upserting exists already, just update. | ||
@@ -147,3 +185,19 @@ if (options.index < this.progressHeight) { | ||
} | ||
upsertProgressBottom(options) { | ||
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; | ||
} | ||
// 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); | ||
@@ -154,3 +208,3 @@ if (options.refresh) { | ||
} | ||
writeLines(...indexes) { | ||
writeLinesTop(...indexes) { | ||
var _a; | ||
@@ -162,4 +216,12 @@ let writeString = indexes.reduce((prev, index) => { | ||
} | ||
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 */ | ||
refresh() { | ||
refreshTop() { | ||
var _a; | ||
@@ -173,4 +235,12 @@ const outString = CUP(0) | ||
} | ||
log(...data) { | ||
refreshBottom() { | ||
var _a; | ||
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); | ||
} | ||
logTop(...data) { | ||
var _a; | ||
const writeString = util.format.apply(null, data); | ||
@@ -181,4 +251,9 @@ // Split by newlines, and then split the resulting lines if they run longer than width. | ||
do { | ||
const front = curr.slice(0, this.width); | ||
curr = curr.slice(this.width); | ||
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); | ||
@@ -201,2 +276,15 @@ } while (curr.length > 0); | ||
} | ||
logBottom(...data) { | ||
var _a; | ||
const writeString = util.format.apply(null, data); | ||
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); | ||
} | ||
} | ||
@@ -219,13 +307,7 @@ | ||
this.FULL_CHAR = this.CHARS[this.CHARS.length - 1]; | ||
this.endLine = 0; | ||
this.longestNameLength = 0; | ||
this.t = 0; | ||
this.logger = new VirtualConsole({ stream: process.stdout }); | ||
this.resizeConsole = () => { | ||
this.consoleSize = { | ||
width: this.stream.columns, | ||
height: this.stream.rows, | ||
}; | ||
}; | ||
this.cleanup = () => { | ||
var _a; | ||
(_a = this.logger) === null || _a === void 0 ? void 0 : _a.done(); | ||
if (this.intervalID) { | ||
@@ -235,10 +317,11 @@ clearInterval(this.intervalID); | ||
}; | ||
const { stream = process.stdout, spinnerFPS = 10, spinnerGenerator = this.hilbertSpinner, } = options || {}; | ||
const { stream = process.stdout, spinnerFPS = 10, spinnerGenerator = this.hilbertSpinner, anchor = 'bottom', persist = false, border = false, } = options || {}; | ||
let { progressWidth = 40, numCrawlers = 4, initMessage, } = options || {}; | ||
this.stream = stream; | ||
this.stream.on('resize', () => { | ||
this.resizeConsole(); | ||
}); | ||
this.logger = new VirtualConsole({ stream, anchor }); | ||
this.persist = persist; | ||
this.spinnerFPS = Math.min(spinnerFPS, 60); | ||
this.spinnerGenerator = spinnerGenerator; | ||
this.border = (typeof border === 'boolean') | ||
? (!border) ? null : '\u2500' | ||
: border; | ||
if (progressWidth % 2 !== 0) { | ||
@@ -257,3 +340,2 @@ progressWidth += 1; | ||
this.progressWidth = progressWidth; | ||
this.resizeConsole(); | ||
if (initMessage === undefined) { | ||
@@ -264,2 +346,22 @@ initMessage = '$ ' + process.argv.map((arg) => { | ||
} | ||
else { | ||
initMessage = initMessage.split('\n')[0]; | ||
} | ||
if (this.border) { | ||
initMessage = clampString(initMessage, this.logger.width - 10); | ||
const startRepeat = (this.border.length > 4) ? 1 : Math.floor(4 / this.border.length); | ||
initMessage = this.border.repeat(startRepeat) + ' ' + initMessage + ' '; | ||
const currentCount = stringWidth__default['default'](initMessage); | ||
const remaining = this.logger.width - currentCount; | ||
const endRepeat = Math.ceil(remaining / this.border.length); | ||
initMessage += this.border.repeat(endRepeat); | ||
initMessage = clampString(initMessage, this.logger.width); | ||
} | ||
else { | ||
initMessage = clampString(initMessage, this.logger.width); | ||
} | ||
if (this.border && this.logger.anchor === 'top') { | ||
this.bottomBorder = | ||
clampString(this.border.repeat(Math.ceil(this.logger.width / this.border.length)), this.logger.width); | ||
} | ||
this.init(initMessage); | ||
@@ -270,11 +372,7 @@ } | ||
process.on('SIGINT', this.cleanup); | ||
// TODO make this account for lines that wrap | ||
const splitMessage = message.split('\n'); | ||
splitMessage.forEach((msg, idx) => { | ||
this.logger.upsertProgress({ | ||
index: idx, | ||
data: msg, | ||
}); | ||
this.logger.upsertProgress({ | ||
index: 0, | ||
data: message, | ||
}); | ||
this.initialLines = splitMessage.length; | ||
this.initialLines = 1; | ||
} | ||
@@ -291,6 +389,2 @@ addTask(name, _a) { | ||
const { type, barColorFn = defaultTransformFn, percentage = 0, message = '', } = options; | ||
this.endLine = Math.max.apply(null, [ | ||
index, | ||
...Object.entries(this.tasks).map(([_, task]) => task.index) | ||
]) + 1; | ||
this.tasks[name] = { | ||
@@ -369,2 +463,8 @@ type, | ||
}); | ||
if (this.bottomBorder) { | ||
this.logger.upsertProgress({ | ||
index: Object.keys(this.tasks).length + this.initialLines, | ||
data: this.bottomBorder, | ||
}); | ||
} | ||
} | ||
@@ -423,2 +523,5 @@ incrementTask(name, _a = {}) { | ||
this.resolve(); | ||
if (!this.persist) { | ||
this.logger.done(); | ||
} | ||
} | ||
@@ -440,2 +543,5 @@ } | ||
} | ||
close() { | ||
this.logger.done(); | ||
} | ||
// TODO maybe make this static? | ||
@@ -442,0 +548,0 @@ hilbertSpinner(t, width) { |
@@ -48,7 +48,9 @@ /// <reference types="node" /> | ||
spinnerGenerator: SpinnerGenerator; | ||
initMessage?: string; | ||
initMessage: string; | ||
anchor: 'top' | 'bottom'; | ||
persist: boolean; | ||
border: boolean | string; | ||
} | ||
export declare class MultiProgressBars { | ||
private tasks; | ||
private stream; | ||
private spinnerFPS; | ||
@@ -62,4 +64,3 @@ private initialLines; | ||
private FULL_CHAR; | ||
private consoleSize; | ||
private endLine; | ||
private persist; | ||
private intervalID; | ||
@@ -72,2 +73,4 @@ private numCrawlers; | ||
private logger; | ||
private border; | ||
private bottomBorder; | ||
promise: Promise<void>; | ||
@@ -79,3 +82,2 @@ /** | ||
constructor(options?: Partial<CtorOptions>); | ||
private resizeConsole; | ||
cleanup: () => void; | ||
@@ -91,2 +93,3 @@ private init; | ||
restart(name: string, options: Pick<UpdateOptions, 'message' | 'barColorFn'>): void; | ||
close(): void; | ||
private hilbertSpinner; | ||
@@ -93,0 +96,0 @@ private renderIndefinite; |
@@ -81,13 +81,48 @@ import { green } from 'chalk'; | ||
this.stream = options.stream; | ||
this.width = process.stdout.columns; | ||
this.height = process.stdout.rows - 1; | ||
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.stream.on('resize', this.resize); | ||
this.progressHeight = 0; | ||
this.consoleHeight = this.height; | ||
this.progressBuffer = []; | ||
this.consoleBuffer = []; | ||
this.warn = this.log; | ||
this.error = this.log; | ||
console = this; | ||
if (this.anchor === 'top') { | ||
this.upsertProgress = this.upsertProgressTop; | ||
this.writeLines = this.writeLinesTop; | ||
this.refresh = this.refreshTop; | ||
this.log = this.logTop; | ||
this.done = this.cleanup; | ||
} | ||
else { | ||
this.upsertProgress = this.upsertProgressBottom; | ||
this.writeLines = this.writeLinesBottom; | ||
this.refresh = this.refreshBottom; | ||
this.log = this.logBottom; | ||
this.done = this.gotoBottom; | ||
} | ||
this.init(); | ||
} | ||
checkConsoleIntercept() { | ||
if (!this.originalConsole) { | ||
this.originalConsole = console; | ||
console = this; | ||
} | ||
} | ||
// height is one less than rows, because if you print to the last line, the console usually adds a newline | ||
resize() { | ||
this.width = process.stdout.columns; | ||
this.height = process.stdout.rows - 1; | ||
} | ||
gotoBottom() { | ||
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; | ||
} | ||
cleanup() { | ||
@@ -97,8 +132,10 @@ var _a; | ||
console = this.originalConsole; | ||
this.originalConsole = null; | ||
} | ||
init() { | ||
var _a; | ||
process.on('SIGINT', this.cleanup); | ||
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); | ||
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); | ||
} | ||
} | ||
@@ -111,3 +148,4 @@ /** Add or Update Progress Entry | ||
*/ | ||
upsertProgress(options) { | ||
upsertProgressTop(options) { | ||
this.checkConsoleIntercept(); | ||
// If the progress we're upserting exists already, just update. | ||
@@ -139,3 +177,19 @@ if (options.index < this.progressHeight) { | ||
} | ||
upsertProgressBottom(options) { | ||
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; | ||
} | ||
// 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); | ||
@@ -146,3 +200,3 @@ if (options.refresh) { | ||
} | ||
writeLines(...indexes) { | ||
writeLinesTop(...indexes) { | ||
var _a; | ||
@@ -154,4 +208,12 @@ let writeString = indexes.reduce((prev, index) => { | ||
} | ||
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 */ | ||
refresh() { | ||
refreshTop() { | ||
var _a; | ||
@@ -165,4 +227,12 @@ const outString = CUP(0) | ||
} | ||
log(...data) { | ||
refreshBottom() { | ||
var _a; | ||
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); | ||
} | ||
logTop(...data) { | ||
var _a; | ||
const writeString = format.apply(null, data); | ||
@@ -173,4 +243,9 @@ // Split by newlines, and then split the resulting lines if they run longer than width. | ||
do { | ||
const front = curr.slice(0, this.width); | ||
curr = curr.slice(this.width); | ||
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); | ||
@@ -193,2 +268,15 @@ } while (curr.length > 0); | ||
} | ||
logBottom(...data) { | ||
var _a; | ||
const writeString = format.apply(null, data); | ||
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); | ||
} | ||
} | ||
@@ -211,13 +299,7 @@ | ||
this.FULL_CHAR = this.CHARS[this.CHARS.length - 1]; | ||
this.endLine = 0; | ||
this.longestNameLength = 0; | ||
this.t = 0; | ||
this.logger = new VirtualConsole({ stream: process.stdout }); | ||
this.resizeConsole = () => { | ||
this.consoleSize = { | ||
width: this.stream.columns, | ||
height: this.stream.rows, | ||
}; | ||
}; | ||
this.cleanup = () => { | ||
var _a; | ||
(_a = this.logger) === null || _a === void 0 ? void 0 : _a.done(); | ||
if (this.intervalID) { | ||
@@ -227,10 +309,11 @@ clearInterval(this.intervalID); | ||
}; | ||
const { stream = process.stdout, spinnerFPS = 10, spinnerGenerator = this.hilbertSpinner, } = options || {}; | ||
const { stream = process.stdout, spinnerFPS = 10, spinnerGenerator = this.hilbertSpinner, anchor = 'bottom', persist = false, border = false, } = options || {}; | ||
let { progressWidth = 40, numCrawlers = 4, initMessage, } = options || {}; | ||
this.stream = stream; | ||
this.stream.on('resize', () => { | ||
this.resizeConsole(); | ||
}); | ||
this.logger = new VirtualConsole({ stream, anchor }); | ||
this.persist = persist; | ||
this.spinnerFPS = Math.min(spinnerFPS, 60); | ||
this.spinnerGenerator = spinnerGenerator; | ||
this.border = (typeof border === 'boolean') | ||
? (!border) ? null : '\u2500' | ||
: border; | ||
if (progressWidth % 2 !== 0) { | ||
@@ -249,3 +332,2 @@ progressWidth += 1; | ||
this.progressWidth = progressWidth; | ||
this.resizeConsole(); | ||
if (initMessage === undefined) { | ||
@@ -256,2 +338,22 @@ initMessage = '$ ' + process.argv.map((arg) => { | ||
} | ||
else { | ||
initMessage = initMessage.split('\n')[0]; | ||
} | ||
if (this.border) { | ||
initMessage = clampString(initMessage, this.logger.width - 10); | ||
const startRepeat = (this.border.length > 4) ? 1 : Math.floor(4 / this.border.length); | ||
initMessage = this.border.repeat(startRepeat) + ' ' + initMessage + ' '; | ||
const currentCount = stringWidth(initMessage); | ||
const remaining = this.logger.width - currentCount; | ||
const endRepeat = Math.ceil(remaining / this.border.length); | ||
initMessage += this.border.repeat(endRepeat); | ||
initMessage = clampString(initMessage, this.logger.width); | ||
} | ||
else { | ||
initMessage = clampString(initMessage, this.logger.width); | ||
} | ||
if (this.border && this.logger.anchor === 'top') { | ||
this.bottomBorder = | ||
clampString(this.border.repeat(Math.ceil(this.logger.width / this.border.length)), this.logger.width); | ||
} | ||
this.init(initMessage); | ||
@@ -262,11 +364,7 @@ } | ||
process.on('SIGINT', this.cleanup); | ||
// TODO make this account for lines that wrap | ||
const splitMessage = message.split('\n'); | ||
splitMessage.forEach((msg, idx) => { | ||
this.logger.upsertProgress({ | ||
index: idx, | ||
data: msg, | ||
}); | ||
this.logger.upsertProgress({ | ||
index: 0, | ||
data: message, | ||
}); | ||
this.initialLines = splitMessage.length; | ||
this.initialLines = 1; | ||
} | ||
@@ -283,6 +381,2 @@ addTask(name, _a) { | ||
const { type, barColorFn = defaultTransformFn, percentage = 0, message = '', } = options; | ||
this.endLine = Math.max.apply(null, [ | ||
index, | ||
...Object.entries(this.tasks).map(([_, task]) => task.index) | ||
]) + 1; | ||
this.tasks[name] = { | ||
@@ -361,2 +455,8 @@ type, | ||
}); | ||
if (this.bottomBorder) { | ||
this.logger.upsertProgress({ | ||
index: Object.keys(this.tasks).length + this.initialLines, | ||
data: this.bottomBorder, | ||
}); | ||
} | ||
} | ||
@@ -415,2 +515,5 @@ incrementTask(name, _a = {}) { | ||
this.resolve(); | ||
if (!this.persist) { | ||
this.logger.done(); | ||
} | ||
} | ||
@@ -432,2 +535,5 @@ } | ||
} | ||
close() { | ||
this.logger.done(); | ||
} | ||
// TODO maybe make this static? | ||
@@ -434,0 +540,0 @@ hilbertSpinner(t, width) { |
@@ -5,2 +5,3 @@ /// <reference types="node" /> | ||
stream: WriteStream; | ||
anchor: 'top' | 'bottom'; | ||
} | ||
@@ -17,3 +18,2 @@ export interface AddProgressOptions { | ||
private consoleBuffer; | ||
private width; | ||
private height; | ||
@@ -24,5 +24,15 @@ private progressHeight; | ||
private stream; | ||
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; | ||
@@ -36,7 +46,11 @@ init(): void; | ||
*/ | ||
upsertProgress(options: UpsertProgressOptions): void; | ||
upsertProgressTop(options: UpsertProgressOptions): void; | ||
upsertProgressBottom(options: UpsertProgressOptions): void; | ||
updateProgress(options: UpsertProgressOptions): void; | ||
writeLines(...indexes: number[]): void; | ||
refresh(): void; | ||
log(...data: any[]): void; | ||
writeLinesTop(...indexes: number[]): void; | ||
writeLinesBottom(...indexes: number[]): void; | ||
refreshTop(): void; | ||
refreshBottom(): void; | ||
logTop(...data: any[]): void; | ||
logBottom(...data: any[]): void; | ||
} |
{ | ||
"name": "multi-progress-bars", | ||
"version": "2.0.7", | ||
"version": "3.1.0", | ||
"description": "Multiple progress bars with option for indefinite spinners", | ||
@@ -17,3 +17,3 @@ "main": "dist/multi-progress-bars.cjs.js", | ||
"clean": "yarn trash 'dist/**/*' 'example/example.js'", | ||
"runExample": "yarn build && yarn buildExample && yarn node example/example.js" | ||
"runExample": "yarn build && yarn buildExample && yarn node example/exampleBottom.js" | ||
}, | ||
@@ -20,0 +20,0 @@ "author": "Sean Chen <kamiyo@gmail.com>", |
# multi-progress-bars | ||
![npm shield](https://img.shields.io/npm/v/multi-progress-bars) | ||
A node library for displaying multiple progress bars, with an option for displaying indefinite tasks by using a spinner. Works well with gulp.js and/or webpack. This library will clear the screen (non-destructively by inserting newlines until the previous commands are above the fold), and display the bars from the top. Any subsequent calls to `console.log` will not affect the fixed progress rows; however, overflow will push previous console messages above the fold. This is enabled by a simple virtual console. This way no console messages will be lost. See the below demo .gif. | ||
> :warning: v3 technically has **breaking changes** in behavior nuance, but should be backwards-compatible. The changes are the addition of anchor position, border, and persist. If you pass in `true` for persist, you should call `mpb.close()` when you are done, or on `SIGINT` for example. Multi-line init messages are now clamped to the first line. | ||
![MultiProgressBar demo](./assets/mpb.gif) | ||
A node library for displaying multiple progress bars, with an option for displaying indefinite tasks by using a spinner. Works well with gulp.js and/or webpack. This library has two display options: top and bottom anchor. | ||
Anchoring the progress bars to the top will clear the screen non-destructively, and display the bars from the top; any subsequent calls to `console.log` will not affect the fixed progress rows, but overflow will push previous console messages above the fold. This is enabled by a simple virtual console. This way no console messages will be lost. See the below demo .gif. | ||
![MultiProgressBar top demo](./assets/mpb_top.gif) | ||
Anchoring to the bottom is a bit simpler, but is just as useful. | ||
![MultiProgressBar bottom demo](./assets/mpb_bottom.gif) | ||
## Install | ||
@@ -14,7 +23,7 @@ `npm install multi-progress-bars` | ||
## Usage | ||
Each bar is represented internally by a `Task`. First instantiate `MultiProgressBars`: | ||
Each bar is represented internally by a `Task`. First instantiate `MultiProgressBars` with options like `anchor` and `border`: | ||
```node | ||
import { MultiProgressBars } from 'multi-progress-bars'; | ||
const mpb = new MultiProgressBars(); | ||
const mpb = new MultiProgressBars(/* options object */); | ||
``` | ||
@@ -75,3 +84,6 @@ | ||
* `spinnerGenerator` `<(t: number, width: number) => string>` A function that takes the current timestamp and total width and returns a string. default = `mpb.hilbertSpinner` | ||
* `initMessage` `<string>` A persistent message to display above the bars. default = `'$ ' + process.argv.map((arg) => { return path.parse(arg).name; }).join(' ');` | ||
* `initMessage` `<string>` A persistent message to display above the bars. If it is multi-line or longer than the console width, it will take the first line and/or clamp it. default = `'$ ' + process.argv.map((arg) => { return path.parse(arg).name; }).join(' ');` | ||
* `anchor` `<'top' | 'bottom'>` The position of the progress bars. default (to match with previous behavior) = `'top'` | ||
* `border` `<boolean | string>` If set to true, will use U+2500 as the character. If set to false, will not show border. If set to string, will use said string to form borders. Bottom-anchored progress section will only have a top border. Top-anchored section will have both top and bottom borders. default = `false` | ||
* `persist` `<boolean>` When true, mpb will continue to intercept console functions even when all the tasks are completed; you must call `mpb.close()` to get back the original console (or else you might get wonky prompt placement afterwards). If false, once all tasks have completed, mpb will automatically restore the original console. However, if you restart a task, it will re-intercept. Use true if doing something like a webpack watch. default = `false` | ||
@@ -129,2 +141,6 @@ ### `mpb.addTask(name, options)` | ||
### `mpb.close()` | ||
This will restore the original console to `console`, and move the cursor to expected position for the next console prompt. If you restart tasks after mpb has closed, it will re-intercept console again. | ||
### `mpb.promise` | ||
@@ -230,3 +246,3 @@ | ||
const mpb = new MultiProgressBars(); | ||
const mpb = new MultiProgressBars({ persist: true }); | ||
const webpackConfig = require('./webpack.dev.config.js'); | ||
@@ -264,8 +280,4 @@ | ||
## TODO | ||
* ~~Make the cursor moving operations atomic with the other writes so we don't get thrashing if other stuff is being printed.~~ Done! | ||
* ~~Will require using ansi escape codes with one call to stdout.write instead of relying on Node's readline/stdout cursorTo and moveCursors; either that or use promises as a mutex for performing the move and the writes together.~~ Done! | ||
* ~~Allow bars to be at top~~ or at bottom, ~~while allowing other logs to be printed~~ Done for top! Lines preserved. | ||
* ~~Will require intercepting/overwriting default console.log or stdout.write. Not sure which would be better yet.~~ See `virtual-console.ts` | ||
* Decouple hilbertSpinner from the instance. | ||
* Allow custom bar format | ||
* Allow custom progress format |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
72938
1316
279