Comparing version 3.3.0 to 4.0.0
@@ -0,1 +1,2 @@ | ||
'use strict'; | ||
/** | ||
@@ -24,4 +25,4 @@ * Inquirer.js | ||
*/ | ||
inquirer.createPromptModule = function (opt) { | ||
var promptModule = function (questions) { | ||
inquirer.createPromptModule = function(opt) { | ||
var promptModule = function(questions) { | ||
var ui = new inquirer.ui.Prompt(promptModule.prompts, opt); | ||
@@ -45,3 +46,3 @@ var promise = ui.run(questions); | ||
promptModule.registerPrompt = function (name, prompt) { | ||
promptModule.registerPrompt = function(name, prompt) { | ||
promptModule.prompts[name] = prompt; | ||
@@ -55,3 +56,3 @@ return this; | ||
promptModule.restoreDefaultPrompts = function () { | ||
promptModule.restoreDefaultPrompts = function() { | ||
this.registerPrompt('list', require('./prompts/list')); | ||
@@ -82,7 +83,7 @@ this.registerPrompt('input', require('./prompts/input')); | ||
// Expose helper functions on the top level for easiest usage by common users | ||
inquirer.registerPrompt = function (name, prompt) { | ||
inquirer.registerPrompt = function(name, prompt) { | ||
inquirer.prompt.registerPrompt(name, prompt); | ||
}; | ||
inquirer.restoreDefaultPrompts = function () { | ||
inquirer.restoreDefaultPrompts = function() { | ||
inquirer.prompt.restoreDefaultPrompts(); | ||
}; |
@@ -12,25 +12,27 @@ 'use strict'; | ||
var Choice = module.exports = function (val, answers) { | ||
// Don't process Choice and Separator object | ||
if (val instanceof Choice || val.type === 'separator') { | ||
return val; | ||
} | ||
module.exports = class Choice { | ||
constructor(val, answers) { | ||
// Don't process Choice and Separator object | ||
if (val instanceof Choice || val.type === 'separator') { | ||
return val; | ||
} | ||
if (_.isString(val)) { | ||
this.name = val; | ||
this.value = val; | ||
this.short = val; | ||
} else { | ||
_.extend(this, val, { | ||
name: val.name || val.value, | ||
value: 'value' in val ? val.value : val.name, | ||
short: val.short || val.name || val.value | ||
}); | ||
} | ||
if (_.isString(val)) { | ||
this.name = val; | ||
this.value = val; | ||
this.short = val; | ||
} else { | ||
_.extend(this, val, { | ||
name: val.name || val.value, | ||
value: 'value' in val ? val.value : val.name, | ||
short: val.short || val.name || val.value | ||
}); | ||
} | ||
if (_.isFunction(val.disabled)) { | ||
this.disabled = val.disabled(answers); | ||
} else { | ||
this.disabled = val.disabled; | ||
if (_.isFunction(val.disabled)) { | ||
this.disabled = val.disabled(answers); | ||
} else { | ||
this.disabled = val.disabled; | ||
} | ||
} | ||
}; |
@@ -14,100 +14,102 @@ 'use strict'; | ||
var Choices = module.exports = function (choices, answers) { | ||
this.choices = choices.map(function (val) { | ||
if (val.type === 'separator') { | ||
if (!(val instanceof Separator)) { | ||
val = new Separator(val.line); | ||
module.exports = class Choices { | ||
constructor(choices, answers) { | ||
this.choices = choices.map(val => { | ||
if (val.type === 'separator') { | ||
if (!(val instanceof Separator)) { | ||
val = new Separator(val.line); | ||
} | ||
return val; | ||
} | ||
return val; | ||
} | ||
return new Choice(val, answers); | ||
}); | ||
return new Choice(val, answers); | ||
}); | ||
this.realChoices = this.choices | ||
.filter(Separator.exclude) | ||
.filter(function (item) { | ||
return !item.disabled; | ||
this.realChoices = this.choices | ||
.filter(Separator.exclude) | ||
.filter(item => !item.disabled); | ||
Object.defineProperty(this, 'length', { | ||
get() { | ||
return this.choices.length; | ||
}, | ||
set(val) { | ||
this.choices.length = val; | ||
} | ||
}); | ||
Object.defineProperty(this, 'length', { | ||
get: function () { | ||
return this.choices.length; | ||
}, | ||
set: function (val) { | ||
this.choices.length = val; | ||
} | ||
}); | ||
Object.defineProperty(this, 'realLength', { | ||
get() { | ||
return this.realChoices.length; | ||
}, | ||
set() { | ||
throw new Error('Cannot set `realLength` of a Choices collection'); | ||
} | ||
}); | ||
} | ||
Object.defineProperty(this, 'realLength', { | ||
get: function () { | ||
return this.realChoices.length; | ||
}, | ||
set: function () { | ||
throw new Error('Cannot set `realLength` of a Choices collection'); | ||
} | ||
}); | ||
}; | ||
/** | ||
* Get a valid choice from the collection | ||
* @param {Number} selector The selected choice index | ||
* @return {Choice|Undefined} Return the matched choice or undefined | ||
*/ | ||
/** | ||
* Get a valid choice from the collection | ||
* @param {Number} selector The selected choice index | ||
* @return {Choice|Undefined} Return the matched choice or undefined | ||
*/ | ||
getChoice(selector) { | ||
assert(_.isNumber(selector)); | ||
return this.realChoices[selector]; | ||
} | ||
Choices.prototype.getChoice = function (selector) { | ||
assert(_.isNumber(selector)); | ||
return this.realChoices[selector]; | ||
}; | ||
/** | ||
* Get a raw element from the collection | ||
* @param {Number} selector The selected index value | ||
* @return {Choice|Undefined} Return the matched choice or undefined | ||
*/ | ||
/** | ||
* Get a raw element from the collection | ||
* @param {Number} selector The selected index value | ||
* @return {Choice|Undefined} Return the matched choice or undefined | ||
*/ | ||
get(selector) { | ||
assert(_.isNumber(selector)); | ||
return this.choices[selector]; | ||
} | ||
Choices.prototype.get = function (selector) { | ||
assert(_.isNumber(selector)); | ||
return this.choices[selector]; | ||
}; | ||
/** | ||
* Match the valid choices against a where clause | ||
* @param {Object} whereClause Lodash `where` clause | ||
* @return {Array} Matching choices or empty array | ||
*/ | ||
/** | ||
* Match the valid choices against a where clause | ||
* @param {Object} whereClause Lodash `where` clause | ||
* @return {Array} Matching choices or empty array | ||
*/ | ||
where(whereClause) { | ||
return _.filter(this.realChoices, whereClause); | ||
} | ||
Choices.prototype.where = function (whereClause) { | ||
return _.filter(this.realChoices, whereClause); | ||
}; | ||
/** | ||
* Pluck a particular key from the choices | ||
* @param {String} propertyName Property name to select | ||
* @return {Array} Selected properties | ||
*/ | ||
/** | ||
* Pluck a particular key from the choices | ||
* @param {String} propertyName Property name to select | ||
* @return {Array} Selected properties | ||
*/ | ||
pluck(propertyName) { | ||
return _.map(this.realChoices, propertyName); | ||
} | ||
Choices.prototype.pluck = function (propertyName) { | ||
return _.map(this.realChoices, propertyName); | ||
}; | ||
// Expose usual Array methods | ||
indexOf() { | ||
return this.choices.indexOf.apply(this.choices, arguments); | ||
} | ||
// Expose usual Array methods | ||
Choices.prototype.indexOf = function () { | ||
return this.choices.indexOf.apply(this.choices, arguments); | ||
forEach() { | ||
return this.choices.forEach.apply(this.choices, arguments); | ||
} | ||
filter() { | ||
return this.choices.filter.apply(this.choices, arguments); | ||
} | ||
find(func) { | ||
return _.find(this.choices, func); | ||
} | ||
push() { | ||
var objs = _.map(arguments, val => new Choice(val)); | ||
this.choices.push.apply(this.choices, objs); | ||
this.realChoices = this.choices.filter(Separator.exclude); | ||
return this.choices; | ||
} | ||
}; | ||
Choices.prototype.forEach = function () { | ||
return this.choices.forEach.apply(this.choices, arguments); | ||
}; | ||
Choices.prototype.filter = function () { | ||
return this.choices.filter.apply(this.choices, arguments); | ||
}; | ||
Choices.prototype.find = function (func) { | ||
return _.find(this.choices, func); | ||
}; | ||
Choices.prototype.push = function () { | ||
var objs = _.map(arguments, function (val) { | ||
return new Choice(val); | ||
}); | ||
this.choices.push.apply(this.choices, objs); | ||
this.realChoices = this.choices.filter(Separator.exclude); | ||
return this.choices; | ||
}; |
@@ -12,7 +12,17 @@ 'use strict'; | ||
var Separator = module.exports = function (line) { | ||
this.type = 'separator'; | ||
this.line = chalk.dim(line || new Array(15).join(figures.line)); | ||
}; | ||
class Separator { | ||
constructor(line) { | ||
this.type = 'separator'; | ||
this.line = chalk.dim(line || new Array(15).join(figures.line)); | ||
} | ||
/** | ||
* Stringify separator | ||
* @return {String} the separator display string | ||
*/ | ||
toString() { | ||
return this.line; | ||
} | ||
} | ||
/** | ||
@@ -24,13 +34,6 @@ * Helper function returning false if object is a separator | ||
Separator.exclude = function (obj) { | ||
Separator.exclude = function(obj) { | ||
return obj.type !== 'separator'; | ||
}; | ||
/** | ||
* Stringify separator | ||
* @return {String} the separator display string | ||
*/ | ||
Separator.prototype.toString = function () { | ||
return this.line; | ||
}; | ||
module.exports = Separator; |
@@ -0,1 +1,2 @@ | ||
'use strict'; | ||
/** | ||
@@ -12,129 +13,122 @@ * Base prompt implementation | ||
var Prompt = module.exports = function (question, rl, answers) { | ||
// Setup instance defaults property | ||
_.assign(this, { | ||
answers: answers, | ||
status: 'pending' | ||
}); | ||
class Prompt { | ||
constructor(question, rl, answers) { | ||
// Setup instance defaults property | ||
_.assign(this, { | ||
answers: answers, | ||
status: 'pending' | ||
}); | ||
// Set defaults prompt options | ||
this.opt = _.defaults(_.clone(question), { | ||
validate: function () { | ||
return true; | ||
}, | ||
filter: function (val) { | ||
return val; | ||
}, | ||
when: function () { | ||
return true; | ||
}, | ||
suffix: '', | ||
prefix: chalk.green('?') | ||
}); | ||
// Set defaults prompt options | ||
this.opt = _.defaults(_.clone(question), { | ||
validate: () => true, | ||
filter: val => val, | ||
when: () => true, | ||
suffix: '', | ||
prefix: chalk.green('?') | ||
}); | ||
// Check to make sure prompt requirements are there | ||
if (!this.opt.message) { | ||
this.throwParamError('message'); | ||
} | ||
if (!this.opt.name) { | ||
this.throwParamError('name'); | ||
} | ||
// Check to make sure prompt requirements are there | ||
if (!this.opt.message) { | ||
this.throwParamError('message'); | ||
} | ||
if (!this.opt.name) { | ||
this.throwParamError('name'); | ||
} | ||
// Normalize choices | ||
if (Array.isArray(this.opt.choices)) { | ||
this.opt.choices = new Choices(this.opt.choices, answers); | ||
// Normalize choices | ||
if (Array.isArray(this.opt.choices)) { | ||
this.opt.choices = new Choices(this.opt.choices, answers); | ||
} | ||
this.rl = rl; | ||
this.screen = new ScreenManager(this.rl); | ||
} | ||
this.rl = rl; | ||
this.screen = new ScreenManager(this.rl); | ||
}; | ||
/** | ||
* Start the Inquiry session and manage output value filtering | ||
* @return {Promise} | ||
*/ | ||
/** | ||
* Start the Inquiry session and manage output value filtering | ||
* @return {Promise} | ||
*/ | ||
Prompt.prototype.run = function () { | ||
return new Promise(function (resolve) { | ||
this._run(function (value) { | ||
resolve(value); | ||
run() { | ||
return new Promise(resolve => { | ||
this._run(value => resolve(value)); | ||
}); | ||
}.bind(this)); | ||
}; | ||
} | ||
// default noop (this one should be overwritten in prompts) | ||
Prompt.prototype._run = function (cb) { | ||
cb(); | ||
}; | ||
// Default noop (this one should be overwritten in prompts) | ||
_run(cb) { | ||
cb(); | ||
} | ||
/** | ||
* Throw an error telling a required parameter is missing | ||
* @param {String} name Name of the missing param | ||
* @return {Throw Error} | ||
*/ | ||
/** | ||
* Throw an error telling a required parameter is missing | ||
* @param {String} name Name of the missing param | ||
* @return {Throw Error} | ||
*/ | ||
Prompt.prototype.throwParamError = function (name) { | ||
throw new Error('You must provide a `' + name + '` parameter'); | ||
}; | ||
throwParamError(name) { | ||
throw new Error('You must provide a `' + name + '` parameter'); | ||
} | ||
/** | ||
* Called when the UI closes. Override to do any specific cleanup necessary | ||
*/ | ||
Prompt.prototype.close = function () { | ||
this.screen.releaseCursor(); | ||
}; | ||
/** | ||
* Called when the UI closes. Override to do any specific cleanup necessary | ||
*/ | ||
close() { | ||
this.screen.releaseCursor(); | ||
} | ||
/** | ||
* Run the provided validation method each time a submit event occur. | ||
* @param {Rx.Observable} submit - submit event flow | ||
* @return {Object} Object containing two observables: `success` and `error` | ||
*/ | ||
Prompt.prototype.handleSubmitEvents = function (submit) { | ||
var self = this; | ||
var validate = runAsync(this.opt.validate); | ||
var filter = runAsync(this.opt.filter); | ||
var validation = submit.flatMap(function (value) { | ||
return filter(value, self.answers).then(function (filteredValue) { | ||
return validate(filteredValue, self.answers).then(function (isValid) { | ||
return {isValid: isValid, value: filteredValue}; | ||
}, function (err) { | ||
return {isValid: err}; | ||
}); | ||
}, function (err) { | ||
return {isValid: err}; | ||
}); | ||
}).share(); | ||
/** | ||
* Run the provided validation method each time a submit event occur. | ||
* @param {Rx.Observable} submit - submit event flow | ||
* @return {Object} Object containing two observables: `success` and `error` | ||
*/ | ||
handleSubmitEvents(submit) { | ||
var self = this; | ||
var validate = runAsync(this.opt.validate); | ||
var filter = runAsync(this.opt.filter); | ||
var validation = submit | ||
.flatMap(value => | ||
filter(value, self.answers).then( | ||
filteredValue => | ||
validate(filteredValue, self.answers).then( | ||
isValid => ({ isValid: isValid, value: filteredValue }), | ||
err => ({ isValid: err }) | ||
), | ||
err => ({ isValid: err }) | ||
) | ||
) | ||
.share(); | ||
var success = validation | ||
.filter(function (state) { | ||
return state.isValid === true; | ||
}) | ||
.take(1); | ||
var success = validation.filter(state => state.isValid === true).take(1); | ||
var error = validation.filter(state => state.isValid !== true).takeUntil(success); | ||
var error = validation | ||
.filter(function (state) { | ||
return state.isValid !== true; | ||
}) | ||
.takeUntil(success); | ||
return { | ||
success: success, | ||
error: error | ||
}; | ||
} | ||
return { | ||
success: success, | ||
error: error | ||
}; | ||
}; | ||
/** | ||
* Generate the prompt question string | ||
* @return {String} prompt question string | ||
*/ | ||
/** | ||
* Generate the prompt question string | ||
* @return {String} prompt question string | ||
*/ | ||
getQuestion() { | ||
var message = | ||
this.opt.prefix + | ||
' ' + | ||
chalk.bold(this.opt.message) + | ||
this.opt.suffix + | ||
chalk.reset(' '); | ||
Prompt.prototype.getQuestion = function () { | ||
var message = this.opt.prefix + ' ' + chalk.bold(this.opt.message) + this.opt.suffix + chalk.reset(' '); | ||
// Append the default if available, and if question isn't answered | ||
if (this.opt.default != null && this.status !== 'answered') { | ||
message += chalk.dim('(' + this.opt.default + ') '); | ||
} | ||
// Append the default if available, and if question isn't answered | ||
if (this.opt.default != null && this.status !== 'answered') { | ||
message += chalk.dim('(' + this.opt.default + ') '); | ||
return message; | ||
} | ||
} | ||
return message; | ||
}; | ||
module.exports = Prompt; |
@@ -0,1 +1,2 @@ | ||
'use strict'; | ||
/** | ||
@@ -6,3 +7,2 @@ * `list` type prompt | ||
var _ = require('lodash'); | ||
var util = require('util'); | ||
var chalk = require('chalk'); | ||
@@ -15,182 +15,187 @@ var cliCursor = require('cli-cursor'); | ||
/** | ||
* Module exports | ||
*/ | ||
class CheckboxPrompt extends Base { | ||
constructor(questions, rl, answers) { | ||
super(questions, rl, answers); | ||
module.exports = Prompt; | ||
if (!this.opt.choices) { | ||
this.throwParamError('choices'); | ||
} | ||
/** | ||
* Constructor | ||
*/ | ||
if (_.isArray(this.opt.default)) { | ||
this.opt.choices.forEach(function(choice) { | ||
if (this.opt.default.indexOf(choice.value) >= 0) { | ||
choice.checked = true; | ||
} | ||
}, this); | ||
} | ||
function Prompt() { | ||
Base.apply(this, arguments); | ||
this.pointer = 0; | ||
this.firstRender = true; | ||
if (!this.opt.choices) { | ||
this.throwParamError('choices'); | ||
} | ||
// Make sure no default is set (so it won't be printed) | ||
this.opt.default = null; | ||
if (_.isArray(this.opt.default)) { | ||
this.opt.choices.forEach(function (choice) { | ||
if (this.opt.default.indexOf(choice.value) >= 0) { | ||
choice.checked = true; | ||
} | ||
}, this); | ||
this.paginator = new Paginator(this.screen); | ||
} | ||
this.pointer = 0; | ||
this.firstRender = true; | ||
/** | ||
* Start the Inquiry session | ||
* @param {Function} cb Callback when prompt is done | ||
* @return {this} | ||
*/ | ||
// Make sure no default is set (so it won't be printed) | ||
this.opt.default = null; | ||
_run(cb) { | ||
this.done = cb; | ||
this.paginator = new Paginator(); | ||
} | ||
util.inherits(Prompt, Base); | ||
var events = observe(this.rl); | ||
/** | ||
* Start the Inquiry session | ||
* @param {Function} cb Callback when prompt is done | ||
* @return {this} | ||
*/ | ||
var validation = this.handleSubmitEvents( | ||
events.line.map(this.getCurrentValue.bind(this)) | ||
); | ||
validation.success.forEach(this.onEnd.bind(this)); | ||
validation.error.forEach(this.onError.bind(this)); | ||
Prompt.prototype._run = function (cb) { | ||
this.done = cb; | ||
events.normalizedUpKey.takeUntil(validation.success).forEach(this.onUpKey.bind(this)); | ||
events.normalizedDownKey | ||
.takeUntil(validation.success) | ||
.forEach(this.onDownKey.bind(this)); | ||
events.numberKey.takeUntil(validation.success).forEach(this.onNumberKey.bind(this)); | ||
events.spaceKey.takeUntil(validation.success).forEach(this.onSpaceKey.bind(this)); | ||
events.aKey.takeUntil(validation.success).forEach(this.onAllKey.bind(this)); | ||
events.iKey.takeUntil(validation.success).forEach(this.onInverseKey.bind(this)); | ||
var events = observe(this.rl); | ||
// Init the prompt | ||
cliCursor.hide(); | ||
this.render(); | ||
this.firstRender = false; | ||
var validation = this.handleSubmitEvents( | ||
events.line.map(this.getCurrentValue.bind(this)) | ||
); | ||
validation.success.forEach(this.onEnd.bind(this)); | ||
validation.error.forEach(this.onError.bind(this)); | ||
return this; | ||
} | ||
events.normalizedUpKey.takeUntil(validation.success).forEach(this.onUpKey.bind(this)); | ||
events.normalizedDownKey.takeUntil(validation.success).forEach(this.onDownKey.bind(this)); | ||
events.numberKey.takeUntil(validation.success).forEach(this.onNumberKey.bind(this)); | ||
events.spaceKey.takeUntil(validation.success).forEach(this.onSpaceKey.bind(this)); | ||
events.aKey.takeUntil(validation.success).forEach(this.onAllKey.bind(this)); | ||
events.iKey.takeUntil(validation.success).forEach(this.onInverseKey.bind(this)); | ||
/** | ||
* Render the prompt to screen | ||
* @return {CheckboxPrompt} self | ||
*/ | ||
// Init the prompt | ||
cliCursor.hide(); | ||
this.render(); | ||
this.firstRender = false; | ||
render(error) { | ||
// Render question | ||
var message = this.getQuestion(); | ||
var bottomContent = ''; | ||
return this; | ||
}; | ||
if (this.firstRender) { | ||
message += | ||
'(Press ' + | ||
chalk.cyan.bold('<space>') + | ||
' to select, ' + | ||
chalk.cyan.bold('<a>') + | ||
' to toggle all, ' + | ||
chalk.cyan.bold('<i>') + | ||
' to inverse selection)'; | ||
} | ||
/** | ||
* Render the prompt to screen | ||
* @return {Prompt} self | ||
*/ | ||
// Render choices or answer depending on the state | ||
if (this.status === 'answered') { | ||
message += chalk.cyan(this.selection.join(', ')); | ||
} else { | ||
var choicesStr = renderChoices(this.opt.choices, this.pointer); | ||
var indexPosition = this.opt.choices.indexOf( | ||
this.opt.choices.getChoice(this.pointer) | ||
); | ||
message += | ||
'\n' + this.paginator.paginate(choicesStr, indexPosition, this.opt.pageSize); | ||
} | ||
Prompt.prototype.render = function (error) { | ||
// Render question | ||
var message = this.getQuestion(); | ||
var bottomContent = ''; | ||
if (error) { | ||
bottomContent = chalk.red('>> ') + error; | ||
} | ||
if (this.firstRender) { | ||
message += '(Press ' + chalk.cyan.bold('<space>') + ' to select, ' + chalk.cyan.bold('<a>') + ' to toggle all, ' + chalk.cyan.bold('<i>') + ' to inverse selection)'; | ||
this.screen.render(message, bottomContent); | ||
} | ||
// Render choices or answer depending on the state | ||
if (this.status === 'answered') { | ||
message += chalk.cyan(this.selection.join(', ')); | ||
} else { | ||
var choicesStr = renderChoices(this.opt.choices, this.pointer); | ||
var indexPosition = this.opt.choices.indexOf(this.opt.choices.getChoice(this.pointer)); | ||
message += '\n' + this.paginator.paginate(choicesStr, indexPosition, this.opt.pageSize); | ||
} | ||
/** | ||
* When user press `enter` key | ||
*/ | ||
if (error) { | ||
bottomContent = chalk.red('>> ') + error; | ||
} | ||
onEnd(state) { | ||
this.status = 'answered'; | ||
this.screen.render(message, bottomContent); | ||
}; | ||
// Rerender prompt (and clean subline error) | ||
this.render(); | ||
/** | ||
* When user press `enter` key | ||
*/ | ||
this.screen.done(); | ||
cliCursor.show(); | ||
this.done(state.value); | ||
} | ||
Prompt.prototype.onEnd = function (state) { | ||
this.status = 'answered'; | ||
onError(state) { | ||
this.render(state.isValid); | ||
} | ||
// Rerender prompt (and clean subline error) | ||
this.render(); | ||
getCurrentValue() { | ||
var choices = this.opt.choices.filter(function(choice) { | ||
return Boolean(choice.checked) && !choice.disabled; | ||
}); | ||
this.screen.done(); | ||
cliCursor.show(); | ||
this.done(state.value); | ||
}; | ||
this.selection = _.map(choices, 'short'); | ||
return _.map(choices, 'value'); | ||
} | ||
Prompt.prototype.onError = function (state) { | ||
this.render(state.isValid); | ||
}; | ||
onUpKey() { | ||
var len = this.opt.choices.realLength; | ||
this.pointer = this.pointer > 0 ? this.pointer - 1 : len - 1; | ||
this.render(); | ||
} | ||
Prompt.prototype.getCurrentValue = function () { | ||
var choices = this.opt.choices.filter(function (choice) { | ||
return Boolean(choice.checked) && !choice.disabled; | ||
}); | ||
onDownKey() { | ||
var len = this.opt.choices.realLength; | ||
this.pointer = this.pointer < len - 1 ? this.pointer + 1 : 0; | ||
this.render(); | ||
} | ||
this.selection = _.map(choices, 'short'); | ||
return _.map(choices, 'value'); | ||
}; | ||
onNumberKey(input) { | ||
if (input <= this.opt.choices.realLength) { | ||
this.pointer = input - 1; | ||
this.toggleChoice(this.pointer); | ||
} | ||
this.render(); | ||
} | ||
Prompt.prototype.onUpKey = function () { | ||
var len = this.opt.choices.realLength; | ||
this.pointer = (this.pointer > 0) ? this.pointer - 1 : len - 1; | ||
this.render(); | ||
}; | ||
Prompt.prototype.onDownKey = function () { | ||
var len = this.opt.choices.realLength; | ||
this.pointer = (this.pointer < len - 1) ? this.pointer + 1 : 0; | ||
this.render(); | ||
}; | ||
Prompt.prototype.onNumberKey = function (input) { | ||
if (input <= this.opt.choices.realLength) { | ||
this.pointer = input - 1; | ||
onSpaceKey() { | ||
this.toggleChoice(this.pointer); | ||
this.render(); | ||
} | ||
this.render(); | ||
}; | ||
Prompt.prototype.onSpaceKey = function () { | ||
this.toggleChoice(this.pointer); | ||
this.render(); | ||
}; | ||
onAllKey() { | ||
var shouldBeChecked = Boolean( | ||
this.opt.choices.find(function(choice) { | ||
return choice.type !== 'separator' && !choice.checked; | ||
}) | ||
); | ||
Prompt.prototype.onAllKey = function () { | ||
var shouldBeChecked = Boolean(this.opt.choices.find(function (choice) { | ||
return choice.type !== 'separator' && !choice.checked; | ||
})); | ||
this.opt.choices.forEach(function(choice) { | ||
if (choice.type !== 'separator') { | ||
choice.checked = shouldBeChecked; | ||
} | ||
}); | ||
this.opt.choices.forEach(function (choice) { | ||
if (choice.type !== 'separator') { | ||
choice.checked = shouldBeChecked; | ||
} | ||
}); | ||
this.render(); | ||
} | ||
this.render(); | ||
}; | ||
onInverseKey() { | ||
this.opt.choices.forEach(function(choice) { | ||
if (choice.type !== 'separator') { | ||
choice.checked = !choice.checked; | ||
} | ||
}); | ||
Prompt.prototype.onInverseKey = function () { | ||
this.opt.choices.forEach(function (choice) { | ||
if (choice.type !== 'separator') { | ||
choice.checked = !choice.checked; | ||
this.render(); | ||
} | ||
toggleChoice(index) { | ||
var item = this.opt.choices.getChoice(index); | ||
if (item !== undefined) { | ||
this.opt.choices.getChoice(index).checked = !item.checked; | ||
} | ||
}); | ||
this.render(); | ||
}; | ||
Prompt.prototype.toggleChoice = function (index) { | ||
var item = this.opt.choices.getChoice(index); | ||
if (item !== undefined) { | ||
this.opt.choices.getChoice(index).checked = !item.checked; | ||
} | ||
}; | ||
} | ||
@@ -207,3 +212,3 @@ /** | ||
choices.forEach(function (choice, i) { | ||
choices.forEach(function(choice, i) { | ||
if (choice.type === 'separator') { | ||
@@ -220,3 +225,3 @@ separatorOffset++; | ||
} else { | ||
var isSelected = (i - separatorOffset === pointer); | ||
var isSelected = i - separatorOffset === pointer; | ||
output += isSelected ? chalk.cyan(figures.pointer) : ' '; | ||
@@ -241,1 +246,3 @@ output += getCheckbox(choice.checked) + ' ' + choice.name; | ||
} | ||
module.exports = CheckboxPrompt; |
@@ -0,1 +1,2 @@ | ||
'use strict'; | ||
/** | ||
@@ -6,3 +7,2 @@ * `confirm` type prompt | ||
var _ = require('lodash'); | ||
var util = require('util'); | ||
var chalk = require('chalk'); | ||
@@ -12,97 +12,90 @@ var Base = require('./base'); | ||
/** | ||
* Module exports | ||
*/ | ||
class ConfirmPrompt extends Base { | ||
constructor(questions, rl, answers) { | ||
super(questions, rl, answers); | ||
module.exports = Prompt; | ||
var rawDefault = true; | ||
/** | ||
* Constructor | ||
*/ | ||
_.extend(this.opt, { | ||
filter: function(input) { | ||
var value = rawDefault; | ||
if (input != null && input !== '') { | ||
value = /^y(es)?/i.test(input); | ||
} | ||
return value; | ||
} | ||
}); | ||
function Prompt() { | ||
Base.apply(this, arguments); | ||
if (_.isBoolean(this.opt.default)) { | ||
rawDefault = this.opt.default; | ||
} | ||
var rawDefault = true; | ||
this.opt.default = rawDefault ? 'Y/n' : 'y/N'; | ||
_.extend(this.opt, { | ||
filter: function (input) { | ||
var value = rawDefault; | ||
if (input != null && input !== '') { | ||
value = /^y(es)?/i.test(input); | ||
} | ||
return value; | ||
} | ||
}); | ||
if (_.isBoolean(this.opt.default)) { | ||
rawDefault = this.opt.default; | ||
return this; | ||
} | ||
this.opt.default = rawDefault ? 'Y/n' : 'y/N'; | ||
/** | ||
* Start the Inquiry session | ||
* @param {Function} cb Callback when prompt is done | ||
* @return {this} | ||
*/ | ||
return this; | ||
} | ||
util.inherits(Prompt, Base); | ||
_run(cb) { | ||
this.done = cb; | ||
/** | ||
* Start the Inquiry session | ||
* @param {Function} cb Callback when prompt is done | ||
* @return {this} | ||
*/ | ||
// Once user confirm (enter key) | ||
var events = observe(this.rl); | ||
events.keypress.takeUntil(events.line).forEach(this.onKeypress.bind(this)); | ||
Prompt.prototype._run = function (cb) { | ||
this.done = cb; | ||
events.line.take(1).forEach(this.onEnd.bind(this)); | ||
// Once user confirm (enter key) | ||
var events = observe(this.rl); | ||
events.keypress.takeUntil(events.line).forEach(this.onKeypress.bind(this)); | ||
// Init | ||
this.render(); | ||
events.line.take(1).forEach(this.onEnd.bind(this)); | ||
return this; | ||
} | ||
// Init | ||
this.render(); | ||
/** | ||
* Render the prompt to screen | ||
* @return {ConfirmPrompt} self | ||
*/ | ||
return this; | ||
}; | ||
render(answer) { | ||
var message = this.getQuestion(); | ||
/** | ||
* Render the prompt to screen | ||
* @return {Prompt} self | ||
*/ | ||
if (typeof answer === 'boolean') { | ||
message += chalk.cyan(answer ? 'Yes' : 'No'); | ||
} else { | ||
message += this.rl.line; | ||
} | ||
Prompt.prototype.render = function (answer) { | ||
var message = this.getQuestion(); | ||
this.screen.render(message); | ||
if (typeof answer === 'boolean') { | ||
message += chalk.cyan(answer ? 'Yes' : 'No'); | ||
} else { | ||
message += this.rl.line; | ||
return this; | ||
} | ||
this.screen.render(message); | ||
/** | ||
* When user press `enter` key | ||
*/ | ||
return this; | ||
}; | ||
onEnd(input) { | ||
this.status = 'answered'; | ||
/** | ||
* When user press `enter` key | ||
*/ | ||
var output = this.opt.filter(input); | ||
this.render(output); | ||
Prompt.prototype.onEnd = function (input) { | ||
this.status = 'answered'; | ||
this.screen.done(); | ||
this.done(output); | ||
} | ||
var output = this.opt.filter(input); | ||
this.render(output); | ||
/** | ||
* When user press a key | ||
*/ | ||
this.screen.done(); | ||
this.done(output); | ||
}; | ||
onKeypress() { | ||
this.render(); | ||
} | ||
} | ||
/** | ||
* When user press a key | ||
*/ | ||
Prompt.prototype.onKeypress = function () { | ||
this.render(); | ||
}; | ||
module.exports = ConfirmPrompt; |
@@ -0,1 +1,2 @@ | ||
'use strict'; | ||
/** | ||
@@ -5,3 +6,2 @@ * `editor` type prompt | ||
var util = require('util'); | ||
var chalk = require('chalk'); | ||
@@ -13,101 +13,90 @@ var ExternalEditor = require('external-editor'); | ||
/** | ||
* Module exports | ||
*/ | ||
class EditorPrompt extends Base { | ||
/** | ||
* Start the Inquiry session | ||
* @param {Function} cb Callback when prompt is done | ||
* @return {this} | ||
*/ | ||
module.exports = Prompt; | ||
_run(cb) { | ||
this.done = cb; | ||
/** | ||
* Constructor | ||
*/ | ||
this.editorResult = new rx.Subject(); | ||
function Prompt() { | ||
return Base.apply(this, arguments); | ||
} | ||
util.inherits(Prompt, Base); | ||
// Open Editor on "line" (Enter Key) | ||
var events = observe(this.rl); | ||
this.lineSubscription = events.line.forEach(this.startExternalEditor.bind(this)); | ||
/** | ||
* Start the Inquiry session | ||
* @param {Function} cb Callback when prompt is done | ||
* @return {this} | ||
*/ | ||
// Trigger Validation when editor closes | ||
var validation = this.handleSubmitEvents(this.editorResult); | ||
validation.success.forEach(this.onEnd.bind(this)); | ||
validation.error.forEach(this.onError.bind(this)); | ||
Prompt.prototype._run = function (cb) { | ||
this.done = cb; | ||
// Prevents default from being printed on screen (can look weird with multiple lines) | ||
this.currentText = this.opt.default; | ||
this.opt.default = null; | ||
this.editorResult = new rx.Subject(); | ||
// Init | ||
this.render(); | ||
// Open Editor on "line" (Enter Key) | ||
var events = observe(this.rl); | ||
this.lineSubscription = events.line.forEach(this.startExternalEditor.bind(this)); | ||
return this; | ||
} | ||
// Trigger Validation when editor closes | ||
var validation = this.handleSubmitEvents(this.editorResult); | ||
validation.success.forEach(this.onEnd.bind(this)); | ||
validation.error.forEach(this.onError.bind(this)); | ||
/** | ||
* Render the prompt to screen | ||
* @return {EditorPrompt} self | ||
*/ | ||
// Prevents default from being printed on screen (can look weird with multiple lines) | ||
this.currentText = this.opt.default; | ||
this.opt.default = null; | ||
render(error) { | ||
var bottomContent = ''; | ||
var message = this.getQuestion(); | ||
// Init | ||
this.render(); | ||
if (this.status === 'answered') { | ||
message += chalk.dim('Received'); | ||
} else { | ||
message += chalk.dim('Press <enter> to launch your preferred editor.'); | ||
} | ||
return this; | ||
}; | ||
if (error) { | ||
bottomContent = chalk.red('>> ') + error; | ||
} | ||
/** | ||
* Render the prompt to screen | ||
* @return {Prompt} self | ||
*/ | ||
this.screen.render(message, bottomContent); | ||
} | ||
Prompt.prototype.render = function (error) { | ||
var bottomContent = ''; | ||
var message = this.getQuestion(); | ||
/** | ||
* Launch $EDITOR on user press enter | ||
*/ | ||
if (this.status === 'answered') { | ||
message += chalk.dim('Received'); | ||
} else { | ||
message += chalk.dim('Press <enter> to launch your preferred editor.'); | ||
startExternalEditor() { | ||
// Pause Readline to prevent stdin and stdout from being modified while the editor is showing | ||
this.rl.pause(); | ||
ExternalEditor.editAsync(this.currentText, this.endExternalEditor.bind(this)); | ||
} | ||
if (error) { | ||
bottomContent = chalk.red('>> ') + error; | ||
endExternalEditor(error, result) { | ||
this.rl.resume(); | ||
if (error) { | ||
this.editorResult.onError(error); | ||
} else { | ||
this.editorResult.onNext(result); | ||
} | ||
} | ||
this.screen.render(message, bottomContent); | ||
}; | ||
onEnd(state) { | ||
this.editorResult.dispose(); | ||
this.lineSubscription.dispose(); | ||
this.answer = state.value; | ||
this.status = 'answered'; | ||
// Re-render prompt | ||
this.render(); | ||
this.screen.done(); | ||
this.done(this.answer); | ||
} | ||
/** | ||
* Launch $EDITOR on user press enter | ||
*/ | ||
Prompt.prototype.startExternalEditor = function () { | ||
// Pause Readline to prevent stdin and stdout from being modified while the editor is showing | ||
this.rl.pause(); | ||
ExternalEditor.editAsync(this.currentText, this.endExternalEditor.bind(this)); | ||
}; | ||
Prompt.prototype.endExternalEditor = function (error, result) { | ||
this.rl.resume(); | ||
if (error) { | ||
this.editorResult.onError(error); | ||
} else { | ||
this.editorResult.onNext(result); | ||
onError(state) { | ||
this.render(state.isValid); | ||
} | ||
}; | ||
} | ||
Prompt.prototype.onEnd = function (state) { | ||
this.editorResult.dispose(); | ||
this.lineSubscription.dispose(); | ||
this.answer = state.value; | ||
this.status = 'answered'; | ||
// Re-render prompt | ||
this.render(); | ||
this.screen.done(); | ||
this.done(this.answer); | ||
}; | ||
Prompt.prototype.onError = function (state) { | ||
this.render(state.isValid); | ||
}; | ||
module.exports = EditorPrompt; |
@@ -0,1 +1,2 @@ | ||
'use strict'; | ||
/** | ||
@@ -6,3 +7,2 @@ * `rawlist` type prompt | ||
var _ = require('lodash'); | ||
var util = require('util'); | ||
var chalk = require('chalk'); | ||
@@ -14,223 +14,227 @@ var Base = require('./base'); | ||
/** | ||
* Module exports | ||
*/ | ||
class ExpandPrompt extends Base { | ||
constructor(questions, rl, answers) { | ||
super(questions, rl, answers); | ||
module.exports = Prompt; | ||
if (!this.opt.choices) { | ||
this.throwParamError('choices'); | ||
} | ||
/** | ||
* Constructor | ||
*/ | ||
this.validateChoices(this.opt.choices); | ||
function Prompt() { | ||
Base.apply(this, arguments); | ||
// Add the default `help` (/expand) option | ||
this.opt.choices.push({ | ||
key: 'h', | ||
name: 'Help, list all options', | ||
value: 'help' | ||
}); | ||
if (!this.opt.choices) { | ||
this.throwParamError('choices'); | ||
} | ||
this.opt.validate = choice => { | ||
if (choice == null) { | ||
return 'Please enter a valid command'; | ||
} | ||
this.validateChoices(this.opt.choices); | ||
return choice !== 'help'; | ||
}; | ||
// 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.opt.validate = function (choice) { | ||
if (choice == null) { | ||
return 'Please enter a valid command'; | ||
} | ||
this.paginator = new Paginator(this.screen); | ||
} | ||
return choice !== 'help'; | ||
}; | ||
/** | ||
* Start the Inquiry session | ||
* @param {Function} cb Callback when prompt is done | ||
* @return {this} | ||
*/ | ||
// Setup the default string (capitalize the default key) | ||
this.opt.default = this.generateChoicesString(this.opt.choices, this.opt.default); | ||
_run(cb) { | ||
this.done = cb; | ||
this.paginator = new Paginator(); | ||
} | ||
util.inherits(Prompt, Base); | ||
// Save user answer and update prompt to show selected option. | ||
var events = observe(this.rl); | ||
var validation = this.handleSubmitEvents( | ||
events.line.map(this.getCurrentValue.bind(this)) | ||
); | ||
validation.success.forEach(this.onSubmit.bind(this)); | ||
validation.error.forEach(this.onError.bind(this)); | ||
this.keypressObs = events.keypress | ||
.takeUntil(validation.success) | ||
.forEach(this.onKeypress.bind(this)); | ||
/** | ||
* Start the Inquiry session | ||
* @param {Function} cb Callback when prompt is done | ||
* @return {this} | ||
*/ | ||
// Init the prompt | ||
this.render(); | ||
Prompt.prototype._run = function (cb) { | ||
this.done = cb; | ||
return this; | ||
} | ||
// Save user answer and update prompt to show selected option. | ||
var events = observe(this.rl); | ||
var validation = this.handleSubmitEvents( | ||
events.line.map(this.getCurrentValue.bind(this)) | ||
); | ||
validation.success.forEach(this.onSubmit.bind(this)); | ||
validation.error.forEach(this.onError.bind(this)); | ||
this.keypressObs = events.keypress.takeUntil(validation.success) | ||
.forEach(this.onKeypress.bind(this)); | ||
/** | ||
* Render the prompt to screen | ||
* @return {ExpandPrompt} self | ||
*/ | ||
// Init the prompt | ||
this.render(); | ||
render(error, hint) { | ||
var message = this.getQuestion(); | ||
var bottomContent = ''; | ||
return this; | ||
}; | ||
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: '; | ||
} | ||
/** | ||
* Render the prompt to screen | ||
* @return {Prompt} self | ||
*/ | ||
message += this.rl.line; | ||
Prompt.prototype.render = function (error, hint) { | ||
var message = this.getQuestion(); | ||
var bottomContent = ''; | ||
if (error) { | ||
bottomContent = chalk.red('>> ') + error; | ||
} | ||
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: '; | ||
if (hint) { | ||
bottomContent = chalk.cyan('>> ') + hint; | ||
} | ||
this.screen.render(message, bottomContent); | ||
} | ||
message += this.rl.line; | ||
getCurrentValue(input) { | ||
if (!input) { | ||
input = this.rawDefault; | ||
} | ||
var selected = this.opt.choices.where({ key: input.toLowerCase().trim() })[0]; | ||
if (!selected) { | ||
return null; | ||
} | ||
if (error) { | ||
bottomContent = chalk.red('>> ') + error; | ||
return selected.value; | ||
} | ||
if (hint) { | ||
bottomContent = chalk.cyan('>> ') + hint; | ||
} | ||
/** | ||
* Generate the prompt choices string | ||
* @return {String} Choices string | ||
*/ | ||
this.screen.render(message, bottomContent); | ||
}; | ||
getChoices() { | ||
var output = ''; | ||
Prompt.prototype.getCurrentValue = function (input) { | ||
if (!input) { | ||
input = this.rawDefault; | ||
} | ||
var selected = this.opt.choices.where({key: input.toLowerCase().trim()})[0]; | ||
if (!selected) { | ||
return null; | ||
} | ||
this.opt.choices.forEach(choice => { | ||
output += '\n '; | ||
return selected.value; | ||
}; | ||
if (choice.type === 'separator') { | ||
output += ' ' + choice; | ||
return; | ||
} | ||
/** | ||
* Generate the prompt choices string | ||
* @return {String} Choices string | ||
*/ | ||
var choiceStr = choice.key + ') ' + choice.name; | ||
if (this.selectedKey === choice.key) { | ||
choiceStr = chalk.cyan(choiceStr); | ||
} | ||
output += choiceStr; | ||
}); | ||
Prompt.prototype.getChoices = function () { | ||
var output = ''; | ||
return output; | ||
} | ||
this.opt.choices.forEach(function (choice) { | ||
output += '\n '; | ||
if (choice.type === 'separator') { | ||
output += ' ' + choice; | ||
onError(state) { | ||
if (state.value === 'help') { | ||
this.selectedKey = ''; | ||
this.status = 'expanded'; | ||
this.render(); | ||
return; | ||
} | ||
this.render(state.isValid); | ||
} | ||
var choiceStr = choice.key + ') ' + choice.name; | ||
if (this.selectedKey === choice.key) { | ||
choiceStr = chalk.cyan(choiceStr); | ||
} | ||
output += choiceStr; | ||
}.bind(this)); | ||
/** | ||
* When user press `enter` key | ||
*/ | ||
return output; | ||
}; | ||
onSubmit(state) { | ||
this.status = 'answered'; | ||
var choice = this.opt.choices.where({ value: state.value })[0]; | ||
this.answer = choice.short || choice.name; | ||
Prompt.prototype.onError = function (state) { | ||
if (state.value === 'help') { | ||
this.selectedKey = ''; | ||
this.status = 'expanded'; | ||
// Re-render prompt | ||
this.render(); | ||
return; | ||
this.screen.done(); | ||
this.done(state.value); | ||
} | ||
this.render(state.isValid); | ||
}; | ||
/** | ||
* When user press `enter` key | ||
*/ | ||
/** | ||
* When user press a key | ||
*/ | ||
Prompt.prototype.onSubmit = function (state) { | ||
this.status = 'answered'; | ||
var choice = this.opt.choices.where({value: state.value})[0]; | ||
this.answer = choice.short || choice.name; | ||
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); | ||
} | ||
} | ||
// Re-render prompt | ||
this.render(); | ||
this.screen.done(); | ||
this.done(state.value); | ||
}; | ||
/** | ||
* Validate the choices | ||
* @param {Array} choices | ||
*/ | ||
/** | ||
* When user press a key | ||
*/ | ||
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(); | ||
}); | ||
Prompt.prototype.onKeypress = function () { | ||
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).forEach(function (choice) { | ||
if (!choice.key || choice.key.length !== 1) { | ||
formatError = true; | ||
if (formatError) { | ||
throw new Error( | ||
'Format error: `key` param must be a single letter and is required.' | ||
); | ||
} | ||
if (keymap[choice.key]) { | ||
errors.push(choice.key); | ||
if (keymap.h) { | ||
throw new Error( | ||
'Reserved key error: `key` param cannot be `h` - this value is reserved.' | ||
); | ||
} | ||
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 (errors.length) { | ||
throw new Error( | ||
'Duplicate key error: `key` param must be unique. Duplicates: ' + | ||
_.uniq(errors).join(', ') | ||
); | ||
} | ||
} | ||
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} defaultIndex - the choice index to capitalize | ||
* @return {String} The rendered choices key string | ||
*/ | ||
Prompt.prototype.generateChoicesString = function (choices, defaultIndex) { | ||
var defIndex = choices.realLength - 1; | ||
if (_.isNumber(defaultIndex) && this.opt.choices.getChoice(defaultIndex)) { | ||
defIndex = defaultIndex; | ||
/** | ||
* 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(''); | ||
} | ||
var defStr = this.opt.choices.pluck('key'); | ||
this.rawDefault = defStr[defIndex]; | ||
defStr[defIndex] = String(defStr[defIndex]).toUpperCase(); | ||
return defStr.join(''); | ||
}; | ||
} | ||
@@ -246,3 +250,3 @@ /** | ||
choices.forEach(function (choice) { | ||
choices.forEach(choice => { | ||
output += '\n '; | ||
@@ -264,1 +268,3 @@ | ||
} | ||
module.exports = ExpandPrompt; |
@@ -0,1 +1,2 @@ | ||
'use strict'; | ||
/** | ||
@@ -5,3 +6,2 @@ * `input` type prompt | ||
var util = require('util'); | ||
var chalk = require('chalk'); | ||
@@ -11,96 +11,85 @@ var Base = require('./base'); | ||
/** | ||
* Module exports | ||
*/ | ||
class InputPrompt extends Base { | ||
/** | ||
* Start the Inquiry session | ||
* @param {Function} cb Callback when prompt is done | ||
* @return {this} | ||
*/ | ||
module.exports = Prompt; | ||
_run(cb) { | ||
this.done = cb; | ||
/** | ||
* Constructor | ||
*/ | ||
// Once user confirm (enter key) | ||
var events = observe(this.rl); | ||
var submit = events.line.map(this.filterInput.bind(this)); | ||
function Prompt() { | ||
return Base.apply(this, arguments); | ||
} | ||
util.inherits(Prompt, Base); | ||
var validation = this.handleSubmitEvents(submit); | ||
validation.success.forEach(this.onEnd.bind(this)); | ||
validation.error.forEach(this.onError.bind(this)); | ||
/** | ||
* Start the Inquiry session | ||
* @param {Function} cb Callback when prompt is done | ||
* @return {this} | ||
*/ | ||
events.keypress.takeUntil(validation.success).forEach(this.onKeypress.bind(this)); | ||
Prompt.prototype._run = function (cb) { | ||
this.done = cb; | ||
// Init | ||
this.render(); | ||
// Once user confirm (enter key) | ||
var events = observe(this.rl); | ||
var submit = events.line.map(this.filterInput.bind(this)); | ||
return this; | ||
} | ||
var validation = this.handleSubmitEvents(submit); | ||
validation.success.forEach(this.onEnd.bind(this)); | ||
validation.error.forEach(this.onError.bind(this)); | ||
/** | ||
* Render the prompt to screen | ||
* @return {InputPrompt} self | ||
*/ | ||
events.keypress.takeUntil(validation.success).forEach(this.onKeypress.bind(this)); | ||
render(error) { | ||
var bottomContent = ''; | ||
var message = this.getQuestion(); | ||
// Init | ||
this.render(); | ||
if (this.status === 'answered') { | ||
message += chalk.cyan(this.answer); | ||
} else { | ||
message += this.rl.line; | ||
} | ||
return this; | ||
}; | ||
if (error) { | ||
bottomContent = chalk.red('>> ') + error; | ||
} | ||
/** | ||
* Render the prompt to screen | ||
* @return {Prompt} self | ||
*/ | ||
this.screen.render(message, bottomContent); | ||
} | ||
Prompt.prototype.render = function (error) { | ||
var bottomContent = ''; | ||
var message = this.getQuestion(); | ||
/** | ||
* When user press `enter` key | ||
*/ | ||
if (this.status === 'answered') { | ||
message += chalk.cyan(this.answer); | ||
} else { | ||
message += this.rl.line; | ||
filterInput(input) { | ||
if (!input) { | ||
return this.opt.default == null ? '' : this.opt.default; | ||
} | ||
return input; | ||
} | ||
if (error) { | ||
bottomContent = chalk.red('>> ') + error; | ||
} | ||
onEnd(state) { | ||
this.answer = state.value; | ||
this.status = 'answered'; | ||
this.screen.render(message, bottomContent); | ||
}; | ||
// Re-render prompt | ||
this.render(); | ||
/** | ||
* When user press `enter` key | ||
*/ | ||
this.screen.done(); | ||
this.done(state.value); | ||
} | ||
Prompt.prototype.filterInput = function (input) { | ||
if (!input) { | ||
return this.opt.default == null ? '' : this.opt.default; | ||
onError(state) { | ||
this.render(state.isValid); | ||
} | ||
return input; | ||
}; | ||
Prompt.prototype.onEnd = function (state) { | ||
this.answer = state.value; | ||
this.status = 'answered'; | ||
/** | ||
* When user press a key | ||
*/ | ||
// Re-render prompt | ||
this.render(); | ||
onKeypress() { | ||
this.render(); | ||
} | ||
} | ||
this.screen.done(); | ||
this.done(state.value); | ||
}; | ||
Prompt.prototype.onError = function (state) { | ||
this.render(state.isValid); | ||
}; | ||
/** | ||
* When user press a key | ||
*/ | ||
Prompt.prototype.onKeypress = function () { | ||
this.render(); | ||
}; | ||
module.exports = InputPrompt; |
@@ -0,1 +1,2 @@ | ||
'use strict'; | ||
/** | ||
@@ -6,3 +7,2 @@ * `list` type prompt | ||
var _ = require('lodash'); | ||
var util = require('util'); | ||
var chalk = require('chalk'); | ||
@@ -16,137 +16,128 @@ var figures = require('figures'); | ||
/** | ||
* Module exports | ||
*/ | ||
class ListPrompt extends Base { | ||
constructor(questions, rl, answers) { | ||
super(questions, rl, answers); | ||
module.exports = Prompt; | ||
if (!this.opt.choices) { | ||
this.throwParamError('choices'); | ||
} | ||
/** | ||
* Constructor | ||
*/ | ||
this.firstRender = true; | ||
this.selected = 0; | ||
function Prompt() { | ||
Base.apply(this, arguments); | ||
var def = this.opt.default; | ||
if (!this.opt.choices) { | ||
this.throwParamError('choices'); | ||
} | ||
// If def is a Number, then use as index. Otherwise, check for value. | ||
if (_.isNumber(def) && def >= 0 && def < this.opt.choices.realLength) { | ||
this.selected = def; | ||
} else if (!_.isNumber(def) && def != null) { | ||
let index = _.findIndex(this.opt.choices.realChoices, ({ value }) => value === def); | ||
this.selected = Math.max(index, 0); | ||
} | ||
this.firstRender = true; | ||
this.selected = 0; | ||
// Make sure no default is set (so it won't be printed) | ||
this.opt.default = null; | ||
var def = this.opt.default; | ||
// If def is a Number, then use as index. Otherwise, check for value. | ||
if (_.isNumber(def) && def >= 0 && def < this.opt.choices.realLength) { | ||
this.selected = def; | ||
} else if (!_.isNumber(def) && def != null) { | ||
this.selected = this.opt.choices.pluck('value').indexOf(def); | ||
this.paginator = new Paginator(this.screen); | ||
} | ||
// Make sure no default is set (so it won't be printed) | ||
this.opt.default = null; | ||
/** | ||
* Start the Inquiry session | ||
* @param {Function} cb Callback when prompt is done | ||
* @return {this} | ||
*/ | ||
this.paginator = new Paginator(); | ||
} | ||
util.inherits(Prompt, Base); | ||
_run(cb) { | ||
this.done = cb; | ||
/** | ||
* Start the Inquiry session | ||
* @param {Function} cb Callback when prompt is done | ||
* @return {this} | ||
*/ | ||
var self = this; | ||
Prompt.prototype._run = function (cb) { | ||
this.done = cb; | ||
var events = observe(this.rl); | ||
events.normalizedUpKey.takeUntil(events.line).forEach(this.onUpKey.bind(this)); | ||
events.normalizedDownKey.takeUntil(events.line).forEach(this.onDownKey.bind(this)); | ||
events.numberKey.takeUntil(events.line).forEach(this.onNumberKey.bind(this)); | ||
events.line | ||
.take(1) | ||
.map(this.getCurrentValue.bind(this)) | ||
.flatMap(value => runAsync(self.opt.filter)(value).catch(err => err)) | ||
.forEach(this.onSubmit.bind(this)); | ||
var self = this; | ||
// Init the prompt | ||
cliCursor.hide(); | ||
this.render(); | ||
var events = observe(this.rl); | ||
events.normalizedUpKey.takeUntil(events.line).forEach(this.onUpKey.bind(this)); | ||
events.normalizedDownKey.takeUntil(events.line).forEach(this.onDownKey.bind(this)); | ||
events.numberKey.takeUntil(events.line).forEach(this.onNumberKey.bind(this)); | ||
events.line | ||
.take(1) | ||
.map(this.getCurrentValue.bind(this)) | ||
.flatMap(function (value) { | ||
return runAsync(self.opt.filter)(value).catch(function (err) { | ||
return err; | ||
}); | ||
}) | ||
.forEach(this.onSubmit.bind(this)); | ||
return this; | ||
} | ||
// Init the prompt | ||
cliCursor.hide(); | ||
this.render(); | ||
/** | ||
* Render the prompt to screen | ||
* @return {ListPrompt} self | ||
*/ | ||
return this; | ||
}; | ||
render() { | ||
// Render question | ||
var message = this.getQuestion(); | ||
/** | ||
* Render the prompt to screen | ||
* @return {Prompt} self | ||
*/ | ||
if (this.firstRender) { | ||
message += chalk.dim('(Use arrow keys)'); | ||
} | ||
Prompt.prototype.render = function () { | ||
// Render question | ||
var message = this.getQuestion(); | ||
// Render choices or answer depending on the state | ||
if (this.status === 'answered') { | ||
message += chalk.cyan(this.opt.choices.getChoice(this.selected).short); | ||
} else { | ||
var choicesStr = listRender(this.opt.choices, this.selected); | ||
var indexPosition = this.opt.choices.indexOf( | ||
this.opt.choices.getChoice(this.selected) | ||
); | ||
message += | ||
'\n' + this.paginator.paginate(choicesStr, indexPosition, this.opt.pageSize); | ||
} | ||
if (this.firstRender) { | ||
message += chalk.dim('(Use arrow keys)'); | ||
} | ||
this.firstRender = false; | ||
// Render choices or answer depending on the state | ||
if (this.status === 'answered') { | ||
message += chalk.cyan(this.opt.choices.getChoice(this.selected).short); | ||
} else { | ||
var choicesStr = listRender(this.opt.choices, this.selected); | ||
var indexPosition = this.opt.choices.indexOf(this.opt.choices.getChoice(this.selected)); | ||
message += '\n' + this.paginator.paginate(choicesStr, indexPosition, this.opt.pageSize); | ||
this.screen.render(message); | ||
} | ||
this.firstRender = false; | ||
/** | ||
* When user press `enter` key | ||
*/ | ||
this.screen.render(message); | ||
}; | ||
onSubmit(value) { | ||
this.status = 'answered'; | ||
/** | ||
* When user press `enter` key | ||
*/ | ||
// Rerender prompt | ||
this.render(); | ||
Prompt.prototype.onSubmit = function (value) { | ||
this.status = 'answered'; | ||
this.screen.done(); | ||
cliCursor.show(); | ||
this.done(value); | ||
} | ||
// Rerender prompt | ||
this.render(); | ||
getCurrentValue() { | ||
return this.opt.choices.getChoice(this.selected).value; | ||
} | ||
this.screen.done(); | ||
cliCursor.show(); | ||
this.done(value); | ||
}; | ||
/** | ||
* When user press a key | ||
*/ | ||
onUpKey() { | ||
var len = this.opt.choices.realLength; | ||
this.selected = this.selected > 0 ? this.selected - 1 : len - 1; | ||
this.render(); | ||
} | ||
Prompt.prototype.getCurrentValue = function () { | ||
return this.opt.choices.getChoice(this.selected).value; | ||
}; | ||
onDownKey() { | ||
var len = this.opt.choices.realLength; | ||
this.selected = this.selected < len - 1 ? this.selected + 1 : 0; | ||
this.render(); | ||
} | ||
/** | ||
* When user press a key | ||
*/ | ||
Prompt.prototype.onUpKey = function () { | ||
var len = this.opt.choices.realLength; | ||
this.selected = (this.selected > 0) ? this.selected - 1 : len - 1; | ||
this.render(); | ||
}; | ||
Prompt.prototype.onDownKey = function () { | ||
var len = this.opt.choices.realLength; | ||
this.selected = (this.selected < len - 1) ? this.selected + 1 : 0; | ||
this.render(); | ||
}; | ||
Prompt.prototype.onNumberKey = function (input) { | ||
if (input <= this.opt.choices.realLength) { | ||
this.selected = input - 1; | ||
onNumberKey(input) { | ||
if (input <= this.opt.choices.realLength) { | ||
this.selected = input - 1; | ||
} | ||
this.render(); | ||
} | ||
this.render(); | ||
}; | ||
} | ||
@@ -162,3 +153,3 @@ /** | ||
choices.forEach(function (choice, i) { | ||
choices.forEach((choice, i) => { | ||
if (choice.type === 'separator') { | ||
@@ -178,3 +169,3 @@ separatorOffset++; | ||
var isSelected = (i - separatorOffset === pointer); | ||
var isSelected = i - separatorOffset === pointer; | ||
var line = (isSelected ? figures.pointer + ' ' : ' ') + choice.name; | ||
@@ -189,1 +180,3 @@ if (isSelected) { | ||
} | ||
module.exports = ListPrompt; |
@@ -0,1 +1,2 @@ | ||
'use strict'; | ||
/** | ||
@@ -5,3 +6,2 @@ * `password` type prompt | ||
var util = require('util'); | ||
var chalk = require('chalk'); | ||
@@ -21,97 +21,88 @@ var Base = require('./base'); | ||
/** | ||
* Module exports | ||
*/ | ||
class PasswordPrompt extends Base { | ||
/** | ||
* Start the Inquiry session | ||
* @param {Function} cb Callback when prompt is done | ||
* @return {this} | ||
*/ | ||
module.exports = Prompt; | ||
_run(cb) { | ||
this.done = cb; | ||
/** | ||
* Constructor | ||
*/ | ||
var events = observe(this.rl); | ||
function Prompt() { | ||
return Base.apply(this, arguments); | ||
} | ||
util.inherits(Prompt, Base); | ||
// Once user confirm (enter key) | ||
var submit = events.line.map(this.filterInput.bind(this)); | ||
/** | ||
* Start the Inquiry session | ||
* @param {Function} cb Callback when prompt is done | ||
* @return {this} | ||
*/ | ||
var validation = this.handleSubmitEvents(submit); | ||
validation.success.forEach(this.onEnd.bind(this)); | ||
validation.error.forEach(this.onError.bind(this)); | ||
Prompt.prototype._run = function (cb) { | ||
this.done = cb; | ||
if (this.opt.mask) { | ||
events.keypress.takeUntil(validation.success).forEach(this.onKeypress.bind(this)); | ||
} | ||
var events = observe(this.rl); | ||
// Init | ||
this.render(); | ||
// Once user confirm (enter key) | ||
var submit = events.line.map(this.filterInput.bind(this)); | ||
var validation = this.handleSubmitEvents(submit); | ||
validation.success.forEach(this.onEnd.bind(this)); | ||
validation.error.forEach(this.onError.bind(this)); | ||
if (this.opt.mask) { | ||
events.keypress.takeUntil(validation.success).forEach(this.onKeypress.bind(this)); | ||
return this; | ||
} | ||
// Init | ||
this.render(); | ||
/** | ||
* Render the prompt to screen | ||
* @return {PasswordPrompt} self | ||
*/ | ||
return this; | ||
}; | ||
render(error) { | ||
var message = this.getQuestion(); | ||
var bottomContent = ''; | ||
/** | ||
* Render the prompt to screen | ||
* @return {Prompt} self | ||
*/ | ||
if (this.status === 'answered') { | ||
message += this.opt.mask | ||
? chalk.cyan(mask(this.answer, this.opt.mask)) | ||
: chalk.italic.dim('[hidden]'); | ||
} else if (this.opt.mask) { | ||
message += mask(this.rl.line || '', this.opt.mask); | ||
} else { | ||
message += chalk.italic.dim('[input is hidden] '); | ||
} | ||
Prompt.prototype.render = function (error) { | ||
var message = this.getQuestion(); | ||
var bottomContent = ''; | ||
if (error) { | ||
bottomContent = '\n' + chalk.red('>> ') + error; | ||
} | ||
if (this.status === 'answered') { | ||
message += this.opt.mask ? chalk.cyan(mask(this.answer, this.opt.mask)) : chalk.italic.dim('[hidden]'); | ||
} else if (this.opt.mask) { | ||
message += mask(this.rl.line || '', this.opt.mask); | ||
} else { | ||
message += chalk.italic.dim('[input is hidden] '); | ||
this.screen.render(message, bottomContent); | ||
} | ||
if (error) { | ||
bottomContent = '\n' + chalk.red('>> ') + error; | ||
/** | ||
* When user press `enter` key | ||
*/ | ||
filterInput(input) { | ||
if (!input) { | ||
return this.opt.default == null ? '' : this.opt.default; | ||
} | ||
return input; | ||
} | ||
this.screen.render(message, bottomContent); | ||
}; | ||
onEnd(state) { | ||
this.status = 'answered'; | ||
this.answer = state.value; | ||
/** | ||
* When user press `enter` key | ||
*/ | ||
// Re-render prompt | ||
this.render(); | ||
Prompt.prototype.filterInput = function (input) { | ||
if (!input) { | ||
return this.opt.default == null ? '' : this.opt.default; | ||
this.screen.done(); | ||
this.done(state.value); | ||
} | ||
return input; | ||
}; | ||
Prompt.prototype.onEnd = function (state) { | ||
this.status = 'answered'; | ||
this.answer = state.value; | ||
onError(state) { | ||
this.render(state.isValid); | ||
} | ||
// Re-render prompt | ||
this.render(); | ||
onKeypress() { | ||
this.render(); | ||
} | ||
} | ||
this.screen.done(); | ||
this.done(state.value); | ||
}; | ||
Prompt.prototype.onError = function (state) { | ||
this.render(state.isValid); | ||
}; | ||
Prompt.prototype.onKeypress = function () { | ||
this.render(); | ||
}; | ||
module.exports = PasswordPrompt; |
@@ -0,1 +1,2 @@ | ||
'use strict'; | ||
/** | ||
@@ -6,3 +7,2 @@ * `rawlist` type prompt | ||
var _ = require('lodash'); | ||
var util = require('util'); | ||
var chalk = require('chalk'); | ||
@@ -14,140 +14,137 @@ var Base = require('./base'); | ||
/** | ||
* Module exports | ||
*/ | ||
class RawListPrompt extends Base { | ||
constructor(questions, rl, answers) { | ||
super(questions, rl, answers); | ||
module.exports = Prompt; | ||
if (!this.opt.choices) { | ||
this.throwParamError('choices'); | ||
} | ||
/** | ||
* Constructor | ||
*/ | ||
this.opt.validChoices = this.opt.choices.filter(Separator.exclude); | ||
function Prompt() { | ||
Base.apply(this, arguments); | ||
this.selected = 0; | ||
this.rawDefault = 0; | ||
if (!this.opt.choices) { | ||
this.throwParamError('choices'); | ||
} | ||
_.extend(this.opt, { | ||
validate: function(val) { | ||
return val != null; | ||
} | ||
}); | ||
this.opt.validChoices = this.opt.choices.filter(Separator.exclude); | ||
var def = this.opt.default; | ||
if (_.isNumber(def) && def >= 0 && def < this.opt.choices.realLength) { | ||
this.selected = def; | ||
this.rawDefault = def; | ||
} else if (!_.isNumber(def) && def != null) { | ||
let index = _.findIndex(this.opt.choices.realChoices, ({ value }) => value === def); | ||
let safeIndex = Math.max(index, 0); | ||
this.selected = safeIndex; | ||
this.rawDefault = safeIndex; | ||
} | ||
this.selected = 0; | ||
this.rawDefault = 0; | ||
// Make sure no default is set (so it won't be printed) | ||
this.opt.default = null; | ||
_.extend(this.opt, { | ||
validate: function (val) { | ||
return val != null; | ||
} | ||
}); | ||
var def = this.opt.default; | ||
if (_.isNumber(def) && def >= 0 && def < this.opt.choices.realLength) { | ||
this.selected = this.rawDefault = def; | ||
this.paginator = new Paginator(); | ||
} | ||
// Make sure no default is set (so it won't be printed) | ||
this.opt.default = null; | ||
/** | ||
* Start the Inquiry session | ||
* @param {Function} cb Callback when prompt is done | ||
* @return {this} | ||
*/ | ||
this.paginator = new Paginator(); | ||
} | ||
util.inherits(Prompt, Base); | ||
_run(cb) { | ||
this.done = cb; | ||
/** | ||
* Start the Inquiry session | ||
* @param {Function} cb Callback when prompt is done | ||
* @return {this} | ||
*/ | ||
// Once user confirm (enter key) | ||
var events = observe(this.rl); | ||
var submit = events.line.map(this.getCurrentValue.bind(this)); | ||
Prompt.prototype._run = function (cb) { | ||
this.done = cb; | ||
var validation = this.handleSubmitEvents(submit); | ||
validation.success.forEach(this.onEnd.bind(this)); | ||
validation.error.forEach(this.onError.bind(this)); | ||
// Once user confirm (enter key) | ||
var events = observe(this.rl); | ||
var submit = events.line.map(this.getCurrentValue.bind(this)); | ||
events.keypress.takeUntil(validation.success).forEach(this.onKeypress.bind(this)); | ||
var validation = this.handleSubmitEvents(submit); | ||
validation.success.forEach(this.onEnd.bind(this)); | ||
validation.error.forEach(this.onError.bind(this)); | ||
// Init the prompt | ||
this.render(); | ||
events.keypress.takeUntil(validation.success).forEach(this.onKeypress.bind(this)); | ||
return this; | ||
} | ||
// Init the prompt | ||
this.render(); | ||
/** | ||
* Render the prompt to screen | ||
* @return {RawListPrompt} self | ||
*/ | ||
return this; | ||
}; | ||
render(error) { | ||
// Render question | ||
var message = this.getQuestion(); | ||
var bottomContent = ''; | ||
/** | ||
* Render the prompt to screen | ||
* @return {Prompt} self | ||
*/ | ||
if (this.status === 'answered') { | ||
message += chalk.cyan(this.answer); | ||
} else { | ||
var choicesStr = renderChoices(this.opt.choices, this.selected); | ||
message += this.paginator.paginate(choicesStr, this.selected, this.opt.pageSize); | ||
message += '\n Answer: '; | ||
} | ||
Prompt.prototype.render = function (error) { | ||
// Render question | ||
var message = this.getQuestion(); | ||
var bottomContent = ''; | ||
message += this.rl.line; | ||
if (this.status === 'answered') { | ||
message += chalk.cyan(this.answer); | ||
} else { | ||
var choicesStr = renderChoices(this.opt.choices, this.selected); | ||
message += this.paginator.paginate(choicesStr, this.selected, this.opt.pageSize); | ||
message += '\n Answer: '; | ||
} | ||
if (error) { | ||
bottomContent = '\n' + chalk.red('>> ') + error; | ||
} | ||
message += this.rl.line; | ||
if (error) { | ||
bottomContent = '\n' + chalk.red('>> ') + error; | ||
this.screen.render(message, bottomContent); | ||
} | ||
this.screen.render(message, bottomContent); | ||
}; | ||
/** | ||
* When user press `enter` key | ||
*/ | ||
/** | ||
* When user press `enter` key | ||
*/ | ||
getCurrentValue(index) { | ||
if (index == null || index === '') { | ||
index = this.rawDefault; | ||
} else { | ||
index -= 1; | ||
} | ||
Prompt.prototype.getCurrentValue = function (index) { | ||
if (index == null || index === '') { | ||
index = this.rawDefault; | ||
} else { | ||
index -= 1; | ||
var choice = this.opt.choices.getChoice(index); | ||
return choice ? choice.value : null; | ||
} | ||
var choice = this.opt.choices.getChoice(index); | ||
return choice ? choice.value : null; | ||
}; | ||
onEnd(state) { | ||
this.status = 'answered'; | ||
this.answer = state.value; | ||
Prompt.prototype.onEnd = function (state) { | ||
this.status = 'answered'; | ||
this.answer = state.value; | ||
// Re-render prompt | ||
this.render(); | ||
// Re-render prompt | ||
this.render(); | ||
this.screen.done(); | ||
this.done(state.value); | ||
} | ||
this.screen.done(); | ||
this.done(state.value); | ||
}; | ||
onError() { | ||
this.render('Please enter a valid index'); | ||
} | ||
Prompt.prototype.onError = function () { | ||
this.render('Please enter a valid index'); | ||
}; | ||
/** | ||
* When user press a key | ||
*/ | ||
/** | ||
* When user press a key | ||
*/ | ||
onKeypress() { | ||
var index = this.rl.line.length ? Number(this.rl.line) - 1 : 0; | ||
Prompt.prototype.onKeypress = function () { | ||
var index = this.rl.line.length ? Number(this.rl.line) - 1 : 0; | ||
if (this.opt.choices.getChoice(index)) { | ||
this.selected = index; | ||
} else { | ||
this.selected = undefined; | ||
} | ||
if (this.opt.choices.getChoice(index)) { | ||
this.selected = index; | ||
} else { | ||
this.selected = undefined; | ||
this.render(); | ||
} | ||
} | ||
this.render(); | ||
}; | ||
/** | ||
@@ -163,3 +160,3 @@ * Function for rendering list choices | ||
choices.forEach(function (choice, i) { | ||
choices.forEach(function(choice, i) { | ||
output += '\n '; | ||
@@ -174,3 +171,3 @@ | ||
var index = i - separatorOffset; | ||
var display = (index + 1) + ') ' + choice.name; | ||
var display = index + 1 + ') ' + choice.name; | ||
if (index === pointer) { | ||
@@ -184,1 +181,3 @@ display = chalk.cyan(display); | ||
} | ||
module.exports = RawListPrompt; |
@@ -10,51 +10,53 @@ 'use strict'; | ||
var UI = module.exports = function (opt) { | ||
// Instantiate the Readline interface | ||
// @Note: Don't reassign if already present (allow test to override the Stream) | ||
if (!this.rl) { | ||
this.rl = readline.createInterface(setupReadlineOptions(opt)); | ||
} | ||
this.rl.resume(); | ||
class UI { | ||
constructor(opt) { | ||
// Instantiate the Readline interface | ||
// @Note: Don't reassign if already present (allow test to override the Stream) | ||
if (!this.rl) { | ||
this.rl = readline.createInterface(setupReadlineOptions(opt)); | ||
} | ||
this.rl.resume(); | ||
this.onForceClose = this.onForceClose.bind(this); | ||
this.onForceClose = this.onForceClose.bind(this); | ||
// Make sure new prompt start on a newline when closing | ||
process.on('exit', this.onForceClose); | ||
// Make sure new prompt start on a newline when closing | ||
process.on('exit', this.onForceClose); | ||
// Terminate process on SIGINT (which will call process.on('exit') in return) | ||
this.rl.on('SIGINT', this.onForceClose); | ||
}; | ||
// Terminate process on SIGINT (which will call process.on('exit') in return) | ||
this.rl.on('SIGINT', this.onForceClose); | ||
} | ||
/** | ||
* Handle the ^C exit | ||
* @return {null} | ||
*/ | ||
/** | ||
* Handle the ^C exit | ||
* @return {null} | ||
*/ | ||
UI.prototype.onForceClose = function () { | ||
this.close(); | ||
process.kill(process.pid, 'SIGINT'); | ||
console.log(''); | ||
}; | ||
onForceClose() { | ||
this.close(); | ||
process.kill(process.pid, 'SIGINT'); | ||
console.log(''); | ||
} | ||
/** | ||
* Close the interface and cleanup listeners | ||
*/ | ||
/** | ||
* Close the interface and cleanup listeners | ||
*/ | ||
UI.prototype.close = function () { | ||
// Remove events listeners | ||
this.rl.removeListener('SIGINT', this.onForceClose); | ||
process.removeListener('exit', this.onForceClose); | ||
close() { | ||
// Remove events listeners | ||
this.rl.removeListener('SIGINT', this.onForceClose); | ||
process.removeListener('exit', this.onForceClose); | ||
this.rl.output.unmute(); | ||
this.rl.output.unmute(); | ||
if (this.activePrompt && typeof this.activePrompt.close === 'function') { | ||
this.activePrompt.close(); | ||
if (this.activePrompt && typeof this.activePrompt.close === 'function') { | ||
this.activePrompt.close(); | ||
} | ||
// Close the readline | ||
this.rl.output.end(); | ||
this.rl.pause(); | ||
this.rl.close(); | ||
} | ||
} | ||
// Close the readline | ||
this.rl.output.end(); | ||
this.rl.pause(); | ||
this.rl.close(); | ||
}; | ||
function setupReadlineOptions(opt) { | ||
@@ -71,7 +73,12 @@ opt = opt || {}; | ||
return _.extend({ | ||
terminal: true, | ||
input: input, | ||
output: output | ||
}, _.omit(opt, ['input', 'output'])); | ||
return _.extend( | ||
{ | ||
terminal: true, | ||
input: input, | ||
output: output | ||
}, | ||
_.omit(opt, ['input', 'output']) | ||
); | ||
} | ||
module.exports = UI; |
@@ -0,1 +1,2 @@ | ||
'use strict'; | ||
/** | ||
@@ -5,3 +6,2 @@ * Sticky bottom bar user interface | ||
var util = require('util'); | ||
var through = require('through'); | ||
@@ -12,97 +12,90 @@ var Base = require('./baseUI'); | ||
/** | ||
* Module exports | ||
*/ | ||
class BottomBar extends Base { | ||
constructor(opt) { | ||
opt = opt || {}; | ||
module.exports = Prompt; | ||
super(opt); | ||
/** | ||
* Constructor | ||
*/ | ||
this.log = through(this.writeLog.bind(this)); | ||
this.bottomBar = opt.bottomBar || ''; | ||
this.render(); | ||
} | ||
function Prompt(opt) { | ||
opt || (opt = {}); | ||
/** | ||
* Render the prompt to screen | ||
* @return {BottomBar} self | ||
*/ | ||
Base.apply(this, arguments); | ||
render() { | ||
this.write(this.bottomBar); | ||
return this; | ||
} | ||
this.log = through(this.writeLog.bind(this)); | ||
this.bottomBar = opt.bottomBar || ''; | ||
this.render(); | ||
} | ||
util.inherits(Prompt, Base); | ||
clean() { | ||
rlUtils.clearLine(this.rl, this.bottomBar.split('\n').length); | ||
return this; | ||
} | ||
/** | ||
* Render the prompt to screen | ||
* @return {Prompt} self | ||
*/ | ||
/** | ||
* Update the bottom bar content and rerender | ||
* @param {String} bottomBar Bottom bar content | ||
* @return {BottomBar} self | ||
*/ | ||
Prompt.prototype.render = function () { | ||
this.write(this.bottomBar); | ||
return this; | ||
}; | ||
updateBottomBar(bottomBar) { | ||
rlUtils.clearLine(this.rl, 1); | ||
this.rl.output.unmute(); | ||
this.clean(); | ||
this.bottomBar = bottomBar; | ||
this.render(); | ||
this.rl.output.mute(); | ||
return this; | ||
} | ||
Prompt.prototype.clean = function () { | ||
rlUtils.clearLine(this.rl, this.bottomBar.split('\n').length); | ||
return this; | ||
}; | ||
/** | ||
* Write out log data | ||
* @param {String} data - The log data to be output | ||
* @return {BottomBar} self | ||
*/ | ||
/** | ||
* Update the bottom bar content and rerender | ||
* @param {String} bottomBar Bottom bar content | ||
* @return {Prompt} self | ||
*/ | ||
writeLog(data) { | ||
this.rl.output.unmute(); | ||
this.clean(); | ||
this.rl.output.write(this.enforceLF(data.toString())); | ||
this.render(); | ||
this.rl.output.mute(); | ||
return this; | ||
} | ||
Prompt.prototype.updateBottomBar = function (bottomBar) { | ||
rlUtils.clearLine(this.rl, 1); | ||
this.rl.output.unmute(); | ||
this.clean(); | ||
this.bottomBar = bottomBar; | ||
this.render(); | ||
this.rl.output.mute(); | ||
return this; | ||
}; | ||
/** | ||
* Make sure line end on a line feed | ||
* @param {String} str Input string | ||
* @return {String} The input string with a final line feed | ||
*/ | ||
/** | ||
* Write out log data | ||
* @param {String} data - The log data to be output | ||
* @return {Prompt} self | ||
*/ | ||
enforceLF(str) { | ||
return str.match(/[\r\n]$/) ? str : str + '\n'; | ||
} | ||
Prompt.prototype.writeLog = function (data) { | ||
this.rl.output.unmute(); | ||
this.clean(); | ||
this.rl.output.write(this.enforceLF(data.toString())); | ||
this.render(); | ||
this.rl.output.mute(); | ||
return this; | ||
}; | ||
/** | ||
* Helper for writing message in Prompt | ||
* @param {BottomBar} prompt - The Prompt object that extends tty | ||
* @param {String} message - The message to be output | ||
*/ | ||
write(message) { | ||
var msgLines = message.split(/\n/); | ||
this.height = msgLines.length; | ||
/** | ||
* Make sure line end on a line feed | ||
* @param {String} str Input string | ||
* @return {String} The input string with a final line feed | ||
*/ | ||
// Write message to screen and setPrompt to control backspace | ||
this.rl.setPrompt(_.last(msgLines)); | ||
Prompt.prototype.enforceLF = function (str) { | ||
return str.match(/[\r\n]$/) ? str : str + '\n'; | ||
}; | ||
if (this.rl.output.rows === 0 && this.rl.output.columns === 0) { | ||
/* When it's a tty through serial port there's no terminal info and the render will malfunction, | ||
so we need enforce the cursor to locate to the leftmost position for rendering. */ | ||
rlUtils.left(this.rl, message.length + this.rl.line.length); | ||
} | ||
this.rl.output.write(message); | ||
} | ||
} | ||
/** | ||
* Helper for writing message in Prompt | ||
* @param {Prompt} prompt - The Prompt object that extends tty | ||
* @param {String} message - The message to be output | ||
*/ | ||
Prompt.prototype.write = function (message) { | ||
var msgLines = message.split(/\n/); | ||
this.height = msgLines.length; | ||
// Write message to screen and setPrompt to control backspace | ||
this.rl.setPrompt(_.last(msgLines)); | ||
if (this.rl.output.rows === 0 && this.rl.output.columns === 0) { | ||
/* When it's a tty through serial port there's no terminal info and the render will malfunction, | ||
so we need enforce the cursor to locate to the leftmost position for rendering. */ | ||
rlUtils.left(this.rl, message.length + this.rl.line.length); | ||
} | ||
this.rl.output.write(message); | ||
}; | ||
module.exports = BottomBar; |
'use strict'; | ||
var _ = require('lodash'); | ||
var rx = require('rx-lite-aggregates'); | ||
var util = require('util'); | ||
var runAsync = require('run-async'); | ||
@@ -13,104 +12,109 @@ var utils = require('../utils/utils'); | ||
var PromptUI = module.exports = function (prompts, opt) { | ||
Base.call(this, opt); | ||
this.prompts = prompts; | ||
}; | ||
util.inherits(PromptUI, Base); | ||
PromptUI.prototype.run = function (questions) { | ||
// Keep global reference to the answers | ||
this.answers = {}; | ||
// Make sure questions is an array. | ||
if (_.isPlainObject(questions)) { | ||
questions = [questions]; | ||
class PromptUI extends Base { | ||
constructor(prompts, opt) { | ||
super(opt); | ||
this.prompts = prompts; | ||
} | ||
// Create an observable, unless we received one as parameter. | ||
// Note: As this is a public interface, we cannot do an instanceof check as we won't | ||
// be using the exact same object in memory. | ||
var obs = _.isArray(questions) ? rx.Observable.from(questions) : questions; | ||
run(questions) { | ||
// Keep global reference to the answers | ||
this.answers = {}; | ||
this.process = obs | ||
.concatMap(this.processQuestion.bind(this)) | ||
// `publish` creates a hot Observable. It prevents duplicating prompts. | ||
.publish(); | ||
// Make sure questions is an array. | ||
if (_.isPlainObject(questions)) { | ||
questions = [questions]; | ||
} | ||
this.process.connect(); | ||
// Create an observable, unless we received one as parameter. | ||
// Note: As this is a public interface, we cannot do an instanceof check as we won't | ||
// be using the exact same object in memory. | ||
var obs = _.isArray(questions) ? rx.Observable.from(questions) : questions; | ||
return this.process | ||
.reduce(function (answers, answer) { | ||
_.set(this.answers, answer.name, answer.answer); | ||
return this.answers; | ||
}.bind(this), {}) | ||
.toPromise(Promise) | ||
.then(this.onCompletion.bind(this)); | ||
}; | ||
this.process = obs | ||
.concatMap(this.processQuestion.bind(this)) | ||
// `publish` creates a hot Observable. It prevents duplicating prompts. | ||
.publish(); | ||
/** | ||
* Once all prompt are over | ||
*/ | ||
this.process.connect(); | ||
PromptUI.prototype.onCompletion = function (answers) { | ||
this.close(); | ||
return this.process | ||
.reduce((answers, answer) => { | ||
_.set(this.answers, answer.name, answer.answer); | ||
return this.answers; | ||
}, {}) | ||
.toPromise(Promise) | ||
.then(this.onCompletion.bind(this)); | ||
} | ||
return answers; | ||
}; | ||
/** | ||
* Once all prompt are over | ||
*/ | ||
PromptUI.prototype.processQuestion = function (question) { | ||
question = _.clone(question); | ||
return rx.Observable.defer(function () { | ||
var obs = rx.Observable.of(question); | ||
onCompletion(answers) { | ||
this.close(); | ||
return obs | ||
.concatMap(this.setDefaultType.bind(this)) | ||
.concatMap(this.filterIfRunnable.bind(this)) | ||
.concatMap(utils.fetchAsyncQuestionProperty.bind(null, question, 'message', this.answers)) | ||
.concatMap(utils.fetchAsyncQuestionProperty.bind(null, question, 'default', this.answers)) | ||
.concatMap(utils.fetchAsyncQuestionProperty.bind(null, question, 'choices', this.answers)) | ||
.concatMap(this.fetchAnswer.bind(this)); | ||
}.bind(this)); | ||
}; | ||
return answers; | ||
} | ||
PromptUI.prototype.fetchAnswer = function (question) { | ||
var Prompt = this.prompts[question.type]; | ||
this.activePrompt = new Prompt(question, this.rl, this.answers); | ||
return rx.Observable.defer(function () { | ||
return rx.Observable.fromPromise(this.activePrompt.run().then(function (answer) { | ||
return {name: question.name, answer: answer}; | ||
})); | ||
}.bind(this)); | ||
}; | ||
processQuestion(question) { | ||
question = _.clone(question); | ||
return rx.Observable.defer(() => { | ||
var obs = rx.Observable.of(question); | ||
PromptUI.prototype.setDefaultType = function (question) { | ||
// Default type to input | ||
if (!this.prompts[question.type]) { | ||
question.type = 'input'; | ||
return obs | ||
.concatMap(this.setDefaultType.bind(this)) | ||
.concatMap(this.filterIfRunnable.bind(this)) | ||
.concatMap(() => | ||
utils.fetchAsyncQuestionProperty(question, 'message', this.answers) | ||
) | ||
.concatMap(() => | ||
utils.fetchAsyncQuestionProperty(question, 'default', this.answers) | ||
) | ||
.concatMap(() => | ||
utils.fetchAsyncQuestionProperty(question, 'choices', this.answers) | ||
) | ||
.concatMap(this.fetchAnswer.bind(this)); | ||
}); | ||
} | ||
return rx.Observable.defer(function () { | ||
return rx.Observable.return(question); | ||
}); | ||
}; | ||
PromptUI.prototype.filterIfRunnable = function (question) { | ||
if (question.when === false) { | ||
return rx.Observable.empty(); | ||
fetchAnswer(question) { | ||
var Prompt = this.prompts[question.type]; | ||
this.activePrompt = new Prompt(question, this.rl, this.answers); | ||
return rx.Observable.defer(() => | ||
rx.Observable.fromPromise( | ||
this.activePrompt.run().then(answer => ({ name: question.name, answer: answer })) | ||
) | ||
); | ||
} | ||
if (!_.isFunction(question.when)) { | ||
return rx.Observable.return(question); | ||
setDefaultType(question) { | ||
// Default type to input | ||
if (!this.prompts[question.type]) { | ||
question.type = 'input'; | ||
} | ||
return rx.Observable.defer(() => rx.Observable.return(question)); | ||
} | ||
var answers = this.answers; | ||
return rx.Observable.defer(function () { | ||
return rx.Observable.fromPromise( | ||
runAsync(question.when)(answers).then(function (shouldRun) { | ||
if (shouldRun) { | ||
return question; | ||
} | ||
}) | ||
).filter(function (val) { | ||
return val != null; | ||
}); | ||
}); | ||
}; | ||
filterIfRunnable(question) { | ||
if (question.when === false) { | ||
return rx.Observable.empty(); | ||
} | ||
if (!_.isFunction(question.when)) { | ||
return rx.Observable.return(question); | ||
} | ||
var answers = this.answers; | ||
return rx.Observable.defer(() => | ||
rx.Observable.fromPromise( | ||
runAsync(question.when)(answers).then(shouldRun => { | ||
if (shouldRun) { | ||
return question; | ||
} | ||
}) | ||
).filter(val => val != null) | ||
); | ||
} | ||
} | ||
module.exports = PromptUI; |
@@ -5,11 +5,9 @@ 'use strict'; | ||
function normalizeKeypressEvents(value, key) { | ||
return {value: value, key: key || {}}; | ||
return { value: value, key: key || {} }; | ||
} | ||
module.exports = function (rl) { | ||
module.exports = function(rl) { | ||
var keypress = rx.Observable.fromEvent(rl.input, 'keypress', normalizeKeypressEvents) | ||
.filter(function (e) { | ||
// Ignore `enter` key. On the readline, we only care about the `line` event. | ||
return e.key.name !== 'enter' && e.key.name !== 'return'; | ||
}); | ||
// Ignore `enter` key. On the readline, we only care about the `line` event. | ||
.filter(({ key }) => key.name !== 'enter' && key.name !== 'return'); | ||
@@ -20,28 +18,25 @@ return { | ||
normalizedUpKey: keypress.filter(function (e) { | ||
return e.key.name === 'up' || e.key.name === 'k' || (e.key.name === 'p' && e.key.ctrl); | ||
}).share(), | ||
normalizedUpKey: keypress | ||
.filter( | ||
({ key }) => | ||
key.name === 'up' || key.name === 'k' || (key.name === 'p' && key.ctrl) | ||
) | ||
.share(), | ||
normalizedDownKey: keypress.filter(function (e) { | ||
return e.key.name === 'down' || e.key.name === 'j' || (e.key.name === 'n' && e.key.ctrl); | ||
}).share(), | ||
normalizedDownKey: keypress | ||
.filter( | ||
({ key }) => | ||
key.name === 'down' || key.name === 'j' || (key.name === 'n' && key.ctrl) | ||
) | ||
.share(), | ||
numberKey: keypress.filter(function (e) { | ||
return e.value && '123456789'.indexOf(e.value) >= 0; | ||
}).map(function (e) { | ||
return Number(e.value); | ||
}).share(), | ||
numberKey: keypress | ||
.filter(e => e.value && '123456789'.indexOf(e.value) >= 0) | ||
.map(e => Number(e.value)) | ||
.share(), | ||
spaceKey: keypress.filter(function (e) { | ||
return e.key && e.key.name === 'space'; | ||
}).share(), | ||
aKey: keypress.filter(function (e) { | ||
return e.key && e.key.name === 'a'; | ||
}).share(), | ||
iKey: keypress.filter(function (e) { | ||
return e.key && e.key.name === 'i'; | ||
}).share() | ||
spaceKey: keypress.filter(({ key }) => key && key.name === 'space').share(), | ||
aKey: keypress.filter(({ key }) => key && key.name === 'a').share(), | ||
iKey: keypress.filter(({ key }) => key && key.name === 'i').share() | ||
}; | ||
}; |
@@ -11,29 +11,44 @@ 'use strict'; | ||
var Paginator = module.exports = function () { | ||
this.pointer = 0; | ||
this.lastIndex = 0; | ||
}; | ||
class Paginator { | ||
constructor(screen) { | ||
this.pointer = 0; | ||
this.lastIndex = 0; | ||
this.screen = screen; | ||
} | ||
Paginator.prototype.paginate = function (output, active, pageSize) { | ||
pageSize = pageSize || 7; | ||
var middleOfList = Math.floor(pageSize / 2); | ||
var lines = output.split('\n'); | ||
paginate(output, active, pageSize) { | ||
pageSize = pageSize || 7; | ||
var middleOfList = Math.floor(pageSize / 2); | ||
var lines = output.split('\n'); | ||
// Make sure there's enough lines to paginate | ||
if (lines.length <= pageSize) { | ||
return output; | ||
} | ||
if (this.screen) { | ||
lines = this.screen.breakLines(lines); | ||
active = _.sum(lines.map(lineParts => lineParts.length).splice(0, active)); | ||
lines = _.flatten(lines); | ||
} | ||
// Move the pointer only when the user go down and limit it to the middle of the list | ||
if (this.pointer < middleOfList && this.lastIndex < active && active - this.lastIndex < pageSize) { | ||
this.pointer = Math.min(middleOfList, this.pointer + active - this.lastIndex); | ||
// Make sure there's enough lines to paginate | ||
if (lines.length <= pageSize) { | ||
return output; | ||
} | ||
// Move the pointer only when the user go down and limit it to the middle of the list | ||
if ( | ||
this.pointer < middleOfList && | ||
this.lastIndex < active && | ||
active - this.lastIndex < pageSize | ||
) { | ||
this.pointer = Math.min(middleOfList, this.pointer + active - this.lastIndex); | ||
} | ||
this.lastIndex = active; | ||
// Duplicate the lines so it give an infinite list look | ||
var infinite = _.flatten([lines, lines, lines]); | ||
var topIndex = Math.max(0, active + lines.length - this.pointer); | ||
var section = infinite.splice(topIndex, pageSize).join('\n'); | ||
return section + '\n' + chalk.dim('(Move up and down to reveal more choices)'); | ||
} | ||
this.lastIndex = active; | ||
} | ||
// Duplicate the lines so it give an infinite list look | ||
var infinite = _.flatten([lines, lines, lines]); | ||
var topIndex = Math.max(0, active + lines.length - this.pointer); | ||
var section = infinite.splice(topIndex, pageSize).join('\n'); | ||
return section + '\n' + chalk.dim('(Move up and down to reveal more choices)'); | ||
}; | ||
module.exports = Paginator; |
@@ -10,3 +10,3 @@ 'use strict'; | ||
exports.left = function (rl, x) { | ||
exports.left = function(rl, x) { | ||
rl.output.write(ansiEscapes.cursorBackward(x)); | ||
@@ -21,3 +21,3 @@ }; | ||
exports.right = function (rl, x) { | ||
exports.right = function(rl, x) { | ||
rl.output.write(ansiEscapes.cursorForward(x)); | ||
@@ -32,3 +32,3 @@ }; | ||
exports.up = function (rl, x) { | ||
exports.up = function(rl, x) { | ||
rl.output.write(ansiEscapes.cursorUp(x)); | ||
@@ -43,3 +43,3 @@ }; | ||
exports.down = function (rl, x) { | ||
exports.down = function(rl, x) { | ||
rl.output.write(ansiEscapes.cursorDown(x)); | ||
@@ -53,4 +53,4 @@ }; | ||
*/ | ||
exports.clearLine = function (rl, len) { | ||
exports.clearLine = function(rl, len) { | ||
rl.output.write(ansiEscapes.eraseLines(len)); | ||
}; |
@@ -16,121 +16,125 @@ 'use strict'; | ||
var ScreenManager = module.exports = function (rl) { | ||
// These variables are keeping information to allow correct prompt re-rendering | ||
this.height = 0; | ||
this.extraLinesUnderPrompt = 0; | ||
class ScreenManager { | ||
constructor(rl) { | ||
// These variables are keeping information to allow correct prompt re-rendering | ||
this.height = 0; | ||
this.extraLinesUnderPrompt = 0; | ||
this.rl = rl; | ||
}; | ||
this.rl = rl; | ||
} | ||
ScreenManager.prototype.render = function (content, bottomContent) { | ||
this.rl.output.unmute(); | ||
this.clean(this.extraLinesUnderPrompt); | ||
render(content, bottomContent) { | ||
this.rl.output.unmute(); | ||
this.clean(this.extraLinesUnderPrompt); | ||
/** | ||
* Write message to screen and setPrompt to control backspace | ||
*/ | ||
/** | ||
* Write message to screen and setPrompt to control backspace | ||
*/ | ||
var promptLine = lastLine(content); | ||
var rawPromptLine = stripAnsi(promptLine); | ||
var promptLine = lastLine(content); | ||
var rawPromptLine = stripAnsi(promptLine); | ||
// Remove the rl.line from our prompt. We can't rely on the content of | ||
// rl.line (mainly because of the password prompt), so just rely on it's | ||
// length. | ||
var prompt = rawPromptLine; | ||
if (this.rl.line.length) { | ||
prompt = prompt.slice(0, -this.rl.line.length); | ||
} | ||
this.rl.setPrompt(prompt); | ||
// Remove the rl.line from our prompt. We can't rely on the content of | ||
// rl.line (mainly because of the password prompt), so just rely on it's | ||
// length. | ||
var prompt = rawPromptLine; | ||
if (this.rl.line.length) { | ||
prompt = prompt.slice(0, -this.rl.line.length); | ||
} | ||
this.rl.setPrompt(prompt); | ||
// setPrompt will change cursor position, now we can get correct value | ||
var cursorPos = this.rl._getCursorPos(); | ||
var width = this.normalizedCliWidth(); | ||
// SetPrompt will change cursor position, now we can get correct value | ||
var cursorPos = this.rl._getCursorPos(); | ||
var width = this.normalizedCliWidth(); | ||
content = forceLineReturn(content, width); | ||
if (bottomContent) { | ||
bottomContent = forceLineReturn(bottomContent, width); | ||
} | ||
// Manually insert an extra line if we're at the end of the line. | ||
// This prevent the cursor from appearing at the beginning of the | ||
// current line. | ||
if (rawPromptLine.length % width === 0) { | ||
content += '\n'; | ||
} | ||
var fullContent = content + (bottomContent ? '\n' + bottomContent : ''); | ||
this.rl.output.write(fullContent); | ||
content = this.forceLineReturn(content, width); | ||
if (bottomContent) { | ||
bottomContent = this.forceLineReturn(bottomContent, width); | ||
} | ||
// Manually insert an extra line if we're at the end of the line. | ||
// This prevent the cursor from appearing at the beginning of the | ||
// current line. | ||
if (rawPromptLine.length % width === 0) { | ||
content += '\n'; | ||
} | ||
var fullContent = content + (bottomContent ? '\n' + bottomContent : ''); | ||
this.rl.output.write(fullContent); | ||
/** | ||
* Re-adjust the cursor at the correct position. | ||
*/ | ||
/** | ||
* Re-adjust the cursor at the correct position. | ||
*/ | ||
// We need to consider parts of the prompt under the cursor as part of the bottom | ||
// content in order to correctly cleanup and re-render. | ||
var promptLineUpDiff = Math.floor(rawPromptLine.length / width) - cursorPos.rows; | ||
var bottomContentHeight = promptLineUpDiff + (bottomContent ? height(bottomContent) : 0); | ||
if (bottomContentHeight > 0) { | ||
util.up(this.rl, bottomContentHeight); | ||
} | ||
// We need to consider parts of the prompt under the cursor as part of the bottom | ||
// content in order to correctly cleanup and re-render. | ||
var promptLineUpDiff = Math.floor(rawPromptLine.length / width) - cursorPos.rows; | ||
var bottomContentHeight = | ||
promptLineUpDiff + (bottomContent ? height(bottomContent) : 0); | ||
if (bottomContentHeight > 0) { | ||
util.up(this.rl, bottomContentHeight); | ||
} | ||
// Reset cursor at the beginning of the line | ||
util.left(this.rl, stringWidth(lastLine(fullContent))); | ||
// Reset cursor at the beginning of the line | ||
util.left(this.rl, stringWidth(lastLine(fullContent))); | ||
// Adjust cursor on the right | ||
util.right(this.rl, cursorPos.cols); | ||
// Adjust cursor on the right | ||
util.right(this.rl, cursorPos.cols); | ||
/** | ||
* Set up state for next re-rendering | ||
*/ | ||
this.extraLinesUnderPrompt = bottomContentHeight; | ||
this.height = height(fullContent); | ||
/** | ||
* Set up state for next re-rendering | ||
*/ | ||
this.extraLinesUnderPrompt = bottomContentHeight; | ||
this.height = height(fullContent); | ||
this.rl.output.mute(); | ||
}; | ||
this.rl.output.mute(); | ||
} | ||
ScreenManager.prototype.clean = function (extraLines) { | ||
if (extraLines > 0) { | ||
util.down(this.rl, extraLines); | ||
clean(extraLines) { | ||
if (extraLines > 0) { | ||
util.down(this.rl, extraLines); | ||
} | ||
util.clearLine(this.rl, this.height); | ||
} | ||
util.clearLine(this.rl, this.height); | ||
}; | ||
ScreenManager.prototype.done = function () { | ||
this.rl.setPrompt(''); | ||
this.rl.output.unmute(); | ||
this.rl.output.write('\n'); | ||
}; | ||
done() { | ||
this.rl.setPrompt(''); | ||
this.rl.output.unmute(); | ||
this.rl.output.write('\n'); | ||
} | ||
ScreenManager.prototype.releaseCursor = function () { | ||
if (this.extraLinesUnderPrompt > 0) { | ||
util.down(this.rl, this.extraLinesUnderPrompt); | ||
releaseCursor() { | ||
if (this.extraLinesUnderPrompt > 0) { | ||
util.down(this.rl, this.extraLinesUnderPrompt); | ||
} | ||
} | ||
}; | ||
ScreenManager.prototype.normalizedCliWidth = function () { | ||
var width = cliWidth({ | ||
defaultWidth: 80, | ||
output: this.rl.output | ||
}); | ||
if (process.platform === 'win32') { | ||
return width - 1; | ||
normalizedCliWidth() { | ||
var width = cliWidth({ | ||
defaultWidth: 80, | ||
output: this.rl.output | ||
}); | ||
if (process.platform === 'win32') { | ||
return width - 1; | ||
} | ||
return width; | ||
} | ||
return width; | ||
}; | ||
function breakLines(lines, width) { | ||
// Break lines who're longuer than the cli width so we can normalize the natural line | ||
// returns behavior accross terminals. | ||
var regex = new RegExp( | ||
'(?:(?:\\033[[0-9;]*m)*.?){1,' + width + '}', | ||
'g' | ||
); | ||
return lines.map(function (line) { | ||
var chunk = line.match(regex); | ||
// last match is always empty | ||
chunk.pop(); | ||
return chunk || ''; | ||
}); | ||
breakLines(lines, width) { | ||
// Break lines who're longer than the cli width so we can normalize the natural line | ||
// returns behavior across terminals. | ||
width = width || this.normalizedCliWidth(); | ||
var regex = new RegExp('(?:(?:\\033[[0-9;]*m)*.?){1,' + width + '}', 'g'); | ||
return lines.map(line => { | ||
var chunk = line.match(regex); | ||
// Last match is always empty | ||
chunk.pop(); | ||
return chunk || ''; | ||
}); | ||
} | ||
forceLineReturn(content, width) { | ||
width = width || this.normalizedCliWidth(); | ||
return _.flatten(this.breakLines(content.split('\n'), width)).join('\n'); | ||
} | ||
} | ||
function forceLineReturn(content, width) { | ||
return _.flatten(breakLines(content.split('\n'), width)).join('\n'); | ||
} | ||
module.exports = ScreenManager; |
@@ -15,3 +15,3 @@ 'use strict'; | ||
exports.fetchAsyncQuestionProperty = function (question, prop, answers) { | ||
exports.fetchAsyncQuestionProperty = function(question, prop, answers) { | ||
if (!_.isFunction(question[prop])) { | ||
@@ -21,4 +21,4 @@ return rx.Observable.return(question); | ||
return rx.Observable.fromPromise(runAsync(question[prop])(answers) | ||
.then(function (value) { | ||
return rx.Observable.fromPromise( | ||
runAsync(question[prop])(answers).then(value => { | ||
question[prop] = value; | ||
@@ -25,0 +25,0 @@ return question; |
{ | ||
"name": "inquirer", | ||
"version": "3.3.0", | ||
"description": "A collection of common interactive command line user interfaces.", | ||
"version": "4.0.0", | ||
"description": | ||
"A collection of common interactive command line user interfaces.", | ||
"author": "Simon Boudrias <admin@simonboudrias.com>", | ||
"files": [ | ||
"lib" | ||
], | ||
"files": ["lib"], | ||
"main": "lib/inquirer.js", | ||
"keywords": [ | ||
"command", | ||
"prompt", | ||
"stdin", | ||
"cli", | ||
"tty", | ||
"menu" | ||
], | ||
"keywords": ["command", "prompt", "stdin", "cli", "tty", "menu"], | ||
"engines": { | ||
"node": ">=6.0.0" | ||
}, | ||
"devDependencies": { | ||
"chai": "^4.0.1", | ||
"cmdify": "^0.0.4", | ||
"coveralls": "^3.0.0", | ||
"eslint": "^4.1.0", | ||
"eslint-config-prettier": "^2.4.0", | ||
"eslint-config-xo": "^0.19.0", | ||
"eslint-plugin-prettier": "^2.2.0", | ||
"husky": "^0.14.3", | ||
"lint-staged": "^5.0.0", | ||
"mocha": "^4.0.0", | ||
"mockery": "^2.1.0", | ||
"nsp": "^3.0.0", | ||
"nyc": "^11.3.0", | ||
"prettier": "^1.7.0", | ||
"sinon": "^4.0.0" | ||
}, | ||
"scripts": { | ||
"test": "gulp", | ||
"prepublish": "gulp prepublish" | ||
"test": "nyc mocha test/** -r ./test/before", | ||
"prepublish": "nsp check", | ||
"pretest": "eslint .", | ||
"precommit": "lint-staged", | ||
"coverage": "nyc report --reporter=text-lcov | coveralls" | ||
}, | ||
@@ -40,21 +55,31 @@ "repository": "SBoudrias/Inquirer.js", | ||
}, | ||
"devDependencies": { | ||
"chai": "^4.0.1", | ||
"cmdify": "^0.0.4", | ||
"eslint": "^4.2.0", | ||
"eslint-config-xo-space": "^0.16.0", | ||
"gulp": "^3.9.0", | ||
"gulp-codacy": "^1.0.0", | ||
"gulp-coveralls": "^0.1.0", | ||
"gulp-eslint": "^4.0.0", | ||
"gulp-exclude-gitignore": "^1.0.0", | ||
"gulp-istanbul": "^1.1.2", | ||
"gulp-line-ending-corrector": "^1.0.1", | ||
"gulp-mocha": "^3.0.0", | ||
"gulp-nsp": "^2.1.0", | ||
"gulp-plumber": "^1.0.0", | ||
"mocha": "^3.4.2", | ||
"mockery": "^2.1.0", | ||
"sinon": "^3.0.0" | ||
"lint-staged": { | ||
"*.js": ["eslint --fix", "git add"], | ||
"*.json": ["prettier --write", "git add"] | ||
}, | ||
"eslintConfig": { | ||
"extends": ["xo", "prettier"], | ||
"env": { | ||
"mocha": true, | ||
"node": true | ||
}, | ||
"rules": { | ||
"no-eq-null": "off", | ||
"eqeqeq": [ | ||
"error", | ||
"always", | ||
{ | ||
"null": "ignore" | ||
} | ||
], | ||
"prettier/prettier": [ | ||
"error", | ||
{ | ||
"singleQuote": true, | ||
"printWidth": 90 | ||
} | ||
] | ||
}, | ||
"plugins": ["prettier"] | ||
} | ||
} |
@@ -8,2 +8,4 @@ Inquirer.js | ||
**Version 4.x** only supports Node 6 and over. For Node 4 support please use [version 3.x](https://github.com/SBoudrias/Inquirer.js/tree/v3.3.0). | ||
## Table of Contents | ||
@@ -57,3 +59,3 @@ | ||
var inquirer = require('inquirer'); | ||
inquirer.prompt([/* Pass your questions in here */]).then(function (answers) { | ||
inquirer.prompt([/* Pass your questions in here */]).then(answers => { | ||
// Use user feedback for... whatever!! | ||
@@ -126,3 +128,3 @@ }); | ||
/* Preferred way: with promise */ | ||
filter: function () { | ||
filter() { | ||
return new Promise(/* etc... */); | ||
@@ -137,3 +139,3 @@ }, | ||
// Do async stuff | ||
setTimeout(function () { | ||
setTimeout(function() { | ||
if (typeof input !== 'number') { | ||
@@ -140,0 +142,0 @@ // Pass the return value in the done callback |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
70889
15
1850
389