Skip to content

Commit

Permalink
Add option 'units', it is a map of unit labels to values (in seconds)…
Browse files Browse the repository at this point in the history
…, e.g. { 's': 1, 'm': 60, 'h': 3600 }

Modify ETA estimation to return the least granular unit for which the eta is >= 1, based on supplied values
Add replacement token ':unit', returns the label associated with the unit value
ProgressBar.defaultUnits holds the h/m/s arrangement; it is not actually used by default to preserve compatibility

Example usage: new ProgressBar(':eta:unit', { units: ProgressBar.defaultUnits });
Example output: '12.3m' -> '38.5s' (changes from minutes to seconds over time)

Addresses visionmedia#63
  • Loading branch information
Kris Reeves committed May 18, 2015
1 parent ae2f52e commit 182b4bb
Showing 1 changed file with 72 additions and 4 deletions.
76 changes: 72 additions & 4 deletions lib/node-progress.js
Expand Up @@ -55,6 +55,7 @@ function ProgressBar(fmt, options) {
this.fmt = fmt;
this.curr = 0;
this.total = options.total;
this.units = ProgressBar.mkUnits(options.units || { 's': 1 });
this.width = options.width || this.total;
this.clear = options.clear
this.chars = {
Expand All @@ -67,6 +68,35 @@ function ProgressBar(fmt, options) {
this.lastDraw = '';
}

/**
* The default unit bindings for enhanced ETA
*/
ProgressBar.defaultUnits = {
'h': 3600,
'm': 60,
's': 1
};

/**
* Converts object mappings into something more useful for calculating ETA with
*
* @param {object} obj Map of suffixes to unit values
*/
ProgressBar.mkUnits = function (obj) {
var arr = Object.keys(obj)
.map(function (key) {
return { label: key, value: obj[key] };
})
.sort(function (a, b) {
return a.value - b.value;
});

var values = arr.map(function (unit) { return unit.value; }),
labels = arr.map(function (unit) { return unit.label; });

return { labels: labels, values: values };
};

/**
* "tick" the progress bar with optional `len` and optional `tokens`.
*
Expand All @@ -86,7 +116,7 @@ ProgressBar.prototype.tick = function(len, tokens){
// start time for eta
if (0 == this.curr) this.start = new Date;

this.curr += len
this.curr += len;

// schedule render
if (!this.renderThrottleTimeout) {
Expand Down Expand Up @@ -125,15 +155,15 @@ ProgressBar.prototype.render = function (tokens) {
var percent = ratio * 100;
var incomplete, complete, completeLength;
var elapsed = new Date - this.start;
var eta = (percent == 100) ? 0 : elapsed * (this.total / this.curr - 1);
var eta = this.estimate(elapsed, percent);

/* populate the bar template with percentages and timestamps */
var str = this.fmt
.replace(':current', this.curr)
.replace(':total', this.total)
.replace(':elapsed', isNaN(elapsed) ? '0.0' : (elapsed / 1000).toFixed(1))
.replace(':eta', (isNaN(eta) || !isFinite(eta)) ? '0.0' : (eta / 1000)
.toFixed(1))
.replace(':eta', eta.value)
.replace(':unit', eta.suffix)
.replace(':percent', percent.toFixed(0) + '%');

/* compute the available space (non-zero) for the bar */
Expand All @@ -159,6 +189,44 @@ ProgressBar.prototype.render = function (tokens) {
}
};

/**
* Estimate the remaining time and return it in the largest unit possible
*
* @param {number} elapsed
* @param {number} percent
* @api private
*/
ProgressBar.prototype.estimate = function (elapsed, percent) {
var eta = (percent == 100) ? 0 : elapsed * (this.total / this.curr - 1);
eta /= 1000;

var values = this.units.values;
var labels = this.units.labels;
var i = values.length - 1;
var suffix = labels[i];

if (isNaN(eta) || !isFinite(eta)) {
return {
eta: '0.0',
suffix: suffix
};
}

while (i > 0 && eta < values[i]) {
i--;
}
suffix = labels[i];

while (i > 0) {
eta /= values[i--];
}

return {
value: eta.toFixed(1),
suffix: suffix
};
};

/**
* "update" the progress bar to represent an exact percentage.
* The ratio (between 0 and 1) specified will be multiplied by `total` and
Expand Down

0 comments on commit 182b4bb

Please sign in to comment.