base.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. /**
  2. * Base prompt implementation
  3. * Should be extended by prompt types.
  4. */
  5. var rx = require('rx-lite');
  6. var _ = require('lodash');
  7. var chalk = require('chalk');
  8. var ansiRegex = require('ansi-regex');
  9. var readline = require('readline');
  10. var cliWidth = require('cli-width');
  11. var runAsync = require('run-async');
  12. var Choices = require('../objects/choices');
  13. var ScreenManager = require('../utils/screen-manager');
  14. var Prompt = module.exports = function (question, rl, answers) {
  15. // Setup instance defaults property
  16. _.assign(this, {
  17. answers: answers,
  18. status : 'pending'
  19. });
  20. // Set defaults prompt options
  21. this.opt = _.defaults(_.clone(question), {
  22. validate: function () { return true; },
  23. filter: function (val) { return val; },
  24. when: function () { return true; }
  25. });
  26. // Check to make sure prompt requirements are there
  27. if (!this.opt.message) {
  28. this.throwParamError('message');
  29. }
  30. if (!this.opt.name) {
  31. this.throwParamError('name');
  32. }
  33. // Normalize choices
  34. if (Array.isArray(this.opt.choices)) {
  35. this.opt.choices = new Choices(this.opt.choices, answers);
  36. }
  37. this.rl = rl;
  38. this.screen = new ScreenManager(this.rl);
  39. };
  40. /**
  41. * Start the Inquiry session and manage output value filtering
  42. * @param {Function} cb Callback when prompt is done
  43. * @return {this}
  44. */
  45. Prompt.prototype.run = function( cb ) {
  46. this._run(function (value) {
  47. this.filter(value, cb);
  48. }.bind(this));
  49. };
  50. // default noop (this one should be overwritten in prompts)
  51. Prompt.prototype._run = function (cb) { cb(); };
  52. /**
  53. * Throw an error telling a required parameter is missing
  54. * @param {String} name Name of the missing param
  55. * @return {Throw Error}
  56. */
  57. Prompt.prototype.throwParamError = function (name) {
  58. throw new Error('You must provide a `' + name + '` parameter');
  59. };
  60. /**
  61. * Validate a given input
  62. * @param {String} value Input string
  63. * @param {Function} callback Pass `true` (if input is valid) or an error message as
  64. * parameter.
  65. * @return {null}
  66. */
  67. Prompt.prototype.validate = function (input, cb) {
  68. runAsync(this.opt.validate, cb, input);
  69. };
  70. /**
  71. * Run the provided validation method each time a submit event occur.
  72. * @param {Rx.Observable} submit - submit event flow
  73. * @return {Object} Object containing two observables: `success` and `error`
  74. */
  75. Prompt.prototype.handleSubmitEvents = function (submit) {
  76. var self = this;
  77. var validation = submit.flatMap(function (value) {
  78. return rx.Observable.create(function (observer) {
  79. runAsync(self.opt.validate, function (isValid) {
  80. observer.onNext({ isValid: isValid, value: self.getCurrentValue(value) });
  81. observer.onCompleted();
  82. }, self.getCurrentValue(value), self.answers);
  83. });
  84. }).share();
  85. var success = validation
  86. .filter(function (state) { return state.isValid === true; })
  87. .take(1);
  88. var error = validation
  89. .filter(function (state) { return state.isValid !== true; })
  90. .takeUntil(success);
  91. return {
  92. success: success,
  93. error: error
  94. };
  95. };
  96. Prompt.prototype.getCurrentValue = function (value) {
  97. return value;
  98. };
  99. /**
  100. * Filter a given input before sending back
  101. * @param {String} value Input string
  102. * @param {Function} callback Pass the filtered input as parameter.
  103. * @return {null}
  104. */
  105. Prompt.prototype.filter = function (input, cb) {
  106. runAsync(this.opt.filter, cb, input);
  107. };
  108. /**
  109. * Return the prompt line prefix
  110. * @param {String} [optionnal] String to concatenate to the prefix
  111. * @return {String} prompt prefix
  112. */
  113. Prompt.prototype.prefix = function (str) {
  114. str || (str = '');
  115. return chalk.green('?') + ' ' + str;
  116. };
  117. /**
  118. * Return the prompt line suffix
  119. * @param {String} [optionnal] String to concatenate to the suffix
  120. * @return {String} prompt suffix
  121. */
  122. var reStrEnd = new RegExp('(?:' + ansiRegex().source + ')$|$');
  123. Prompt.prototype.suffix = function (str) {
  124. str || (str = '');
  125. // make sure we get the `:` inside the styles
  126. if (str.length < 1 || /[a-z1-9]$/i.test(chalk.stripColor(str))) {
  127. str = str.replace(reStrEnd, ':$&');
  128. }
  129. return str.trim() + ' ';
  130. };
  131. /**
  132. * Generate the prompt question string
  133. * @return {String} prompt question string
  134. */
  135. Prompt.prototype.getQuestion = function () {
  136. var message = chalk.green('?') + ' ' + chalk.bold(this.opt.message) + ' ';
  137. // Append the default if available, and if question isn't answered
  138. if ( this.opt.default != null && this.status !== 'answered' ) {
  139. message += chalk.dim('('+ this.opt.default + ') ');
  140. }
  141. return message;
  142. };