简体   繁体   English

根据Node.js中的问题数组创建一系列提示

[英]Create Series of Prompts from Array of Questions in Node.js

I am creating a command line tool in node. 我正在节点中创建命令行工具。 After the user enters a specific command I parse through the options they have entered. 用户输入特定命令后,我将解析他们输入的选项。

> mycommand init --name name --email user@domain.com

I then validate the values of the options. 然后,我验证选项的值。 After validation I generate an array of questions that were not answered from the options the user entered and from the entered options that did not pass validation. 验证之后,我生成了一系列问题,这些问题无法通过用户输入的选项以及未通过验证的输入选项来回答。 Once the array has been generated, I loop through it and give a prompt for each question: 生成数组后,我将遍历该数组并针对每个问题给出提示:

    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();
        });
    }

The problem is, the code loops through the entire array without waiting for the user input and ends with a final prompt displaying a message from the last item in the array. 问题是,代码在不等待用户输入的情况下循环遍历整个数组,并以显示来自数组最后一项的消息的最终提示结尾。 When I try to type a response, each keystroke is multiplied by the number of items in the array. 当我尝试键入响应时,每个击键都将乘以数组中的项目数。 For instance, attempting to type "myname" results in: 例如,尝试输入“ myname”将导致:

> Enter Your Name (none): mmmmyyyynnnnaaaammmmeeee

I have tried multiple node modules including read and prompt and am experienceing the same problem So I must not fully understand how command line tools read user input. 我已经尝试了多个节点模块,包括读取提示,并且遇到了相同的问题,所以我不能完全理解命令行工具如何读取用户输入。

Any insight on how to solve this problem would be appreciated. 任何有关如何解决此问题的见解将不胜感激。

Thanks. 谢谢。

So, I think the problem is that it's creating callbacks and the loop continues on so you're getting weird results. 因此,我认为问题在于它正在创建回调,并且循环继续进行,因此您得到的结果很奇怪。 Using a variant of the Tiny CLI example, you can create a prompt with a question from the array and watch the line event to get the input, then repeat. 使用Tiny CLI示例的变体,您可以从阵列中创建一个带有问题的提示,并观察line事件以获取输入,然后重复。 Here's a quick example. 这是一个简单的例子。

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);
    }
});

I wrote some utility methods to help with this. 我写了一些实用的方法来解决这个问题。 Couple of added gems in here like it automatically writes the last values entered to a hidden file so a user can press enter 像这样在其中添加了几对宝石,它会自动将最后输入的值写入一个隐藏文件,以便用户可以按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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM