UNPKG

cli-chain

Version:

chainable cli creation, minimal, interactive, powerful.

191 lines (164 loc) 4.63 kB
/** * @file a Stepper chain * @exports Stepper * @requires inquirer * @requires fliplog * @requires ./StepperPersistance */ const {inquirer, log} = require('./deps') const StepperPersistance = require('./StepperPersistance') /** * @prop {*} parent * @prop {Object} data answers from prompting * @prop {* | Function} answerCallbacks * @prop {boolean} goingBack for stepping back in interactive * @prop {number} indx current step index * @prop {Object} prompter inquirer instance * @prop {Object} ui inquirer.ui * @prop {Array<Step>} steps in Stepper.start * @prop {Function} resolve in Stepper.start, for using .then * @prop {Object} stdin for subscribing to stdin events (for going back) * @prop {StepperPersistance} persistance persisting & hydrating * @type {ChainedMap} */ class Stepper { /** * @since 0.0.1 * @param {Chainable | any} parent */ constructor(parent) { this.data = {} this.parent = parent this.answerCallbacks = parent.answerCallbacks this.goingBack = false this.persistance = new StepperPersistance(this) } /** * @description * sets this.resolve in a returned promise * which can be used by Stepper.then * when iterating through steps in Stepper.thenner * * @since 0.0.8 * @param {Array<Step>} steps * @return {Promise} */ start(steps) { // this.data = {} this.steps = steps this.indx = 0 if (this.persistance.isHydratable(this.steps)) { this.persistance.thenner(this.steps) } else { this._start() } return new Promise(resolve => { this.resolve = resolve }) } /** * @protected * @since 0.0.11 * @see this.start, persistance.hydrate * @desc does actual setup * @return {Stepper} @chainable */ _start() { this.setupStdIn() this.thenner() return this } /** * @TODO going back 2 doubles things, need to clear and fix? * * @tutorial https://github.com/SBoudrias/Inquirer.js/issues/405 * @since 0.0.9 * @desc sets up process.stdin observer that will step back if needed * @see Stepper.index, Stepper.thenner, Stepper.start * @return {Stepper} @chainable */ setupStdIn() { // https://github.com/SBoudrias/Inquirer.js/issues/405 const stdin = process.stdin const onStdIn = buffer => { const key = buffer.toString() // control + b if (key === '\u0002') { if (this.goingBack !== false) { // console.log('\n goingBack was not false', this.goingBack) return buffer } // ignore because we cannot go back if (this.indx === 0) { return buffer } // console.log('\ncatching ctrl+b, going back 1 step', this.goingBack) this.indx = this.indx - 1 this.goingBack = true // remove listener, will continue afterwards // otherwise it listens twice this.ui.close() setTimeout(() => { this.goingBack = false }, 500) // thenFn(this.data) this.thenner() } else if (key === '\u0003') { // control + c this.persistance.persist() } return buffer } stdin.on('data', onStdIn) this.stdin = stdin return this } /** * @see Stepper.steps * @since 0.0.6 * @return {Promise | null} */ thenner() { const steps = this.steps[this.indx] // done steps, save and resolve promise if (!steps) { // this.indx = 0 this.persistance.persist() this.resolve(this.data) return null } // scoped const thenFn = answers => { if (this.goingBack === true) { console.log('thenned when going back...') } // log.quick(this.answerCallbacks.values(), this.parent) // HERE, COULD KEEP A COPY OF THE STEPS BEFORE .TOSTEPS // THEN ADD A METHOD LIKE `FROM-STEP` // AND DEFAULT-FOR // console.log({answers}, this.steps) this.answerCallbacks.values().forEach(cb => { const args = { answers, steps, stepper: this, } cb(args) }) Object.assign(this.data, answers) this.indx++ setTimeout(() => this.thenner(), 1) } if (this.indx === 0) { steps.message += log.colored(' (use [cmd + b] to go back)', 'dim') } this.prompter = inquirer.prompt(steps, {input: this.stdin}) this.ui = this.prompter.ui // if (steps.type === 'checkbox') steps.message += ' (use [spacebar])' this.prompter.then(thenFn).catch(log.catch) return this.prompter } } module.exports = Stepper