[英]Create Series of Prompts from Array of Questions in Node.js
我正在节点中创建命令行工具。 用户输入特定命令后,我将解析他们输入的选项。
> mycommand init --name name --email user@domain.com
然后,我验证选项的值。 验证之后,我生成了一系列问题,这些问题无法通过用户输入的选项以及未通过验证的输入选项来回答。 生成数组后,我将遍历该数组并针对每个问题给出提示:
for(var i = 0; i < questions.length; i++) {
var prop = questions[i],
title = messages.prompts.init[prop].question,
def = messages.prompts.init[prop].def,
input = read.createInterface(process.stdin, process.stdout);
if(messages.prompts.init[prop].hasOwnProperty('format')){
title = title + ' <' + messages.prompts.init[prop].format + '> ';
}
input.question(title + ' (' + def + ')', function (a) {
//dosomething(a);
process.exit();
});
}
问题是,代码在不等待用户输入的情况下循环遍历整个数组,并以显示来自数组最后一项的消息的最终提示结尾。 当我尝试键入响应时,每个击键都将乘以数组中的项目数。 例如,尝试输入“ myname”将导致:
> Enter Your Name (none): mmmmyyyynnnnaaaammmmeeee
我已经尝试了多个节点模块,包括读取和提示,并且遇到了相同的问题,所以我不能完全理解命令行工具如何读取用户输入。
任何有关如何解决此问题的见解将不胜感激。
谢谢。
因此,我认为问题在于它正在创建回调,并且循环继续进行,因此您得到的结果很奇怪。 使用Tiny CLI示例的变体,您可以从阵列中创建一个带有问题的提示,并观察line事件以获取输入,然后重复。 这是一个简单的例子。
var read = require('readline'),
input = read.createInterface(process.stdin, process.stdout),
questions = ['test1: ', 'test2: '],
counter = 0;
input.setPrompt(questions[0]);
input.prompt();
input.on('line', function (a) {
console.log('answer: ', a);
counter++;
if (counter < questions.length) {
input.setPrompt(questions[counter]);
input.prompt();
} else {
process.exit(0);
}
});
我写了一些实用的方法来解决这个问题。 像这样在其中添加了几对宝石,它会自动将最后输入的值写入一个隐藏文件,以便用户可以按Enter键
```
var async = require('async');
var fileUtils = require('../util/file-util');
var LAST_OPTIONS_FILE='./.last-options-file';
/**
* The prompt object type that is used by askSeries
* @typedef {Object} Prompt
* @property {string} key - is used as the variable name
* @property {string} [default] - is a default value so you can just press enter
* @property {string} [format] - validates input against a regex
* @property {string} [question] - use to make a more human readable prompt
* @property {boolean} [confirm] - will force user to confirm the value if it was found on the command line
* @property {boolean} [forceEnter] - if true, user must enter this value (last value is ignored)
*/
/**
* Asks user for input for each of the prompts
*
* <PRE>
* Example:
* askSeries([{key:"customerId"}, {key:"subscriptionId", "default":"blah"}], function(inputs) {
* console.log(inputs);
* });
* OUTPUT:
* customerId: 5
* subscriptionId [blah]: 9
* [ customerId: '5', subscriptionId: '9' ]
*
* askSeries([{key:"customerId", question:"Enter Customer Id", format: /\d+/}, {key:"subscriptionId", "default":"blah"}], function(inputs) {
* console.log(inputs);
* });
* OUTPUT:
* Enter Customer Id: abc
* It should match: /\d+/
* Enter Customer Id: 123
* subscriptionId [blah]:
* [ customerId: '123', subscriptionId: 'blah' ]
* </PRE>
*
* @param {Prompt[]} prompts - an array of Prompts which dictate what user should be asked
* @param {object} [argv] - if any of prompts .keys match argv it sets the default appropriately,
* argv will get all prompt .key values set on it as well
* @param {function} callback - signature is function(err,params)
*/
exports.askSeries = function(prompts,argv,callback) {
var input = {};
var lastVal = {};
if(typeof argv === 'function') { callback = argv; argv=null; }
lastVal = fileUtils.readJSONFileSync(LAST_OPTIONS_FILE);
if( !lastVal ) { lastVal = {}; }
console.log("LASTVAL", lastVal);
async.eachSeries(prompts, function(prompt, next) {
if( !prompt.key ) { callback(new Error("prompt doesn't have required 'key' param")); }
if( !prompt.confirm && argv && argv[prompt.key] ) {
input[prompt.key] = argv[prompt.key];
return next();
}
else {
var defaultVal = prompt.default;
if( argv && argv[prompt.key] ) { defaultVal = argv[prompt.key]; }
else if( !prompt.forceEnter && lastVal[prompt.key] ) { defaultVal = lastVal[prompt.key]; }
exports.ask( prompt.question || prompt.key, prompt.format || /.+|/, defaultVal, function(value) {
if( !value ) {
if( prompt.default ) {
value = prompt.default;
}
if( argv && argv[prompt.key] ) {
value = argv[prompt.key];
}
}
input[prompt.key] = value;
if( argv ) { argv[key] = value;}
next();
});
}
}, function(err) {
try {
var fileData = JSON.stringify(input);
fileUtils.writeToFile(LAST_OPTIONS_FILE, fileData );
}catch(err) { console.log("Unable to save entered values"); }
callback(err,input);
});
};
/**
* Prompts user for input
*
* @param {string} question prompt that is displayed to the user
* @param {string} [format] regex used to validate
* @param {string} [defaultVal] uses this value if enter is pressed
* @param callback is invoked with value input as callback(value);
*/
exports.ask = function ask(question, format, defaultVal, callback) {
var stdin = process.stdin, stdout = process.stdout;
if( typeof(format) === 'function' ) {
callback = format;
format = null;
}
if( typeof(defaultVal) === 'function') {
callback = defaultVal;
defaultVal = null;
}
stdin.resume();
var prompt = question;
if( defaultVal ) {
prompt += " [" + defaultVal + "]";
}
prompt += ": ";
stdout.write(prompt);
stdin.once('data', function(data) {
data = data.toString().trim();
if( !data && defaultVal ) {
data = defaultVal;
}
if (!format || format.test(data)) {
callback(data);
} else {
stdout.write("It should match: "+ format +"\n");
ask(question, format, callback);
}
});
};
/**
* Prints the usage from the <link Prompt> array.
* @param {Prompt[]} prompts - the array of prompts that will be asked if not entered on cmd line.
* @param {string} binaryName - if specified it puts it in the usage as <binaryName> [options]
* @param {boolean} [dontPrint] - if true, the usage will not be written to console
*/
exports.getUsage = function(prompts, binaryName, dontPrint) {
if(!binaryName) { binaryName = "program_name";}
var s = "\nUsage: \n./" + binaryName + " [options]\n\n options:\n";
console.log(prompts);
prompts.forEach( function(p) {
s += " --" + p.key;
if(p.question) { s += ": " + p.question;}
if(p.format) { s += ", format: " + p.format; }
s += "\n";
});
if( !dontPrint ) {
console.log( s );
}
return s;
};fs
```
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.