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.

433 lines (349 loc) 14.5 kB
'use strict'; var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } var fs = require('fs'); var path = require('path'); var read = fs.readFileSync; var exists = fs.existsSync; var join = path.join; var debug = require('./debug')('tabtab:complete'); var assign = require('object-assign'); var uniq = require('lodash.uniq'); var difference = require('lodash.difference'); var _require = require('events'); var EventEmitter = _require.EventEmitter; var CacheMixin = require('./cache'); // Public: Complete class. This is the main API to interract with the // completion system and extends EventEmitter. // // Examples // // var complete = new Complete({ // name: 'binary-name' // }); // // complete.on('list', function(data, done) { // return done(null, ['completion', 'result', 'here']); // }); var Complete = function (_EventEmitter) { _inherits(Complete, _EventEmitter); _createClass(Complete, [{ key: 'defaults', // Public: Options defaults. // // Returns the binary name being completed. Uses process.title if not the // default "node" value, or attempt to determine the package name from // package.json file. get: function get() { return { name: process.title !== 'node' ? process.title : '' }; } // Public: Complete constructor, accepts options hash with // // options - Accepts options hash (default: {}) // // Examples // // new Complete({ // // the String package name being completed, defaults to process.title // // (if not node default) or will attempt to determine parent's // // package.json location and extract the name from it. // name: 'foobar' // // // Enable / Disable cache (defaults: true) // cache: true, // // // Cache Time To Live duration in ms (default: 5min) // ttl: 1000 * 60 * 5 // }); }]); function Complete(options) { _classCallCheck(this, Complete); var _this = _possibleConstructorReturn(this, (Complete.__proto__ || Object.getPrototypeOf(Complete)).call(this)); _this.options = options || _this.defaults; _this.options.name = _this.options.name || _this.resolve('name'); _this.options.cache = typeof _this.options.cache !== 'undefined' ? _this.options.cache : true; _this.options.ttl = _this.options.ttl || 1000 * 60 * 5; _this._results = []; _this._completions = []; return _this; } _createClass(Complete, [{ key: 'start', value: function start() { this.handle(); } // Public: Takes care of all the completion plumbing mechanism. // // It checks the environment to determine if we act in plumbing mode or not, // to parse COMP args and emit the appropriated events to gather completion // results. // // options - options hash to pass to self#parseEnv }, { key: 'handle', value: function handle(options) { var _this2 = this; options = assign({}, options, this.options); var name = options.name; if (!name) throw new Error('Cannot determine package name'); var env = this.env = this.parseEnv(options); if (env.args[0] !== 'completion') return; var line = env.line.replace(name, '').trim(); var first = line.split(' ')[0]; if (first) first = first.trim(); var event = (first || env.prev || name).trim(); var cache = this.cache(env.line); if (this.options.cache && cache) { return process.nextTick(function () { // this.addCompletions(cache.value); _this2.send(event + ':cache', env); debug('cache', cache.value); _this2.output(cache.value); }); } if (!env.complete) { return debug('Completion command but without COMP args'); } process.nextTick(function () { _this2.completePackage(env); var prev = env.prev.trim(); name = name.trim(); // Keeps emitting event only if previous one is not being listened to. // Emits in series: first, prev and name. _this2.send(event, env, _this2.recv.bind(_this2)); if (prev !== event) _this2.send(prev, env, _this2.recv.bind(_this2)); if (name !== event && name !== prev) _this2.send(name, env, _this2.recv.bind(_this2)); if (_this2.options.cache) { _this2.cache(env.line, _this2._results); _this2.writeToStore(_this2.cacheStore); } }); } }, { key: 'output', value: function output(results) { var _this3 = this; var shell = (process.env.SHELL || '').split('/').slice(-1)[0]; if (shell === 'bash') { results = results.filter(function (res) { return res.indexOf(_this3.env.last) === 0; }); } // create a duplicate-free version of results results = uniq(results); // only include results different from past completion results results = difference(results, this._completions); this._completions = this._completions.concat(results); debug('results:', results, this.env); console.log(results.join('\n')); } }, { key: 'send', value: function send(evt, env, done) { var res = this.emit(evt, env, done); debug('Emit evt: %s (listener: %s)', evt, res); return res; } }, { key: 'completePackage', value: function completePackage(env, stop) { var config = this.resolve('tabtab'); if (!config) return; var pkgname = config[this.options.name]; var last = (env.last || env.prev).trim(); var prop = last || this.options.name; if (!prop) return; if (stop) { var first = env.line.split(' ')[0]; var results = config[first]; if (!results) return; return this.recv(null, results, env); } // Keeps looking up for completion only if previous one have not returned // any results. var command = config[prop]; var completions = this.recv(null, command, env); if (!completions) { if (last && !stop) { var reg = new RegExp('\\s*' + last + '\\s*$'); var line = env.line.replace(reg, ''); this.completePackage(this.parseEnv({ env: { COMP_LINE: line, COMP_WORD: env.words, COMP_POINT: env.point } }), true); } } return true; } // Public: Completions handlers // // will call back this function with an Array of completion items. // // err - Error object // completions - The Array of String completion results to write to stdout // env - Env object as parsed by parseEnv }, { key: 'recv', value: function recv(err, completions, env) { if (!completions) return; env = env || this.env; debug('Received %s', completions); if (err) return this.emit('error', err); this.addCompletions(completions); this.output(this._results); return completions; } }, { key: 'addCompletions', value: function addCompletions(completions) { completions = Array.isArray(completions) ? completions : [completions]; var shell = (process.env.SHELL || '').split('/').slice(-1)[0]; completions = completions.map(this.completionItem).map(function (item) { return shell === 'zsh' ? item.name.replace(/:/g, '\\:') + ':' + item.description : shell === 'fish' ? item.name + '\t' + item.description : item.name; }); this._results = this._results.concat(completions); } }, { key: 'completionItem', value: function completionItem(str) { debug('completion item', str, typeof str === 'undefined' ? 'undefined' : _typeof(str)); if (typeof str !== 'string') return str; var shell = (process.env.SHELL || '').split('/').slice(-1)[0]; var parts = str.split(/(\\)?:/); var name = parts[0]; var desc = parts.slice(-1)[0]; if (desc === name) { desc = ''; } if (shell === 'zsh' && /\\/.test(str)) { name = name + '\\'; } return { name: name, description: desc }; } // Public: Main utility to extract information from command line arguments and // Environment variables, namely COMP args in "plumbing" mode. // // options - The options hash as parsed by minimist, plus an env property // representing user environment (default: { env: process.env }) // :_ - The arguments Array parsed by minimist (positional arguments) // :env - The environment Object that holds COMP args (default: process.env) // // Examples // // var env = complete.parseEnv(); // // env: // // args the positional arguments used // // complete A Boolean indicating whether we act in "plumbing mode" or not // // words The Number of words in the completed line // // point A Number indicating cursor position // // line The String input line // // partial The String part of line preceding cursor position // // last The last String word of the line // // lastPartial The last word String of partial // // prev The String word preceding last // // Returns the data env object. }, { key: 'parseEnv', value: function parseEnv(options) { options = options || {}; options = assign({}, options, this.options); var args = options._ || process.argv.slice(2); var env = options.env || process.env; var cword = Number(env.COMP_CWORD); var point = Number(env.COMP_POINT); var line = env.COMP_LINE || ''; if (isNaN(cword)) cword = 0; if (isNaN(point)) point = 0; var partial = line.slice(0, point); var parts = line.split(' '); var prev = parts.slice(0, -1).slice(-1)[0]; var last = parts.slice(-1).join(''); var lastPartial = partial.split(' ').slice(-1).join(''); var complete = args[0] === 'completion'; if (!env.COMP_CWORD || typeof env.COMP_POINT === 'undefined' || typeof env.COMP_LINE === 'undefined') { complete = false; } return { args: args, complete: complete, words: cword, point: point, line: line, partial: partial, last: last, lastPartial: lastPartial, prev: prev || '' }; } // Public: Script templating helper // // Outputs npm's completion script with pkgname and completer placeholder // replaced. // // name - The String package/binary name // complete - The String completer name, usualy the same as name above. Can // differ to delegate the completion behavior to another command. // // Returns the script content with placeholders replaced }, { key: 'script', value: function script(name, completer, shell) { return read(join(__dirname, '../scripts/' + (shell || 'completion') + '.sh'), 'utf8').replace(/\{pkgname\}/g, name).replace(/{completer}/g, completer); } // Public: Recursively walk up the `module.parent` chain to find original file. }, { key: 'findParent', value: function findParent(module, last) { if (!module.parent) return module; return this.findParent(module.parent); } // Public: Recursively walk up the directories, untill it finds the `file` // provided, or reach the user $HOME dir. }, { key: 'findUp', value: function findUp(file, dir) { dir = path.resolve(dir || './'); // stop at user $HOME dir if (dir === process.env.HOME) return; if (exists(join(dir, file))) return join(dir, file); return this.findUp(file, path.dirname(dir)); } // Public: package name resolver. // // When options.name is not defined, this gets called to attempt to determine // completer name. // // It'll attempt to follow the module chain and find the package.json file to // determine the command name being completed. }, { key: 'resolve', value: function resolve(prop) { // `module` is special node builtin var parent = this.findParent(module); if (!parent) return; var dirname = path.dirname(parent.filename); // was invoked by cli tabtab, fallback to local package.json if (parent.filename === path.join(__dirname, '../bin/tabtab')) { dirname = path.resolve(); } var jsonfile = this.findUp('package.json', dirname); if (!jsonfile) return; return require(jsonfile)[prop]; } }]); return Complete; }(EventEmitter); assign(Complete.prototype, CacheMixin); module.exports = Complete;