2014-11-01 21 views
0

我正在節點中創建一個命令行工具。用戶輸入特定命令後,我將解析他們輸入的選項。從Node.js中的問題陣列創建一系列提示

> mycommand init --name name --email [email protected] 

然後驗證選項的值。驗證之後,我生成一系列問題,這些問題未從用戶輸入的選項以及未通過驗證的輸入選項中回答。一旦陣列已經產生的,我循環通過它並給出提示針對每個問題:

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 

我已經試過多個節點模塊包括readprompt和我experienceing同樣的問題,所以我不能完全理解命令行工具如何讀取用戶輸入。

任何有關如何解決這個問題的意見,將不勝感激。

謝謝。

回答

1

所以,我認爲問題在於它創建了回調函數,並且循環繼續,所以你得到了奇怪的結果。使用Tiny CLI示例的變體,您可以使用數組中的問題創建提示,並觀察線條事件以獲取輸入,然後重複。這是一個簡單的例子。

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

這是完美的。謝謝你的回答和明確的解釋。 – 2014-11-01 21:39:14

0

我寫了一些實用方法來幫助解決這個問題。在這裏喜歡它 夫婦加寶石的自動寫入輸入的一個隱藏文件的最後一個值,這樣用戶就可以按回車

```

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 

```