UNPKG

serverless-spy

Version:

CDK-based library for writing elegant integration tests on AWS serverless architecture and an additional web console to monitor events in real time.

235 lines (195 loc) 7.34 kB
"use strict"; const tabtab = require('tabtab'); const parseArgs = require('micromist'); const Promise = require('bluebird'); const Option = require('./option'); class Autocomplete { constructor(program) { this._program = program; this._tab = tabtab({ name: this._program.bin(), cache: false }); this._completions = {}; } listen(options) { this._tab.on(this._program.bin(), this._complete.bind(this)); this._tab.handle(options); } /** * Register a completion handler * * @param {Argument|Option} arg_or_opt argument or option to complete * @param {Function} completer */ registerCompletion(arg_or_opt, completer) { this._completions[arg_or_opt.name()] = completer; } /** * @param {Argument|Option} arg_or_opt argument or option to complete */ getCompletion(arg_or_opt) { return this._completions[arg_or_opt.name()]; } _findCommand(cmdStr) { let cmd; const commandArr = []; const args = cmdStr.split(' ').filter(s => s != ''); const commands = this._program.getCommands(); while (!cmd && args.length) { commandArr.push(args.shift()); const cmdStr = commandArr.join(' '); cmd = commands.filter(c => { return (c.name() === cmdStr || c.getAlias() === cmdStr) })[0]; } if (!cmd && this._program._getDefaultCommand()) { cmd = this._program._getDefaultCommand(); } return cmd; } _countArgs(currentCommand, args) { let realArgsCmdFound = false; return args._.reduce((acc, value, index, arr) => { const possibleCmd = arr.slice(0, index + 1).join(' '); if (currentCommand && (currentCommand.name() === possibleCmd || currentCommand.alias() === possibleCmd)) { realArgsCmdFound = true; } else if (realArgsCmdFound && value.trim()) { acc++; } return acc; }, 0); } _getOptionsAlreadyUsed(args) { return args.filter(o => o.startsWith('-') || o.startsWith('--')); } _lastPartialIsKnownOption(currentCommand, lastPartialIsOption, lastPartial) { return lastPartialIsOption && currentCommand && currentCommand.options().some(o => (lastPartial === o.getShortName() || lastPartial === o.getLongName())); } _lastPartialIsKnownArg(currentCommand, lastPartialIsArg, lastPartial) { return lastPartialIsArg && currentCommand && currentCommand.args().some( a => (a.getChoices().length && a.getChoices().includes(lastPartial)) ); } _getCurrentOption(currentCommand, lastPartialIsKnownOption, lastPartial) { return lastPartialIsKnownOption ? currentCommand.options() .filter(o => (lastPartial === o.getShortName() || lastPartial === o.getLongName()))[0] : null; } _getPossibleArgumentValues(currentCommand, lastPartialIsOption, argsCount, lastPartial) { if (!currentCommand || lastPartialIsOption) { return Promise.resolve([]); } const arg = currentCommand.args(lastPartial ? argsCount - 1 : argsCount); if (!arg) { return Promise.resolve([]); } // Choices if (arg.getChoices().length) { return Promise.resolve( arg.getChoices() .map(choice => choice + ':Value for argument ' + arg.synopsis()) .filter(choice => !lastPartial || choice.startsWith(lastPartial)) ); } // Promise completion const completion = this.getCompletion(arg); if (typeof completion === 'function') { return this._hanldleCompletionHandler(completion, arg); } return Promise.resolve([]); } _hanldleCompletionHandler(handler, argOrOpt) { const result = handler(); const type = argOrOpt instanceof Option ? 'option' : 'argument'; return Promise.resolve(result).then(res => { if (!Array.isArray(res)) { res = [res]; } return res.map(r => r + `:Value for ${type} ${argOrOpt.synopsis()}`) }); } _getPossibleOptionNames(currentCommand, optionsAlreadyUsed, lastPartial, lastPartialIsOption) { const optNames = currentCommand ? currentCommand.options().map(o => { if ((o.getShortName() && o.getShortName() != lastPartial && o.getShortName().startsWith(lastPartial)) || (o.getLongName() && o.getLongName() != lastPartial && o.getLongName().startsWith(lastPartial))) { return o.getLongOrShortName() + ':' + o.description(); } else if (!lastPartialIsOption && optionsAlreadyUsed.indexOf(o.getShortName()) === -1 && optionsAlreadyUsed.indexOf(o.getLongName()) === -1) { return o.getLongOrShortName() + ':' + o.description(); } }).filter(o => typeof o != 'undefined') : []; return Promise.resolve(optNames); } _getPossibleOptionValues(currentOption) { if (!currentOption) { return Promise.resolve([]); } // Choices if (currentOption.getChoices().length) { return Promise.resolve(currentOption.getChoices()); } // Promise completion const completion = this.getCompletion(currentOption); if (typeof completion === 'function') { return this._hanldleCompletionHandler(completion, currentOption); } return Promise.resolve([]); } _getPossibleCommands(currentCommand, cmdStr) { const commands = this._program .getCommands() .filter(c => { if (!cmdStr) { return true; } return (c.name().startsWith(cmdStr) || (c.getAlias() && c.getAlias().startsWith(cmdStr))) && (!currentCommand || !this._isSameCommand(c, currentCommand)) }) .filter(c => c.name() !== '') // do not take the default command .map(c => { return c.name() + ':' + c.description() }); return Promise.resolve(commands); } _isSameCommand(command, command2) { return command2.name() === command.name(); } _complete(data, done) { const currCommand = this._findCommand(data.args.slice(3).join(' ')); const args = parseArgs(data.args.slice(1), currCommand ? currCommand.parseArgsOpts : {}); const cmd = args._.join(' '); const realArgsCount = this._countArgs(currCommand, args); const optionsAlreadyUsed = this._getOptionsAlreadyUsed(data.args); const lastPartial = data.lastPartial; const lastPartIsOption = data.lastPartial.startsWith('-') const lastPartIsKnownOption = this._lastPartialIsKnownOption(currCommand, lastPartIsOption, lastPartial); const currOption = this._getCurrentOption(currCommand, lastPartIsKnownOption, lastPartial); const possArgValues = this._getPossibleArgumentValues(currCommand, lastPartIsOption, realArgsCount, data.lastPartial); const possOptNames = this._getPossibleOptionNames(currCommand, optionsAlreadyUsed, lastPartial, lastPartIsOption); const possOptValues = this._getPossibleOptionValues(currOption); const possCommands = this._getPossibleCommands(currCommand, cmd); return Promise.all([possCommands, possArgValues, possOptNames, possOptValues]) .then(function (results) { const completions = [] .concat.apply([], results) .filter(e => typeof e != 'undefined'); done(null, completions); return completions; }) .catch(err => { done(err); return []; }) } } module.exports = Autocomplete;