'use strict'; /** * `rawlist` type prompt */ var util = require('util'); var Separator = require('../objects/separator'); var Paginator = require('../utils/paginator'); var observe = require('../utils/events'); var utils = require('../utils/'); var Base = require('./base'); /** * Module exports */ module.exports = Prompt; /** * Constructor */ function Prompt() { Base.apply(this, arguments); if (!this.opt.choices) { this.throwParamError('choices'); } this.validateChoices(this.opt.choices); // Add the default `help` (/expand) option this.opt.choices.push({ key: 'h', name: 'Help, list all options', value: 'help' }); // Setup the default string (capitalize the default key) this.opt.default = this.generateChoicesString(this.opt.choices, this.opt.default); this.paginator = new Paginator(); } util.inherits(Prompt, Base); /** * Start the Inquiry session * @param {Function} cb Callback when prompt is done * @return {this} */ Prompt.prototype._run = function(cb) { this.done = cb; // Save user answer and update prompt to show selected option. var events = observe(this.rl); this.lineObs = events.line.forEach(this.onSubmit.bind(this)); this.keypressObs = events.keypress.forEach(this.onKeypress.bind(this)); // Init the prompt this.render(); return this; }; /** * Render the prompt to screen * @return {Prompt} self */ Prompt.prototype.render = function(error, hint) { var cursor = 0; var message = this.getQuestion(); if (this.status === 'answered') { message += utils.chalk.cyan(this.selected.name); } else if (this.status === 'expanded') { var choicesStr = renderChoices(this.opt.choices, this.selectedKey); message += this.paginator.paginate(choicesStr, this.selectedKey, this.opt.pageSize); message += '\n Answer: '; } message += this.rl.line; if (error) { message += '\n' + utils.chalk.red('>> ') + error; cursor++; } if (hint) { message += '\n' + utils.chalk.cyan('>> ') + hint; cursor++; } this.screen.render(message, { cursor: cursor }); }; /** * Generate the prompt choices string * @return {String} Choices string */ Prompt.prototype.getChoices = function() { var output = ''; this.opt.choices.forEach(function(choice, i) { output += '\n '; if (choice.type === 'separator') { output += ' ' + choice; return; } var choiceStr = choice.key + ') ' + choice.name; if (this.selectedKey === choice.key) { choiceStr = utils.chalk.cyan(choiceStr); } output += choiceStr; }.bind(this)); return output; }; /** * When user press `enter` key */ Prompt.prototype.onSubmit = function(input) { if (input == null || input === '') { input = this.rawDefault; } var selected = this.opt.choices.where({ key: input.toLowerCase() })[0]; if (selected != null && selected.key === 'h') { this.selectedKey = ''; this.status = 'expanded'; this.render(); return; } if (selected != null) { this.status = 'answered'; this.selected = selected; // Re-render prompt this.render(); this.lineObs.dispose(); this.keypressObs.dispose(); this.screen.done(); this.done(this.selected.value); return; } // Input is invalid this.render('Please enter a valid command'); }; /** * When user press a key */ Prompt.prototype.onKeypress = function(s, key) { this.selectedKey = this.rl.line.toLowerCase(); var selected = this.opt.choices.where({ key: this.selectedKey })[0]; if (this.status === 'expanded') { this.render(); } else { this.render(null, selected ? selected.name : null); } }; /** * Validate the choices * @param {Array} choices */ Prompt.prototype.validateChoices = function(choices) { var formatError; var errors = []; var keymap = {}; choices.filter(Separator.exclude).map(function(choice) { if (!choice.key || choice.key.length !== 1) { formatError = true; } if (keymap[choice.key]) { errors.push(choice.key); } keymap[choice.key] = true; choice.key = String(choice.key).toLowerCase(); }); if (formatError) { throw new Error('Format error: `key` param must be a single letter and is required.'); } if (keymap.h) { throw new Error('Reserved key error: `key` param cannot be `h` - this value is reserved.'); } if (errors.length) { throw new Error('Duplicate key error: `key` param must be unique. Duplicates: ' + utils.unique(errors).join(', ')); } }; /** * Generate a string out of the choices keys * @param {Array} choices * @param {Number} defaultIndex - the choice index to capitalize * @return {String} The rendered choices key string */ Prompt.prototype.generateChoicesString = function(choices, defaultIndex) { var defIndex = 0; if (utils.isNumber(defaultIndex) && this.opt.choices.getChoice(defaultIndex)) { defIndex = defaultIndex; } var defStr = this.opt.choices.pluck('key'); this.rawDefault = defStr[defIndex]; defStr[defIndex] = String(defStr[defIndex]).toUpperCase(); return defStr.join(''); }; /** * Function for rendering checkbox choices * @param {String} pointer Selected key * @return {String} Rendered content */ function renderChoices(choices, pointer) { var output = ''; choices.forEach(function(choice, i) { output += '\n '; if (choice.type === 'separator') { output += ' ' + choice; return; } var choiceStr = choice.key + ') ' + choice.name; if (pointer === choice.key) { choiceStr = utils.chalk.cyan(choiceStr); } output += choiceStr; }); return output; }