From f12b33e7614953464a1f90122f3e015057e3670c Mon Sep 17 00:00:00 2001 From: Pascal Hartig Date: Sat, 13 Apr 2013 01:39:20 +0200 Subject: [PATCH 1/2] Replaced 'read' with high-level 'prompt' lib /ref #199 This changes the prompt API slightly and adds the following features: - `grunt-init` style coloring - `default` can be a function (in preperation of "magic defaults") - if `default` is a boolean, it will display a 'y/n' prompt with proper validation - validations occurs after a value is entered and not after the whole wizard completed --- lib/actions/prompt.js | 64 +++++++++++++++++++++++++++++++++--------- lib/util/conflicter.js | 4 +-- package.json | 4 +-- 3 files changed, 55 insertions(+), 17 deletions(-) diff --git a/lib/actions/prompt.js b/lib/actions/prompt.js index 43af5d22..e70072d8 100644 --- a/lib/actions/prompt.js +++ b/lib/actions/prompt.js @@ -1,35 +1,73 @@ -var read = require('read'); +'use strict'; + +var prompt_ = require('prompt'); +var _ = require('lodash'); +var exports = module.exports; + + +function evaluatePrompts(prompt) { + if (_.isFunction(prompt.default)) { + prompt.default = prompt.default(); + } else if (typeof prompt.default === 'boolean') { + // Handle boolean defaults as confirmation prompts. + var defaultMsg = prompt.default ? 'Y/n' : 'y/N'; + prompt.default = defaultMsg; + + prompt.validator = function (value) { + return value.match(/^([yYnN]|(y\/N)|(Y\/n))$/); + }; + prompt.required = true; + + prompt.before = function (val) { + if (val === 'Y/n' || val.toLowerCase() === 'y') { + return true; + } else if (val === 'y/N' || val.toLowerCase() === 'n') { + return false; + } + + return val; + }; + } + + return prompt; +} // Prompt for user input based on the given Array of `prompts` to perform in // series, and call `done` callback on completion. `prompts` can be a single // Hash of options in which case a single prompt is performed. // -// Options can be any read's option: https://github.com/isaacs/read#options +// Options can be any prompt's option: https://npmjs.org/package/prompt // // - prompts - A single or an Array of Hash options. // - done - Callback to call on error or on completion. // // Returns the generator instance. -module.exports = function prompt(prompts, done) { +exports.prompt = function prompt(prompts, done) { prompts = Array.isArray(prompts) ? prompts : [prompts]; - var results = {}; - (function next(prompt) { - if (!prompt) { - return done(null, results); - } + prompts = prompts.map(evaluatePrompts); - if (!prompt.prompt) { - prompt.prompt = prompt.message; - } + prompt_.message = '[' + '?'.green + ']'; + prompt_.delimiter = ' '; + prompt_.start(); - read(prompt, function (err, value) { + var results = {}; + (function next(prompt) { + function handleResult(err, value) { if (err) { return done(err); } + results[prompt.name] = value; next(prompts.shift()); - }); + } + + if (!prompt) { + return done(null, results); + } + + prompt_.get(prompt, handleResult); })(prompts.shift()); + return this; }; diff --git a/lib/util/conflicter.js b/lib/util/conflicter.js index a40d9ce3..5d600b03 100644 --- a/lib/util/conflicter.js +++ b/lib/util/conflicter.js @@ -94,7 +94,7 @@ conflicter._ask = function (filepath, content, cb) { return cb(err); } - var answer = result.overwrite; + var answer = result.overwrite.overwrite; var ok = 'Yynaqdh'.split('').some(function (valid) { return valid === answer; @@ -198,4 +198,4 @@ conflicter.colorLines = function colorLines(name, str) { return str.split('\n').map(function (str) { return conflicter.color(name, str); }).join('\n'); -}; +}; \ No newline at end of file diff --git a/package.json b/package.json index 336415dc..eb83c3c7 100644 --- a/package.json +++ b/package.json @@ -34,12 +34,12 @@ "underscore.string": "~2.3.1", "lodash": "~1.1.1", "mkdirp": "~0.3.5", - "read": "~1.0.4", "glob": "~3.1.21", "nopt": "~2.1.1", "cli-table": "~0.2.0", "debug": "~0.7.2", - "isbinaryfile": "~0.1.8" + "isbinaryfile": "~0.1.8", + "prompt": "~0.2.9" }, "devDependencies": { "mocha": "~1.9.0", From 71495d87163b3837eb0029ffd01c493ceab91819 Mon Sep 17 00:00:00 2001 From: Pascal Hartig Date: Sun, 14 Apr 2013 03:57:07 +0200 Subject: [PATCH 2/2] Monkey-patch prompt to remove verbose error message --- lib/actions/prompt.js | 46 ++++++++++++++++++++++++++++++++++++++++-- lib/util/conflicter.js | 2 +- package.json | 3 ++- test/conflicter.js | 2 +- 4 files changed, 48 insertions(+), 5 deletions(-) diff --git a/lib/actions/prompt.js b/lib/actions/prompt.js index e70072d8..ec17ad55 100644 --- a/lib/actions/prompt.js +++ b/lib/actions/prompt.js @@ -2,7 +2,7 @@ var prompt_ = require('prompt'); var _ = require('lodash'); -var exports = module.exports; +var validate = require('revalidator').validate; function evaluatePrompts(prompt) { @@ -32,6 +32,48 @@ function evaluatePrompts(prompt) { return prompt; } +// Monkey-patching prompt._performValidation to get rid of the overly verbose +// error message. +// +// Arguments: +// - name {Object} Variable name +// - prop {Object|string} Variable to get input for. +// - against {Object} Input +// - schema {Object} Validation schema +// - line {String|Boolean} Input line +// - callback {function} Continuation to pass control to when complete. +// +// Perfoms user input validation, print errors if needed and returns value +// according to validation +// +prompt_._performValidation = function (name, prop, against, schema, line, callback) { + var numericInput, valid, msg; + + try { + valid = validate(against, schema); + } catch (err) { + return (line !== -1) ? callback(err) : false; + } + + if (!valid.valid) { + msg = 'Invalid input'; + + if (prompt_.colors) { + prompt_.logger.error(msg); + } else { + prompt_.logger.error(msg); + } + + if (prop.schema.message) { + prompt_.logger.error(prop.schema.message); + } + + prompt_.emit('invalid', prop, line); + } + + return valid.valid; +}; + // Prompt for user input based on the given Array of `prompts` to perform in // series, and call `done` callback on completion. `prompts` can be a single // Hash of options in which case a single prompt is performed. @@ -42,7 +84,7 @@ function evaluatePrompts(prompt) { // - done - Callback to call on error or on completion. // // Returns the generator instance. -exports.prompt = function prompt(prompts, done) { +module.exports = function prompt(prompts, done) { prompts = Array.isArray(prompts) ? prompts : [prompts]; prompts = prompts.map(evaluatePrompts); diff --git a/lib/util/conflicter.js b/lib/util/conflicter.js index 5d600b03..e44e7058 100644 --- a/lib/util/conflicter.js +++ b/lib/util/conflicter.js @@ -198,4 +198,4 @@ conflicter.colorLines = function colorLines(name, str) { return str.split('\n').map(function (str) { return conflicter.color(name, str); }).join('\n'); -}; \ No newline at end of file +}; diff --git a/package.json b/package.json index eb83c3c7..5f50a8a1 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,8 @@ "cli-table": "~0.2.0", "debug": "~0.7.2", "isbinaryfile": "~0.1.8", - "prompt": "~0.2.9" + "prompt": "~0.2.9", + "revalidator": "~0.1.5" }, "devDependencies": { "mocha": "~1.9.0", diff --git a/test/conflicter.js b/test/conflicter.js index 598b384d..6d5de49b 100644 --- a/test/conflicter.js +++ b/test/conflicter.js @@ -83,7 +83,7 @@ describe('conflicter', function () { describe('conflicter#_ask', function () { var promptMock = { prompt: function (config, cb) { - cb(this.err, { overwrite: this.answer }); + cb(this.err, { overwrite: { overwrite: this.answer } }); }, err: null, answer: null