Browse files

Initial implementaion.

  • Loading branch information...
1 parent cd3bac4 commit 66508485984196938c56001ed9644fc0423d83c8 @cpsubrian committed Jul 30, 2012
Showing with 348 additions and 2 deletions.
  1. +23 −2 README.md
  2. +259 −0 pace.js
  3. +22 −0 package.json
  4. BIN screenshot.png
  5. +25 −0 test/advanced.js
  6. +19 −0 test/simple.js
View
25 README.md
@@ -1,4 +1,25 @@
-pace
+Pace
====
-A node.js module that outputs a progress bar and other metrics to the command-line.
+A node.js module that outputs a progress bar and other metrics to the command-line.
+
+Example
+-------
+Running the following code:
+```
+var total = 50000,
+ count = 0,
+ pace = require('../')(total);
+
+while (count++ < total) {
+ pace.op();
+
+ // Cause some work to be done.
+ for (var i = 0; i < 1000000; i++) {
+ count = count;
+ }
+}
+```
+
+Will cause output to your console similar to:
+![Sample progress bar output](screenshot.png)
View
259 pace.js
@@ -0,0 +1,259 @@
+/**
+ * Pace
+ *
+ * A portable progress bar for the command-line.
+ *
+ * Example usage:
+ *
+ * var total = 50000,
+ * count = 0,
+ * pace = require('pace')(total);
+ *
+ * while (count++ < total) {
+ * pace.op();
+ *
+ * // Cause some work to be done.
+ * for (var i = 0; i < 1000000; i++) {
+ * count = count;
+ * }
+ * }
+ *
+ * @license MIT
+ * @author Brian Link, Carlos Rodriguez
+ * @copyright 2012 Terra Eclipse, Inc.
+ */
+
+// Module dependencies.
+var charm = require('charm');
+
+/**
+ * Pace 'class'.
+ */
+function Pace(options) {
+ options = options || {};
+
+ // Total number of items to process.
+ if (!options.total) {
+ throw new Error('You MUST specify the total number of operations that will be processed.');
+ }
+ this.total = options.total;
+
+ // Current item number.
+ this.current = 0;
+
+ // Maximum percent of total time the progressbar is allowed to take during processing.
+ // Defaults to 0.5%
+ this.max_burden = options.maxBurden || 0.5;
+
+ // Whether to show current burden %.
+ this.show_burden = options.showBurden || false;
+
+ // Internal time tracking properties.
+ this.started = false;
+ this.size = 50;
+ this.inner_time = 0;
+ this.outer_time = 0;
+ this.elapsed = 0;
+ this.time_start = 0;
+ this.time_end = 0;
+ this.time_left = 0;
+ this.time_burden = 0;
+ this.skip_steps = 0;
+ this.skipped = 0;
+
+ // Setup charm.
+ this.charm = charm();
+ this.charm.pipe(process.stdout);
+ this.charm.cursor(false);
+ this.charm.on('^C', this.abort.bind(this));
+
+ // Prepare the output.
+ this.charm.write("\n\n\n");
+}
+
+/**
+ * Export a factory function for new pace instances.
+ */
+module.exports = function(options) {
+ if (typeof options === 'number') {
+ options = {
+ total: options
+ };
+ }
+ return new Pace(options);
+};
+
+/**
+ * An operation has been emitted.
+ */
+Pace.prototype.op = function op(count) {
+ var hours, min, sec;
+
+ if (count) {
+ this.current = count;
+ }
+ else {
+ this.current++;
+ }
+
+ if (this.burdenReached()) {
+ return;
+ }
+
+ // Perform the main progress bar logic.
+ if (!this.started) {
+ this.started = new Date().getTime();
+ }
+
+ // Start collecting time.
+ this.time_start = new Date().getTime();
+ this.elapsed = this.time_start - this.started;
+ if (this.time_end > 0) {
+ this.outer_time = this.time_start - this.time_end;
+ }
+ if (this.inner_time > 0 && this.outer_time > 0) {
+ // Set Current Burden
+ this.time_burden = (this.inner_time / (this.inner_time + this.outer_time)) * 100;
+
+ // Estimate time left.
+ this.time_left = (this.elapsed / this.current) * (this.total - this.current);
+
+ if (this.time_left < 0) this.time_left = 0;
+ }
+ // If our "burden" is too high, increase the skip steps.
+ if (this.time_burden > this.max_burden && (this.skip_steps < (this.total / this.size))) {
+ this.skip_steps = Math.floor(++this.skip_steps * 1.3);
+ }
+
+ // Reset charm cursor position.
+ this.charm.erase('line').up(1).erase('line').up(1).erase('line').write("\r");
+
+ // Write the progress bar.
+ this.charm.write('Processing: ');
+ this.charm.foreground('green').background('green');
+ for (var i = 0; i < ((this.current / this.total) * this.size) - 1 ; i++) {
+ this.charm.write(' ');
+ }
+ this.charm.foreground('white').background('white');
+ while (i < this.size - 1) {
+ this.charm.write(' ');
+ i++;
+ }
+ this.charm.display('reset').down(1).left(100);
+
+ // Output numerical stats.
+ this.perc = (this.current/this.total)*100;
+ this.perc = padLeft(this.perc.toFixed(2), 2);
+ this.charm.write(' ').display('bright').write(this.perc + '%').display('reset');
+ this.total_len = formatNumber(this.total).length;
+ this.charm.write(' ').display('bright').write(padLeft(formatNumber(this.current), this.total_len)).display('reset');
+ this.charm.write('/' + formatNumber(this.total));
+
+ // Output burden.
+ if (this.show_burden) {
+ this.charm.write(' ').display('bright').write('Burden: ').display('reset');
+ this.charm.write(this.time_burden.toFixed(2) + '% / ' + this.skip_steps);
+ }
+
+ this.charm.display('reset').down(1).left(100);
+
+ // Output times.
+ hours = Math.floor(this.elapsed / (1000 * 60 * 60));
+ min = Math.floor(((this.elapsed / 1000) % (60 * 60)) / 60);
+ sec = Math.floor((this.elapsed / 1000) % 60);
+ this.charm.write(' ').display('bright').write('Elapsed: ').display('reset');
+ this.charm.write(hours + 'h ' + min + 'm ' + sec + 's');
+
+ if (this.time_left){
+ hours = Math.floor(this.time_left / (1000 * 60 * 60));
+ min = Math.floor(((this.time_left / 1000) % (60 * 60)) / 60);
+ sec = Math.ceil((this.time_left / 1000) % 60);
+ this.charm.write(' ').display('bright').write('Remaining: ').display('reset');
+ this.charm.write(hours + 'h ' + min + 'm ' + sec + 's');
+ }
+
+ // The task is complete.
+ if (this.current >= this.total) {
+ this.finished();
+ }
+
+ // Record end time.
+ this.time_end = new Date().getTime();
+ this.inner_time = this.time_end - this.time_start;
+};
+
+/**
+ * The progress has finished.
+ */
+Pace.prototype.finished = function finished() {
+ this.charm.write("\n\n");
+ this.charm.write('Finished!');
+ this.charm.write("\n\n");
+ this.charm.cursor(true);
+ this.charm.end();
+};
+
+/**
+ * The process was aborted.
+ */
+Pace.prototype.abort = function abort() {
+ this.charm.write("\n\n");
+ this.charm.write('Aborted!');
+ this.charm.write("\n\n");
+ this.charm.cursor(true);
+ this.charm.end();
+};
+
+/**
+ * Check if the burden threshold has been reached.
+ */
+Pace.prototype.burdenReached = function burdenReached() {
+ // Skip this cycle if the burden has determined we should.
+ if ((this.skip_steps > 0) && (this.current < this.total)) {
+ if (this.skipped < this.skip_steps) {
+ this.skipped++;
+ return true;
+ }
+ else {
+ this.skipped = 0;
+ }
+ }
+ return false;
+};
+
+
+/**
+ * Utility functions.
+ */
+
+// Left-pad a string.
+function padLeft(str, length, pad) {
+ pad = pad || ' ';
+ while (str.length < length)
+ str = pad + str;
+ return str;
+}
+
+// Ported from php.js. Same has php's number_format().
+function formatNumber(number, decimals, dec_point, thousands_sep) {
+ number = (number + '').replace(/[^0-9+\-Ee.]/g, '');
+ var n = !isFinite(+number) ? 0 : +number,
+ prec = !isFinite(+decimals) ? 0 : Math.abs(decimals),
+ sep = (typeof thousands_sep === 'undefined') ? ',' : thousands_sep,
+ dec = (typeof dec_point === 'undefined') ? '.' : dec_point,
+ s = '',
+ toFixedFix = function (n, prec) {
+ var k = Math.pow(10, prec);
+ return '' + Math.round(n * k) / k;
+ };
+ // Fix for IE parseFloat(0.55).toFixed(0) = 0;
+ s = (prec ? toFixedFix(n, prec) : '' + Math.round(n)).split('.');
+ if (s[0].length > 3) {
+ s[0] = s[0].replace(/\B(?=(?:\d{3})+(?!\d))/g, sep);
+ }
+ if ((s[1] || '').length < prec) {
+ s[1] = s[1] || '';
+ s[1] += new Array(prec - s[1].length + 1).join('0');
+ }
+ return s.join(dec);
+}
View
22 package.json
@@ -0,0 +1,22 @@
+{
+ "author": "Brian Link <cpsubrian@gmail.com>",
+ "name": "pace",
+ "description": "Command-line progress bar and progress metrics. Helps you measure the 'pace' of a long-running activity.",
+ "version": "0.0.1",
+ "homepage": "http://cantina.github.com",
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/cantina/pace.git"
+ },
+ "main": "pace.js",
+ "scripts": {},
+ "dependencies": {
+ "charm": "~0.1.0",
+ "inherits": "~1.0.0"
+ },
+ "devDependencies": {},
+ "optionalDependencies": {},
+ "engines": {
+ "node": "*"
+ }
+}
View
BIN screenshot.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
25 test/advanced.js
@@ -0,0 +1,25 @@
+/**
+ * Advanced test of pace.js.
+ *
+ * Set the current position in op() and also randomly increase the total.
+ */
+
+var total = 50000,
+ current = 0,
+ pace = require('../')(total);
+
+while (current++ < total) {
+ if (Math.random() > 0.9) {
+ pace.op(current);
+ }
+
+ if (Math.random() < 0.05 && total <= 50000) {
+ total += Math.floor(Math.random() * 100);
+ pace.total = total;
+ }
+
+ // Cause some work to be done.
+ for (var i = 0; i < 1000000; i++) {
+ current = current;
+ }
+}
View
19 test/simple.js
@@ -0,0 +1,19 @@
+/**
+ * Simple test of pace.js.
+ *
+ * Play with maxBurden to see how it effects total execution speed and the
+ * progress bar refresh rate.
+ */
+
+var total = 50000,
+ count = 0,
+ pace = require('../')({total: total, showBurden: true, maxBurden: 0.5});
+
+while (count++ < total) {
+ pace.op();
+
+ // Cause some work to be done.
+ for (var i = 0; i < 1000000; i++) {
+ count = count;
+ }
+}

0 comments on commit 6650848

Please sign in to comment.