/*! * question-store * * Copyright (c) 2015, Jon Schlinkert. * Licensed under the MIT License. */ 'use strict'; var util = require('util'); var debug = require('debug')('questions:question'); var utils = require('./utils'); /** * Create new `Question` store `name`, with the given `options`. * * ```js * var question = new Question(name, options); * ``` * @param {String} `name` The question property name. * @param {Object} `options` Store options * @api public */ function Question(name, message, options) { if (utils.isObject(name)) { options = utils.merge({}, message, name); message = options.message; name = options.name; } if (utils.isObject(message)) { options = utils.merge({}, options, message); message = options.message; } utils.define(this, 'isQuestion', true); utils.define(this, 'cache', {}); this.options = options || {}; this.type = this.options.type || 'input'; this.message = message || this.options.message; this.name = name || this.options.name; if (!this.message) { this.message = this.name; } utils.merge(this, this.options); createNext(this); } /** * Create the next question to ask, if `next` is passed on the * options. * * @param {Object} `app` question instance */ function createNext(question) { if (!question.options.next) return; if (typeof question.options.next === 'function') { question.next = function() { question.options.next.apply(question, arguments); }; return; } if (typeof question.options.next === 'string') { question.type = 'confirm'; question.next = function(answer, questions, answers, next) { if (answer === true) { questions.ask(question.options.next, next); } else { next(null, answers); } }; } } /** * Optionally define the next question to ask by setting a custom `next` * function on the question object. * * ```js * questions.choice('deps', 'Where?') * questions.set('install', 'Want to install?', { * type: 'confirm', * next: function(answer, questions, answers, cb) { * if (answer === true) { * questions.ask('config.other', cb); * } else { * cb(null, answers); * } * } * }); * ``` * * @param {Object} `answer` * @param {Object} `questions` * @param {Object} `answers` * @param {Function} `next` * @return {Function} * @api public */ Question.prototype.next = function(answer, questions, answers, next) { next(null, answers); }; /** * Merge the given `options` object with `questions.options` and expose * `get`, `set` and `enabled` properties to simplify checking for options values. * * @param {Object} options * @return {Object} */ Question.prototype.opts = function(options) { var args = [].slice.call(arguments); options = [].concat.apply([], args); var opts = utils.omitEmpty(utils.merge.apply(utils.merge, [{}].concat(options))); utils.decorate(opts); if (typeof opts.default !== 'undefined') { this.default = opts.default; } if (opts.persist === false) { opts.global = false; opts.hint = false; opts.save = false; } if (opts.save === false) { opts.force = true; } return opts; }; Question.prototype.setAnswer = function(val) { this.answer = val; return this; }; /** * Resolve the answer for the question from the given data sources, then * set the question's `default` value with any stored hints or answers * if not already defined. * * ```js * question.answer(answers, data, store, hints); * ``` * @param {Object} `answers` * @param {Object} `data` * @param {Object} `store` * @param {Object} `hints` * @return {Object} * @api public */ Question.prototype.getAnswer = function(answers, data, app) { debug('looking for answer for: "%s"', this.name); // data passed on options var answer = utils.get(data, this.name); debug('answer:data %j', util.inspect(answer)); // cached answers (ignoring dot-notation in key) if (typeof answer === 'undefined') { answer = answers[this.name]; debug('answer:answers %j', util.inspect(answer)); } // cached answers (respecting dot-notation in key) if (typeof answer === 'undefined') { answer = utils.get(answers, this.name); debug('answer:answers.get %j', util.inspect(answer)); } // app.store, if one exists if (typeof answer === 'undefined' && app.store) { answer = app.store.get(this.name); debug('answer:store.get %j', util.inspect(answer)); } // app.globals store, if one exists if (typeof answer === 'undefined' && this.options.global === true && app.globals) { answer = app.globals.get(this.name); debug('answer:globals.get %j', util.inspect(answer)); } // if `type` is checkbox, return now. we don't want to set hints for checkboxes if (this.type === 'checkbox' || this.options.hints === false) { debug('returning answer: %j', answer); this.setAnswer(answer); return answer; } if (typeof answer !== 'undefined') { this.default = answer; debug('default:answer %j', util.inspect(this.default)); } // app.hints store, if one exists if (typeof this.default === 'undefined' && app.hints) { this.default = app.hints.get(this.name); debug('default:hint %j', util.inspect(this.default)); } debug('returning answer: %j', answer); this.setAnswer(answer); return answer; }; /** * Force the question to be asked. * * ```js * question.force(); * ``` * @return {undefined} * @api public */ Question.prototype.force = function() { this.options.force = true; return this; }; /** * Expose `Question` */ module.exports = Question;