console.js
* @author Hermann Mayer <hermann.mayer92@gmail.com>
var colors = require('colors');
* Simple process.stdout.write wrapper.
* @param {String} str - String to print
Console.prototype.print = function(str)
process.stdout.write(((str || '\n') + '').white);
* Print a given message as a section.
* @param {String} str - String to print as section
Console.prototype.section = function(str)
this.print('\n ' + msg.yellow + '\n');
* Print a given message as a heading.
* @param {String} str - String to print as heading
Console.prototype.heading = function(str)
for (var i = 0; i < str.length; i += 1) {
(sep + '\n' + '| ').white + str.red.bold +
(' |' + '\n' + sep).white + '\n\n'
Console.prototype.clear = function()
process.stdout.write('\033[2J\033[;H');
* @param {Object} options - Options to use
* @param {Function} callback - Function to call on finish
* @param {Function} abort - Function to call on abort
Console.prototype.ask = function(options, callback, abort)
options.prompt = options.prompt || '';
var completion = function(line)
var hits = (options.values || []).filter(function(c) {
return [hits.length ? hits : options.values, line];
// process.stdin.removeAllListeners('data');
// console.log(process.stdout.listeners('data'));
var rl = (require('readline')).createInterface({
return callback && callback('Aborted');
self.print('(^C again to break)\n');
'Available values:\n' + ' * '.red + options.values.join('\n * '.red) + '\n\n'
if (options.hint || options.validator) {
this.print(' Hint: '.yellow.bold);
if (options.validator && options.validator instanceof RegExp) {
this.print('Regex: ' + ('' + options.validator).bold.green);
if (options.validator && options.validator instanceof RegExp) {
var validator = options.validator || null;
var prompt = ' ' + options.prompt;
prompt += ' [' + options.default + ']';
// Set default on empty inputs or false
// if no default value was specified
input = options.default ? options.default : '';
// Check for a regex validator callback
if (validator instanceof RegExp) {
if (null !== input.match(validator)) {
return callback && callback(null, input);
' Input ' + input.green.bold + ' is not valid (' +
('' + validator).green.bold + ').\n\n'
// Start a recursive copy on faulty inputs to prompt again
return rl.question(prompt, handler);
if (validator instanceof Function) {
if (true === validator(input)) {
return callback && callback(null, input);
' Input ' + input.green.bold + ' is not valid.\n\n'
// Start a recursive copy on faulty inputs to prompt again
return rl.question(prompt, handler);
// No input and no default value was given, repeat the question
'\n[Error]'.red.bold + ' Input was empty.\n\n'
// Start a recursive copy on faulty inputs to prompt again
return rl.question(prompt, handler);
// If we find input in the given values everything went well
if (-1 !== options.values.indexOf(input)) {
return callback && callback(null, input);
'\n[Error]'.red.bold + ' Input "' + ('' + input).green.bold +
// Start a recursive copy on faulty inputs to prompt again
return rl.question(prompt, handler);
return callback && callback(null, input);
* Build a new Question Object.
Console.prototype.buildQuestion = function()
return new (Question.constructor.bind.apply(Question, args))();
* Build a new QuestionSet Object.
Console.prototype.buildQuestionSet = function()
return new (QuestionSet.constructor.bind.apply(QuestionSet, args))();
var QuestionSet = function(options, questions)
throw new Error('No options was specified');
throw new Error('No question id was specified');
throw new Error('No description was specified');
throw new Error('No question was specified');
this.description = options.description;
this.repeat = options.repeat || false;
this.preAsk = options.preAsk || false;
this.postAsk = options.postAsk || false;
* Ask the questions of the question set.
* @param {Function} callback - Function to call on finish
QuestionSet.prototype.ask = function(callback)
'\n[Question Set] '.red + this.description.white +
' (Press Ctrl+C to exit the loop)' + '\n'
async.forever(function(feCallback) {
async.mapSeries(self.questions, function(question, callback) {
question.ask(function(err, data) {
return callback && callback(err);
return self.preAsk(question, ask);
return feCallback && feCallback(err);
results.forEach(function(result) {
resultSet[result.id] = result.value;
return feCallback && feCallback('Done');
return self.postAsk(results, feCallback);
// Next run for the forever loop
self.answers.forEach(function(result) {
Object.keys(result).forEach(function(key) {
entry[key] = result[key].result;
var Question = function(options)
throw new Error('No options was specified');
throw new Error('No question was specified');
throw new Error('No question id was specified');
* @param {Function} callback - Function to call on finish
Question.prototype.ask = function(callback)
this.console.print('\n -- ' + this.options.question.yellow + '\n\n');