| 'use strict'; |
| /** |
| * `rawlist` type prompt |
| */ |
| |
| var _ = require('lodash'); |
| var chalk = require('chalk'); |
| var { map, takeUntil } = require('rxjs/operators'); |
| var Base = require('./base'); |
| var Separator = require('../objects/separator'); |
| var observe = require('../utils/events'); |
| var Paginator = require('../utils/paginator'); |
| |
| class ExpandPrompt extends Base { |
| constructor(questions, rl, answers) { |
| super(questions, rl, answers); |
| |
| 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' |
| }); |
| |
| this.opt.validate = choice => { |
| if (choice == null) { |
| return 'Please enter a valid command'; |
| } |
| |
| return choice !== 'help'; |
| }; |
| |
| // Setup the default string (capitalize the default key) |
| this.opt.default = this.generateChoicesString(this.opt.choices, this.opt.default); |
| |
| this.paginator = new Paginator(this.screen); |
| } |
| |
| /** |
| * Start the Inquiry session |
| * @param {Function} cb Callback when prompt is done |
| * @return {this} |
| */ |
| |
| _run(cb) { |
| this.done = cb; |
| |
| // Save user answer and update prompt to show selected option. |
| var events = observe(this.rl); |
| var validation = this.handleSubmitEvents( |
| events.line.pipe(map(this.getCurrentValue.bind(this))) |
| ); |
| validation.success.forEach(this.onSubmit.bind(this)); |
| validation.error.forEach(this.onError.bind(this)); |
| this.keypressObs = events.keypress |
| .pipe(takeUntil(validation.success)) |
| .forEach(this.onKeypress.bind(this)); |
| |
| // Init the prompt |
| this.render(); |
| |
| return this; |
| } |
| |
| /** |
| * Render the prompt to screen |
| * @return {ExpandPrompt} self |
| */ |
| |
| render(error, hint) { |
| var message = this.getQuestion(); |
| var bottomContent = ''; |
| |
| if (this.status === 'answered') { |
| message += chalk.cyan(this.answer); |
| } 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) { |
| bottomContent = chalk.red('>> ') + error; |
| } |
| |
| if (hint) { |
| bottomContent = chalk.cyan('>> ') + hint; |
| } |
| |
| this.screen.render(message, bottomContent); |
| } |
| |
| getCurrentValue(input) { |
| if (!input) { |
| input = this.rawDefault; |
| } |
| |
| var selected = this.opt.choices.where({ key: input.toLowerCase().trim() })[0]; |
| if (!selected) { |
| return null; |
| } |
| |
| return selected.value; |
| } |
| |
| /** |
| * Generate the prompt choices string |
| * @return {String} Choices string |
| */ |
| |
| getChoices() { |
| var output = ''; |
| |
| this.opt.choices.forEach(choice => { |
| output += '\n '; |
| |
| if (choice.type === 'separator') { |
| output += ' ' + choice; |
| return; |
| } |
| |
| var choiceStr = choice.key + ') ' + choice.name; |
| if (this.selectedKey === choice.key) { |
| choiceStr = chalk.cyan(choiceStr); |
| } |
| |
| output += choiceStr; |
| }); |
| |
| return output; |
| } |
| |
| onError(state) { |
| if (state.value === 'help') { |
| this.selectedKey = ''; |
| this.status = 'expanded'; |
| this.render(); |
| return; |
| } |
| |
| this.render(state.isValid); |
| } |
| |
| /** |
| * When user press `enter` key |
| */ |
| |
| onSubmit(state) { |
| this.status = 'answered'; |
| var choice = this.opt.choices.where({ value: state.value })[0]; |
| this.answer = choice.short || choice.name; |
| |
| // Re-render prompt |
| this.render(); |
| this.screen.done(); |
| this.done(state.value); |
| } |
| |
| /** |
| * When user press a key |
| */ |
| |
| onKeypress() { |
| 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 |
| */ |
| |
| validateChoices(choices) { |
| var formatError; |
| var errors = []; |
| var keymap = {}; |
| choices.filter(Separator.exclude).forEach(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: ' + |
| _.uniq(errors).join(', ') |
| ); |
| } |
| } |
| |
| /** |
| * Generate a string out of the choices keys |
| * @param {Array} choices |
| * @param {Number|String} default - the choice index or name to capitalize |
| * @return {String} The rendered choices key string |
| */ |
| generateChoicesString(choices, defaultChoice) { |
| var defIndex = choices.realLength - 1; |
| if (_.isNumber(defaultChoice) && this.opt.choices.getChoice(defaultChoice)) { |
| defIndex = defaultChoice; |
| } else if (_.isString(defaultChoice)) { |
| let index = _.findIndex( |
| choices.realChoices, |
| ({ value }) => value === defaultChoice |
| ); |
| defIndex = index === -1 ? defIndex : index; |
| } |
| |
| 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(choice => { |
| output += '\n '; |
| |
| if (choice.type === 'separator') { |
| output += ' ' + choice; |
| return; |
| } |
| |
| var choiceStr = choice.key + ') ' + choice.name; |
| if (pointer === choice.key) { |
| choiceStr = chalk.cyan(choiceStr); |
| } |
| |
| output += choiceStr; |
| }); |
| |
| return output; |
| } |
| |
| module.exports = ExpandPrompt; |