diff --git a/.gitignore b/.gitignore index 05b4834..1a09921 100644 --- a/.gitignore +++ b/.gitignore @@ -12,17 +12,14 @@ # Main code, tests, and docs !/lib/ +!/deps/ !/test/ !/doc/ !/tools/ !/setup/ -# Exclude most packages under node_modules because they are dev dependencies -!/node_modules/ -/node_modules/*/ - -# This one is actually a runtime dependency -!/node_modules/progress/ +# Exclude packages under node_modules because they are dev dependencies +/node_modules/ # Default version link /default diff --git a/node_modules/progress/.npmignore b/deps/progress/.npmignore similarity index 100% rename from node_modules/progress/.npmignore rename to deps/progress/.npmignore diff --git a/node_modules/progress/History.md b/deps/progress/History.md similarity index 100% rename from node_modules/progress/History.md rename to deps/progress/History.md diff --git a/node_modules/progress/LICENSE b/deps/progress/LICENSE similarity index 100% rename from node_modules/progress/LICENSE rename to deps/progress/LICENSE diff --git a/node_modules/progress/Makefile b/deps/progress/Makefile similarity index 100% rename from node_modules/progress/Makefile rename to deps/progress/Makefile diff --git a/node_modules/progress/Readme.md b/deps/progress/Readme.md similarity index 73% rename from node_modules/progress/Readme.md rename to deps/progress/Readme.md index 202cef3..23614b8 100644 --- a/node_modules/progress/Readme.md +++ b/deps/progress/Readme.md @@ -35,6 +35,7 @@ These are keys in the options object you can pass to the progress bar along with - `stream` the output stream defaulting to stderr - `complete` completion character defaulting to "=" - `incomplete` incomplete character defaulting to "-" +- `renderThrottle` minimum time between updates in milliseconds defaulting to 16 - `clear` option to clear the bar on completion defaulting to false - `callback` optional function to call when the progress bar completes @@ -49,6 +50,32 @@ These are tokens you can use in the format of your progress bar. - `:percent` completion percentage - `:eta` estimated completion time in seconds +Tokens other than `:bar` may include a suffix to specify width, that is a minus +(for left-padding) or plus (for right-padding) followed by an integer, for example +`:percent-4`. + +### Custom Tokens + +You can define custom tokens by adding a `{'name': value}` object parameter to your method (`tick()`, `update()`, etc.) calls. + +```javascript +var bar = new ProgressBar(':current: :token1 :token2', { total: 3 }) +bar.tick({ + 'token1': "Hello", + 'token2': "World!\n" +}) +bar.tick(2, { + 'token1': "Goodbye", + 'token2': "World!" +}) +``` +The above example would result in the output below. + +``` +1: Hello World! +3: Goodbye World! +``` + ## Examples ### Download @@ -71,7 +98,7 @@ req.on('response', function(res){ var len = parseInt(res.headers['content-length'], 10); console.log(); - var bar = new ProgressBar(' downloading [:bar] :percent :etas', { + var bar = new ProgressBar(' downloading [:bar] :percent-4 :eta-3s', { complete: '=', incomplete: ' ', width: 20, @@ -93,7 +120,7 @@ req.end(); The above example result in a progress bar like the one below. ``` -downloading [===== ] 29% 3.7s +downloading [===== ] 29% 3.7s ``` You can see more examples in the `examples` folder. diff --git a/node_modules/progress/index.js b/deps/progress/index.js similarity index 100% rename from node_modules/progress/index.js rename to deps/progress/index.js diff --git a/node_modules/progress/lib/node-progress.js b/deps/progress/lib/node-progress.js similarity index 62% rename from node_modules/progress/lib/node-progress.js rename to deps/progress/lib/node-progress.js index 0f47a9f..95de4fe 100644 --- a/node_modules/progress/lib/node-progress.js +++ b/deps/progress/lib/node-progress.js @@ -21,6 +21,7 @@ exports = module.exports = ProgressBar; * - `stream` the output stream defaulting to stderr * - `complete` completion character defaulting to "=" * - `incomplete` incomplete character defaulting to "-" + * - `renderThrottle` minimum time between updates in milliseconds defaulting to 16 * - `callback` optional function to call when the progress bar completes * - `clear` will clear the progress bar upon termination * @@ -33,6 +34,10 @@ exports = module.exports = ProgressBar; * - `:percent` completion percentage * - `:eta` eta in seconds * + * Tokens other than `:bar` may include a suffix to specify width, that is a minus + * (for left-padding) or plus (for right-padding) followed by an integer, for example + * `:percent-4`. + * * @param {string} fmt * @param {object|number} options or total * @api public @@ -60,7 +65,9 @@ function ProgressBar(fmt, options) { complete : options.complete || '=', incomplete : options.incomplete || '-' }; + this.renderThrottle = options.renderThrottle !== 0 ? (options.renderThrottle || 16) : 0; this.callback = options.callback || function () {}; + this.tokens = {}; this.lastDraw = ''; } @@ -78,15 +85,21 @@ ProgressBar.prototype.tick = function(len, tokens){ // swap tokens if ('object' == typeof len) tokens = len, len = 1; + if (tokens) this.tokens = tokens; // start time for eta if (0 == this.curr) this.start = new Date; this.curr += len - this.render(tokens); + + // schedule render + if (!this.renderThrottleTimeout) { + this.renderThrottleTimeout = setTimeout(this.render.bind(this), this.renderThrottle); + } // progress complete - if (this.curr >= this.total) { + if (!this.complete && this.curr >= this.total) { + if (this.renderThrottleTimeout) this.render(); this.complete = true; this.terminate(); this.callback(this); @@ -103,6 +116,11 @@ ProgressBar.prototype.tick = function(len, tokens){ */ ProgressBar.prototype.render = function (tokens) { + clearTimeout(this.renderThrottleTimeout); + this.renderThrottleTimeout = null; + + if (tokens) this.tokens = tokens; + if (!this.stream.isTTY) return; var ratio = this.curr / this.total; @@ -114,13 +132,14 @@ ProgressBar.prototype.render = function (tokens) { var eta = (percent == 100) ? 0 : elapsed * (this.total / this.curr - 1); /* 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(':percent', percent.toFixed(0) + '%'); + var str = this.fmt; + str = replaceToken(str, 'current', this.curr); + str = replaceToken(str, 'total', this.total); + str = replaceToken(str, 'elapsed', isNaN(elapsed) ? '0.0' + : elapsed >= 10000 ? Math.round(elapsed / 1000) : (elapsed / 1000).toFixed(1)); + str = replaceToken(str, 'eta', (isNaN(eta) || !isFinite(eta)) ? '0.0' + : eta >= 10000 ? Math.round(eta / 1000) : (eta / 1000).toFixed(1)); + str = replaceToken(str, 'percent', percent.toFixed(0) + '%'); /* compute the available space (non-zero) for the bar */ var availableSpace = Math.max(0, this.stream.columns - str.replace(':bar', '').length); @@ -135,16 +154,54 @@ ProgressBar.prototype.render = function (tokens) { str = str.replace(':bar', complete + incomplete); /* replace the extra tokens */ - if (tokens) for (var key in tokens) str = str.replace(':' + key, tokens[key]); + if (this.tokens) for (var key in this.tokens) str = replaceToken(str, key, this.tokens[key]); if (this.lastDraw !== str) { - this.stream.clearLine(); this.stream.cursorTo(0); this.stream.write(str); + if (str.length < this.lastDraw.length) { + // Reduce flicker - don't clear unless the new line is shorter. + this.stream.clearLine(1); + } this.lastDraw = str; } }; +/** + * Replace a token in a string, using optional width specifiers after the token. + * @param str {string} The string that may contain the token to be replaced. + * @param token {string} Token to replace, not including the ':' prefix or width suffix. + * @param value {string} The replacement value. + * @return The resulting string after replacement. + */ +function replaceToken(str, token, value) { + token = ':' + token; + var tokenIndex = str.indexOf(token); + if (tokenIndex < 0) { + return str; + } + + value = (value ? value.toString() : ''); + + function repeat(s, n) { return n <= 0 ? '' : Array(n + 1).join(s); }; + + if (str[tokenIndex + token.length] === '-') { + width = parseInt(str.substr(tokenIndex + token.length + 1)); + if (width) { + token = token + '-' + width; + value = repeat(' ', width - value.length) + value; + } + } else if (str[tokenIndex + token.length] === '+') { + width = parseInt(str.substr(tokenIndex + token.length + 1)); + if (width) { + token = token + '+' + width; + value = value + repeat(' ', width - value.length); + } + } + + return str.replace(token, value); +} + /** * "update" the progress bar to represent an exact percentage. * The ratio (between 0 and 1) specified will be multiplied by `total` and @@ -176,5 +233,5 @@ ProgressBar.prototype.terminate = function () { if (this.clear) { this.stream.clearLine(); this.stream.cursorTo(0); - } else console.log(); + } else this.stream.write('\n'); }; diff --git a/deps/progress/package.json b/deps/progress/package.json new file mode 100644 index 0000000..4cf6409 --- /dev/null +++ b/deps/progress/package.json @@ -0,0 +1,44 @@ +{ + "name": "progress", + "version": "1.2.0", + "description": "Flexible ascii progress bar", + "keywords": [ + "cli", + "progress" + ], + "author": { + "name": "TJ Holowaychuk", + "email": "tj@vision-media.ca" + }, + "contributors": [ + { + "name": "Christoffer Hallas", + "email": "christoffer.hallas@gmail.com" + }, + { + "name": "Jordan Scales", + "email": "scalesjordan@gmail.com" + } + ], + "dependencies": {}, + "main": "index", + "engines": { + "node": ">=0.4.0" + }, + "repository": { + "type": "git", + "url": "git://github.com/visionmedia/node-progress.git" + }, + "license": "MIT", + "gitHead": "4a6c2fdc782cff6c9f8933339e7aae0b38d682d4", + "readme": "Flexible ascii progress bar.\r\n\r\n## Installation\r\n\r\n```bash\r\n$ npm install progress\r\n```\r\n\r\n## Usage\r\n\r\nFirst we create a `ProgressBar`, giving it a format string\r\nas well as the `total`, telling the progress bar when it will\r\nbe considered complete. After that all we need to do is `tick()` appropriately.\r\n\r\n```javascript\r\nvar ProgressBar = require('progress');\r\n\r\nvar bar = new ProgressBar(':bar', { total: 10 });\r\nvar timer = setInterval(function () {\r\n bar.tick();\r\n if (bar.complete) {\r\n console.log('\\ncomplete\\n');\r\n clearInterval(timer);\r\n }\r\n}, 100);\r\n```\r\n\r\n### Options\r\n\r\nThese are keys in the options object you can pass to the progress bar along with\r\n`total` as seen in the example above.\r\n\r\n- `total` total number of ticks to complete\r\n- `width` the displayed width of the progress bar defaulting to total\r\n- `stream` the output stream defaulting to stderr\r\n- `complete` completion character defaulting to \"=\"\r\n- `incomplete` incomplete character defaulting to \"-\"\r\n- `renderThrottle` minimum time between updates in milliseconds defaulting to 16\r\n- `clear` option to clear the bar on completion defaulting to false\r\n- `callback` optional function to call when the progress bar completes\r\n\r\n### Tokens\r\n\r\nThese are tokens you can use in the format of your progress bar.\r\n\r\n- `:bar` the progress bar itself\r\n- `:current` current tick number\r\n- `:total` total ticks\r\n- `:elapsed` time elapsed in seconds\r\n- `:percent` completion percentage\r\n- `:eta` estimated completion time in seconds\r\n\r\nTokens other than `:bar` may include a suffix to specify width, that is a minus\r\n(for left-padding) or plus (for right-padding) followed by an integer, for example\r\n`:percent-4`.\r\n\r\n### Custom Tokens\r\n\r\nYou can define custom tokens by adding a `{'name': value}` object parameter to your method (`tick()`, `update()`, etc.) calls.\r\n\r\n```javascript\r\nvar bar = new ProgressBar(':current: :token1 :token2', { total: 3 })\r\nbar.tick({\r\n 'token1': \"Hello\",\r\n 'token2': \"World!\\n\"\r\n})\r\nbar.tick(2, {\r\n 'token1': \"Goodbye\",\r\n 'token2': \"World!\"\r\n})\r\n```\r\nThe above example would result in the output below.\r\n\r\n```\r\n1: Hello World!\r\n3: Goodbye World!\r\n```\r\n\r\n## Examples\r\n\r\n### Download\r\n\r\nIn our download example each tick has a variable influence, so we pass the chunk\r\nlength which adjusts the progress bar appropriately relative to the total\r\nlength.\r\n\r\n```javascript\r\nvar ProgressBar = require('../');\r\nvar https = require('https');\r\n\r\nvar req = https.request({\r\n host: 'download.github.com',\r\n port: 443,\r\n path: '/visionmedia-node-jscoverage-0d4608a.zip'\r\n});\r\n\r\nreq.on('response', function(res){\r\n var len = parseInt(res.headers['content-length'], 10);\r\n\r\n console.log();\r\n var bar = new ProgressBar(' downloading [:bar] :percent-4 :eta-3s', {\r\n complete: '=',\r\n incomplete: ' ',\r\n width: 20,\r\n total: len\r\n });\r\n\r\n res.on('data', function (chunk) {\r\n bar.tick(chunk.length);\r\n });\r\n\r\n res.on('end', function () {\r\n console.log('\\n');\r\n });\r\n});\r\n\r\nreq.end();\r\n```\r\n\r\nThe above example result in a progress bar like the one below.\r\n\r\n```\r\ndownloading [===== ] 29% 3.7s\r\n```\r\n\r\nYou can see more examples in the `examples` folder.\r\n\r\n## License\r\n\r\nMIT\r\n", + "readmeFilename": "Readme.md", + "bugs": { + "url": "https://github.com/visionmedia/node-progress/issues" + }, + "homepage": "https://github.com/visionmedia/node-progress#readme", + "_id": "progress@1.2.0", + "_shasum": "e9185384798a6911b3c37945b4dcf3ad28c13e46", + "_from": "git://github.com/jasongin/node-progress.git", + "_resolved": "git://github.com/jasongin/node-progress.git#4a6c2fdc782cff6c9f8933339e7aae0b38d682d4" +} diff --git a/lib/addRemove.js b/lib/addRemove.js index 16d5cb1..fb3165f 100644 --- a/lib/addRemove.js +++ b/lib/addRemove.js @@ -6,10 +6,11 @@ const Error = require('./error'); let nvsList = require('./list'); // Non-const enables test mocking let nvsUse = require('./use'); // Non-const enables test mocking let nvsLink = require('./link'); // Non-const enables test mocking -let nvsDownload = require('./download'); // Non-const enables test mocking -let nvsExtract = require('./extract'); // Non-const enables test mocking const NodeVersion = require('./version'); +let nvsDownload = null; // Delay-load +let nvsExtract = null; // Delay-load + /** * Downloads and extracts a version of node. */ @@ -72,12 +73,14 @@ function downloadAndExtractAsync(version, remoteUri) { version.semanticVersion, version.arch); + nvsDownload = nvsDownload || require('./download'); return nvsDownload.ensureFileCachedAsync( archiveFileName, archiveFileUri, shasumFileName, shasumFileUri ).then(zipFilePath => { + nvsExtract = nvsExtract || require('./extract'); return nvsExtract.extractAsync(zipFilePath, targetDir); }).then(() => { // Guess the name of the top-level extracted directory. diff --git a/lib/download.js b/lib/download.js index 7e08254..ff14dab 100644 --- a/lib/download.js +++ b/lib/download.js @@ -2,7 +2,7 @@ const crypto = require('crypto'); const path = require('path'); const stream = require('stream'); -const ProgressBar = require('progress'); +const ProgressBar = require('../deps/progress'); let fs = require('fs'); // Non-const enables test mocking let http = require('http'); // Non-const enables test mocking @@ -14,7 +14,7 @@ function downloadFileAsync(filePath, fileUri) { let stream = null; return new Promise((resolve, reject) => { try { - const progressFormat = 'Downloading [:bar] :percent :etas '; + const progressFormat = 'Downloading [:bar] :percent-4 :eta-3s '; stream = fs.createWriteStream(filePath); if (path.isAbsolute(fileUri)) { @@ -41,7 +41,7 @@ function downloadFileAsync(filePath, fileUri) { client.get(fileUri, (res) => { if (res.statusCode === 200) { let totalBytes = parseInt(res.headers['content-length'], 10); - let progressFormat = 'Downloading [:bar] :percent :etas '; + let progressFormat = 'Downloading [:bar] :percent-4 :eta-3s '; if (!settings.quiet && totalBytes > 100000) { res.pipe(streamProgress(progressFormat, { complete: '#', @@ -163,7 +163,7 @@ function streamProgress(progressFormat, options) { let progressBar = new ProgressBar(progressFormat, options); passThrough.on('data', chunk => { if (progressBar.curr + chunk.length >= progressBar.total) { - let finalFormat = progressFormat.replace(/:etas/, ' '); + let finalFormat = progressFormat.replace(/:eta-3s/, ' '); progressBar.fmt = finalFormat; } progressBar.tick(chunk.length); diff --git a/lib/extract.js b/lib/extract.js index cc1676f..ef407b0 100644 --- a/lib/extract.js +++ b/lib/extract.js @@ -1,7 +1,7 @@ /* global settings */ let childProcess = require('child_process'); // Non-const enables test mocking const path = require('path'); -const ProgressBar = require('progress'); +const ProgressBar = require('../deps/progress'); const Error = require('./error'); @@ -40,7 +40,7 @@ function extractZipArchiveAsync(archiveFile, targetDir) { } return new Promise((resolve, reject) => { - let progressFormat = '\x1b[1GExtracting [:bar] :percent :etas '; + let progressFormat = '\x1b[1GExtracting [:bar] :percent-4 :eta-3s '; let progressBar = null; let nextData = ''; if (!settings.quiet && totalFiles > 10) { @@ -77,7 +77,7 @@ function extractZipArchiveAsync(archiveFile, targetDir) { } if (progressBar.curr + fileCount >= totalFiles) { - let finalFormat = progressFormat.replace(/:etas/, ' '); + let finalFormat = progressFormat.replace(/:eta-3s/, ' '); progressBar.fmt = finalFormat; } progressBar.tick(fileCount); @@ -131,7 +131,7 @@ function extractTarArchiveAsync(archiveFile, targetDir) { } return new Promise((resolve, reject) => { - let progressFormat = 'Extracting [:bar] :percent :etas '; + let progressFormat = 'Extracting [:bar] :percent-4 :eta-3s '; let progressBar = null; if (!settings.quiet && totalFiles > 10) { progressBar = new ProgressBar(progressFormat, { @@ -158,7 +158,7 @@ function extractTarArchiveAsync(archiveFile, targetDir) { if (progressBar) { let fileCount = countChars(data.toString(), '\n'); if (progressBar.curr + fileCount >= totalFiles) { - let finalFormat = progressFormat.replace(/:etas/, ' '); + let finalFormat = progressFormat.replace(/:eta-3s/, ' '); progressBar.fmt = finalFormat; } progressBar.tick(fileCount); diff --git a/node_modules/progress/package.json b/node_modules/progress/package.json deleted file mode 100644 index 1815528..0000000 --- a/node_modules/progress/package.json +++ /dev/null @@ -1,103 +0,0 @@ -{ - "_args": [ - [ - { - "raw": "progress", - "scope": null, - "escapedName": "progress", - "name": "progress", - "rawSpec": "", - "spec": "latest", - "type": "tag" - }, - "/Users/Jason/Projects/nvs" - ] - ], - "_from": "progress@latest", - "_id": "progress@1.1.8", - "_inCache": true, - "_location": "/progress", - "_npmUser": { - "name": "prezjordan", - "email": "scalesjordan@gmail.com" - }, - "_npmVersion": "1.4.14", - "_phantomChildren": {}, - "_requested": { - "raw": "progress", - "scope": null, - "escapedName": "progress", - "name": "progress", - "rawSpec": "", - "spec": "latest", - "type": "tag" - }, - "_requiredBy": [ - "#USER", - "/", - "/eslint" - ], - "_resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", - "_shasum": "e260c78f6161cdd9b0e56cc3e0a85de17c7a57be", - "_shrinkwrap": null, - "_spec": "progress", - "_where": "/Users/Jason/Projects/nvs", - "author": { - "name": "TJ Holowaychuk", - "email": "tj@vision-media.ca" - }, - "bugs": { - "url": "https://github.com/visionmedia/node-progress/issues" - }, - "contributors": [ - { - "name": "Christoffer Hallas", - "email": "christoffer.hallas@gmail.com" - }, - { - "name": "Jordan Scales", - "email": "scalesjordan@gmail.com" - } - ], - "dependencies": {}, - "description": "Flexible ascii progress bar", - "devDependencies": {}, - "directories": {}, - "dist": { - "shasum": "e260c78f6161cdd9b0e56cc3e0a85de17c7a57be", - "tarball": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz" - }, - "engines": { - "node": ">=0.4.0" - }, - "gitHead": "6b9524c0d07df9555d20ae95c65918020c50e3e2", - "homepage": "https://github.com/visionmedia/node-progress", - "keywords": [ - "cli", - "progress" - ], - "main": "index", - "maintainers": [ - { - "name": "tjholowaychuk", - "email": "tj@vision-media.ca" - }, - { - "name": "hallas", - "email": "christoffer.hallas@forsvikgroup.com" - }, - { - "name": "prezjordan", - "email": "scalesjordan@gmail.com" - } - ], - "name": "progress", - "optionalDependencies": {}, - "readme": "ERROR: No README data found!", - "repository": { - "type": "git", - "url": "git://github.com/visionmedia/node-progress.git" - }, - "scripts": {}, - "version": "1.1.8" -} diff --git a/setup/NvsSetup.wixproj b/setup/NvsSetup.wixproj index cfca797..46f146b 100644 --- a/setup/NvsSetup.wixproj +++ b/setup/NvsSetup.wixproj @@ -14,7 +14,7 @@ $(MSBuildExtensionsPath32)\Microsoft\WiX\v3.x\Wix.targets $(MSBuildExtensionsPath)\Microsoft\WiX\v3.x\Wix.targets ICE105 - NvsLibDir=..\lib\;NvsDocDir=..\doc\;NMProgressDir=..\node_modules\progress\ + NvsLibDir=..\lib\;NvsDocDir=..\doc\;NvsDepsDir=..\deps\ $(HeatDirPaths) $(Configuration)\ $(OutputPath) @@ -34,7 +34,7 @@ - + @@ -68,7 +68,7 @@ - + diff --git a/setup/nvs.wxs b/setup/nvs.wxs index 83fcab5..a6d218f 100644 --- a/setup/nvs.wxs +++ b/setup/nvs.wxs @@ -52,7 +52,7 @@ - +