You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							281 lines
						
					
					
						
							6.4 KiB
						
					
					
				
			
		
		
	
	
							281 lines
						
					
					
						
							6.4 KiB
						
					
					
				'use strict'; | 
						|
/** | 
						|
 * `rawlist` type prompt | 
						|
 */ | 
						|
 | 
						|
var _ = { | 
						|
  uniq: require('lodash/uniq'), | 
						|
  isString: require('lodash/isString'), | 
						|
  isNumber: require('lodash/isNumber'), | 
						|
  findIndex: require('lodash/findIndex'), | 
						|
}; | 
						|
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;
 | 
						|
 |