berlioz
Version:
Berlioz - cloud deployment and migration services
1,328 lines (1,146 loc) • 31.7 kB
JavaScript
'use strict';
/**
* Polyfill for ES6.
*/
if (!global._babelPolyfill) {
// When the runtime transformer properly detects all shimmed methods, use instead.
// http://www.2ality.com/2015/12/babel6-helpersstandard-library.html#babel-plugin-transform-runtime
require('babel-polyfill');
}
/**
* Module dependencies.
*/
var _ = require('lodash');
var EventEmitter = require('events').EventEmitter;
var Command = require('./command');
var CommandInstance = require('./command-instance');
var VorpalUtil = require('./util');
var ui = require('./ui');
var Session = require('./session');
var intercept = require('./intercept');
var minimist = require('minimist');
var commons = require('./vorpal-commons');
var chalk = require('chalk');
var os = require('os');
var History = require('./history');
var LocalStorage = require('./local-storage');
var wrap = require('wrap-ansi');
/**
* Initialize a new `Vorpal` instance.
*
* @return {Vorpal}
* @api public
*/
function Vorpal() {
if (!(this instanceof Vorpal)) {
return new Vorpal();
}
// Program version
// Exposed through vorpal.version(str);
this._version = '';
// Program title
this._title = '';
// Program description
this._description = '';
// Program baner
this._banner = '';
// Command line history instance
this.cmdHistory = new this.CmdHistoryExtension();
// Registered `vorpal.command` commands and
// their options.
this.commands = [];
// Queue of IP requests, executed async, in sync.
this._queue = [];
// Current command being executed.
this._command = undefined;
// Expose UI.
this.ui = ui;
// Expose chalk as a convenience.
this.chalk = chalk;
// Expose lodash as a convenience.
this.lodash = _;
// Exposed through vorpal.delimiter(str).
this._delimiter = 'local@' + String(os.hostname()).split('.')[0] + '~$ ';
ui.setDelimiter(this._delimiter);
// Placeholder for vantage server. If vantage
// is used, this will be over-written.
this.server = {
sessions: []
};
// Whether all stdout is being hooked through a function.
this._hooked = false;
this._useDeprecatedAutocompletion = false;
// Expose common utilities, like padding.
this.util = VorpalUtil;
this.Session = Session;
// Active vorpal server session.
this.session = new this.Session({
local: true,
user: 'local',
parent: this,
delimiter: this._delimiter
});
// Allow unix-like key value pair normalization to be turned off by toggling this switch on.
this.isCommandArgKeyPairNormalized = true;
this._init();
return this;
}
/**
* Extend Vorpal prototype as an event emitter.
*/
Vorpal.prototype = Object.create(EventEmitter.prototype);
/**
* Vorpal prototype.
*/
var vorpal = Vorpal.prototype;
/**
* Expose `Vorpal`.
*/
exports = module.exports = Vorpal;
/**
* Extension to `constructor`.
* @api private
*/
Vorpal.prototype._init = function () {
var self = this;
ui.on('vorpal_ui_keypress', function (data) {
self.emit('keypress', data);
self._onKeypress(data.key, data.value);
});
self.use(commons);
};
/**
* Parses `process.argv` and executes
* a Vorpal command based on it.
* @api public
*/
Vorpal.prototype.parse = function (argv, options) {
options = options || {};
var args = argv;
var result = this;
var catchExists = !(_.find(this.commands, { _catch: true }) === undefined);
args.shift();
args.shift();
if (args.length > 0 || catchExists) {
if (options.use === 'minimist') {
result = minimist(args);
} else {
// Wrap the spaced args back in quotes.
for (var i = 0; i < args.length; ++i) {
if (i === 0) {
continue;
}
if (args[i].indexOf(' ') > -1) {
args[i] = '"' + args[i] + '"';
}
}
this.exec(args.join(' '), function (err) {
if (err !== undefined && err !== null) {
throw new Error(err);
}
process.exit(0);
});
}
}
return result;
};
/**
* Sets version of your application's API.
*
* @param {String} version
* @return {Vorpal}
* @api public
*/
vorpal.version = function (version) {
this._version = version;
return this;
};
/**
* Sets the title of your application.
*
* @param {String} title
* @return {Vorpal}
* @api public
*/
vorpal.title = function (title) {
this._title = title;
return this;
};
/**
* Sets the description of your application.
*
* @param {String} description
* @return {Vorpal}
* @api public
*/
vorpal.description = function (description) {
this._description = description;
return this;
};
/**
* Sets the banner of your application.
*
* @param {String} banner
* @return {Vorpal}
* @api public
*/
vorpal.banner = function (banner) {
this._banner = banner;
return this;
};
/**
* Sets the permanent delimiter for this
* Vorpal server instance.
*
* @param {String} str
* @return {Vorpal}
* @api public
*/
vorpal.delimiter = function (str) {
this._delimiter = str;
if (this.session.isLocal() && !this.session.client) {
this.session.delimiter(str);
}
return this;
};
/**
* Imports a library of Vorpal API commands
* from another Node module as an extension
* of Vorpal.
*
* @param {Array} commands
* @return {Vorpal}
* @api public
*/
vorpal.use = function (commands, options) {
if (!commands) {
return this;
}
if (_.isFunction(commands)) {
commands.call(this, this, options);
} else if (_.isString(commands)) {
return this.use(require(commands), options);
} else {
commands = _.isArray(commands) ? commands : [commands];
for (var i = 0; i < commands.length; ++i) {
var cmd = commands[i];
if (cmd.command) {
var command = this.command(cmd.command);
if (cmd.description) {
command.description(cmd.description);
}
if (cmd.options) {
cmd.options = _.isArray(cmd.options) ? cmd.options : [cmd.options];
for (var j = 0; j < cmd.options.length; ++j) {
command.option(cmd.options[j][0], cmd.options[j][1]);
}
}
if (cmd.action) {
command.action(cmd.action);
}
}
}
}
return this;
};
/**
* Registers a new command in the vorpal API.
*
* @param {String} name
* @param {String} desc
* @param {Object} opts
* @return {Command}
* @api public
*/
vorpal.command = function (name, desc, opts) {
opts = opts || {};
name = String(name);
var argsRegExp = /(\[[^\]]*\]|\<[^\>]*\>)/g;
var args = [];
var arg;
while ((arg = argsRegExp.exec(name)) !== null) {
args.push(arg[1]);
}
var cmdNameRegExp = /^([^\[\<]*)/;
var cmdName = cmdNameRegExp.exec(name)[0].trim();
var cmd = new Command(cmdName, this);
if (desc) {
cmd.description(desc);
this.executables = true;
}
cmd._noHelp = Boolean(opts.noHelp);
cmd._mode = opts.mode || false;
cmd._catch = opts.catch || false;
cmd._parseExpectedArgs(args);
cmd.parent = this;
var exists = false;
for (var i = 0; i < this.commands.length; ++i) {
exists = this.commands[i]._name === cmd._name ? true : exists;
if (exists) {
this.commands[i] = cmd;
break;
}
}
if (!exists) {
this.commands.push(cmd);
} else {
console.warn(chalk.yellow('Warning: command named "' + name + '" was registered more than once.\nIf you intend to override a command, you should explicitly remove the first command with command.remove().'));
}
this.emit('command_registered', { command: cmd, name: name });
return cmd;
};
/**
* Registers a new 'mode' command in the vorpal API.
*
* @param {String} name
* @param {String} desc
* @param {Object} opts
* @return {Command}
* @api public
*/
vorpal.mode = function (name, desc, opts) {
return this.command(name, desc, _.extend(opts || {}, { mode: true }));
};
/**
* Registers a 'catch' command in the vorpal API.
* This is executed when no command matches are found.
*
* @param {String} name
* @param {String} desc
* @param {Object} opts
* @return {Command}
* @api public
*/
vorpal.catch = function (name, desc, opts) {
return this.command(name, desc, _.extend(opts || {}, { catch: true }));
};
/**
* An alias to the `catch` command.
*
* @param {String} name
* @param {String} desc
* @param {Object} opts
* @return {Command}
* @api public
*/
vorpal.default = function (name, desc, opts) {
return this.command(name, desc, _.extend(opts || {}, { catch: true }));
};
/**
* Delegates to ui.log.
*
* @param {String} log
* @return {Vorpal}
* @api public
*/
vorpal.log = function () {
this.ui.log.apply(this.ui, arguments);
return this;
};
/**
* Intercepts all logging through `vorpal.log`
* and runs it through the function declared by
* `vorpal.pipe()`.
*
* @param {Function} fn
* @return {Vorpal}
* @api public
*/
vorpal.pipe = function (fn) {
if (this.ui) {
this.ui._pipeFn = fn;
}
return this;
};
/**
* If Vorpal is the local terminal,
* hook all stdout, through a fn.
*
* @return {Vorpal}
* @api private
*/
vorpal.hook = function (fn) {
if (fn !== undefined) {
this._hook(fn);
} else {
this._unhook();
}
return this;
};
/**
* Unhooks stdout capture.
*
* @return {Vorpal}
* @api public
*/
vorpal._unhook = function () {
if (this._hooked && this._unhook !== undefined) {
this._unhook();
this._hooked = false;
}
return this;
};
/**
* Hooks all stdout through a given function.
*
* @param {Function} fn
* @return {Vorpal}
* @api public
*/
vorpal._hook = function (fn) {
if (this._hooked && this._unhook !== undefined) {
this._unhook();
}
this._unhook = intercept(fn);
this._hooked = true;
return this;
};
/**
* History module used to get command history
*/
vorpal.CmdHistoryExtension = History;
/**
* Set id for command line history
* @param id
* @return {Vorpal}
* @api public
*/
vorpal.history = function (id) {
this.cmdHistory.setId(id);
return this;
};
/**
* Set id for local storage
* @param id
* @return {Vorpal}
* @api public
*/
vorpal.localStorage = function (id) {
var ls = Object.create(LocalStorage);
ls.setId(id);
_.extend(this.localStorage, ls);
return this;
};
/**
* Set the path to where command line history is persisted.
* Must be called before vorpal.history
* @param path
* @return {Vorpal}
* @api public
*/
vorpal.historyStoragePath = function (path) {
this.cmdHistory.setStoragePath(path);
return this;
};
/**
* Hook the tty prompt to this given instance
* of vorpal.
*
* @return {Vorpal}
* @api public
*/
vorpal.show = function () {
ui.attach(this);
return this;
};
/**
* Disables the vorpal prompt on the
* local terminal.
*
* @return {Vorpal}
* @api public
*/
vorpal.hide = function () {
ui.detach(this);
return this;
};
/**
* Listener for a UI keypress. Either
* handles the keypress locally or sends
* it upstream.
*
* @param {String} key
* @param {String} value
* @api private
*/
vorpal._onKeypress = function (key, value) {
var self = this;
if (this.session.isLocal() && !this.session.client && !this._command) {
this.session.getKeypressResult(key, value, function (err, result) {
if (!err && result !== undefined) {
if (_.isArray(result)) {
var formatted = VorpalUtil.prettifyArray(result);
self.ui.imprint();
self.session.log(formatted);
} else {
self.ui.input(result);
}
}
});
} else {
this._send('vantage-keypress-upstream', 'upstream', {
key: key,
value: value,
sessionId: this.session.id
});
}
};
/**
* For use in vorpal API commands, sends
* a prompt command downstream to the local
* terminal. Executes a prompt and returns
* the response upstream to the API command.
*
* @param {Object} options
* @param {Function} userCallback
* @return {Vorpal}
* @api public
*/
vorpal.prompt = function () {
var _this = this;
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var userCallback = arguments[1];
return new Promise(function (resolve) {
// Setup callback to also resolve promise.
var cb = function cb(response) {
// Does not currently handle Inquirer validation errors.
resolve(response);
if (userCallback) {
userCallback(response);
}
};
var prompt = void 0;
var ssn = _this.getSessionById(options.sessionId);
if (!ssn) {
throw new Error('Vorpal.prompt was called without a passed Session ID.');
}
var handler = function handler(data) {
var response = data.value;
_this.removeListener('vantage-prompt-upstream', handler);
cb(response);
};
if (ssn.isLocal()) {
ui.setDelimiter(options.message || ssn.delimiter());
prompt = ui.prompt(options, function (result) {
ui.setDelimiter(ssn.delimiter());
cb(result);
});
} else {
_this.on('vantage-prompt-upstream', handler);
_this._send('vantage-prompt-downstream', 'downstream', { options: options, value: undefined, sessionId: ssn.id });
}
return prompt;
});
};
/**
* Renders the CLI prompt or sends the
* request to do so downstream.
*
* @param {Object} data
* @return {Vorpal}
* @api private
*/
vorpal._prompt = function (data) {
var self = this;
var prompt;
data = data || {};
if (!data.sessionId) {
data.sessionId = self.session.id;
}
var ssn = self.getSessionById(data.sessionId);
// If we somehow got to _prompt and aren't the
// local client, send the command downstream.
if (!ssn.isLocal()) {
this._send('vantage-resume-downstream', 'downstream', { sessionId: data.sessionId });
return self;
}
if (ui.midPrompt()) {
return self;
}
prompt = ui.prompt({
type: 'input',
name: 'command',
message: ssn.fullDelimiter()
}, function (result) {
if (self.ui._cancelled === true) {
self.ui._cancelled = false;return;
}
var str = String(result.command).trim();
self.emit('client_prompt_submit', str);
if (str === '' || str === 'undefined') {
self._prompt(data);return;
}
self.exec(str, function () {
self._prompt(data);
});
});
return prompt;
};
/**
* Executes a vorpal API command and
* returns the response either through a
* callback or Promise in the absence
* of a callback.
*
* A little black magic here - because
* we sometimes have to send commands 10
* miles upstream through 80 other instances
* of vorpal and we aren't going to send
* the callback / promise with us on that
* trip, we store the command, callback,
* resolve and reject objects (as they apply)
* in a local vorpal._command variable.
*
* When the command eventually comes back
* downstream, we dig up the callbacks and
* finally resolve or reject the promise, etc.
*
* Lastly, to add some more complexity, we throw
* command and callbacks into a queue that will
* be unearthed and sent in due time.
*
* @param {String} cmd
* @param {Function} cb
* @return {Promise or Vorpal}
* @api public
*/
vorpal.exec = function (cmd, args, cb) {
var self = this;
var ssn = self.session;
cb = _.isFunction(args) ? args : cb;
args = args || {};
if (args.sessionId) {
ssn = self.getSessionById(args.sessionId);
}
var command = {
command: cmd,
args: args,
callback: cb,
session: ssn
};
if (cb !== undefined) {
self._queue.push(command);
self._queueHandler();
return self;
}
return new Promise(function (resolve, reject) {
command.resolve = resolve;
command.reject = reject;
self._queue.push(command);
self._queueHandler();
});
};
/**
* Executes a Vorpal command in sync.
*
* @param {String} cmd
* @param {Object} args
* @return {*} stdout
* @api public
*/
vorpal.execSync = function (cmd, options) {
var self = this;
var ssn = self.session;
options = options || {};
if (options.sessionId) {
ssn = self.getSessionById(options.sessionId);
}
var command = {
command: cmd,
args: options,
session: ssn,
sync: true,
options: options
};
return self._execQueueItem(command);
};
/**
* Commands issued to Vorpal server
* are executed in sequence. Called once
* when a command is inserted or completes,
* shifts the next command in the queue
* and sends it to `vorpal._execQueueItem`.
*
* @api private
*/
vorpal._queueHandler = function () {
if (this._queue.length > 0 && this._command === undefined) {
var item = this._queue.shift();
this._execQueueItem(item);
}
};
/**
* Fires off execution of a command - either
* calling upstream or executing locally.
*
* @param {Object} cmd
* @api private
*/
vorpal._execQueueItem = function (cmd) {
var self = this;
self._command = cmd;
if (cmd.session.isLocal() && !cmd.session.client) {
return this._exec(cmd);
}
self._send('vantage-command-upstream', 'upstream', {
command: cmd.command,
args: cmd.args,
completed: false,
sessionId: cmd.session.id
});
};
/**
* Executes a vorpal API command.
* Warning: Dragons lie beyond this point.
*
* @param {String} item
* @api private
*/
vorpal._exec = function (item) {
var self = this;
item = item || {};
item.command = item.command || '';
var modeCommand = item.command;
item.command = item.session._mode ? item.session._mode : item.command;
var promptCancelled = false;
if (this.ui._midPrompt) {
promptCancelled = true;
this.ui.cancel();
}
if (!item.session) {
throw new Error('Fatal Error: No session was passed into command for execution: ' + item);
}
if (String(item.command).indexOf('undefine') > -1) {
throw new Error('vorpal._exec was called with an undefined command.');
}
// History for our 'up' and 'down' arrows.
item.session.history(item.session._mode ? modeCommand : item.command);
var commandData = this.util.parseCommand(item.command, this.commands);
item.command = commandData.command;
item.pipes = commandData.pipes;
var match = commandData.match;
var matchArgs = commandData.matchArgs;
function throwHelp(cmd, msg, alternativeMatch) {
if (msg) {
cmd.session.log(msg);
}
var pickedMatch = alternativeMatch || match;
cmd.session.log(pickedMatch.helpInformation());
}
function callback(cmd, err, msg, argus) {
// Resume the prompt if we had to cancel
// an active prompt, due to programmatic
// execution.
if (promptCancelled) {
self._prompt();
}
if (cmd.sync) {
// If we want the command to be fatal,
// throw a real error. Otherwise, silently
// return the error.
delete self._command;
if (err) {
if (cmd.options && (cmd.options.fatal === true || vorpal._fatal === true)) {
throw new Error(err);
}
return err;
}
return msg;
} else if (cmd.callback) {
if (argus) {
cmd.callback.apply(self, argus);
} else {
cmd.callback.call(self, err, msg);
}
} else if (!err && cmd.resolve) {
cmd.resolve(msg);
} else if (err && cmd.reject) {
cmd.reject(msg);
}
delete self._command;
self._queueHandler();
}
if (match) {
item.fn = match._fn;
item._cancel = match._cancel;
item.validate = match._validate;
item.commandObject = match;
var init = match._init || function (arrgs, cb) {
cb();
};
var delimiter = match._delimiter || String(item.command).toLowerCase() + ':';
item.args = self.util.buildCommandArgs(matchArgs, match, item, self.isCommandArgKeyPairNormalized);
// If we get a string back, it's a validation error.
// Show help and return.
if (_.isString(item.args) || !_.isObject(item.args)) {
throwHelp(item, item.args);
return callback(item, undefined, item.args);
}
// Build the piped commands.
var allValid = true;
for (var j = 0; j < item.pipes.length; ++j) {
var commandParts = self.util.matchCommand(item.pipes[j], self.commands);
if (!commandParts.command) {
item.session.log(self._commandHelp(item.pipes[j]));
allValid = false;
break;
}
commandParts.args = self.util.buildCommandArgs(commandParts.args, commandParts.command);
if (_.isString(commandParts.args) || !_.isObject(commandParts.args)) {
throwHelp(item, commandParts.args, commandParts.command);
allValid = false;
break;
}
item.pipes[j] = commandParts;
}
// If invalid piped commands, return.
if (!allValid) {
return callback(item);
}
// If `--help` or `/?` is passed, do help.
if (item.args.options.help && _.isFunction(match._help)) {
// If the command has a custom help function, run it
// as the actual "command". In this way it can go through
// the whole cycle and expect a callback.
item.fn = match._help;
delete item.validate;
delete item._cancel;
} else if (item.args.options.help) {
// Otherwise, throw the standard help.
throwHelp(item, '');
return callback(item);
}
// If this command throws us into a 'mode',
// prepare for it.
if (match._mode === true && !item.session._mode) {
// Assign vorpal to be in a 'mode'.
item.session._mode = item.command;
// Execute the mode's `init` function
// instead of the `action` function.
item.fn = init;
delete item.validate;
self.cmdHistory.enterMode();
item.session.modeDelimiter(delimiter);
} else if (item.session._mode) {
if (String(modeCommand).trim() === 'exit') {
self._exitMode({ sessionId: item.session.id });
return callback(item);
}
// This executes when actually in a 'mode'
// session. We now pass in the raw text of what
// is typed into the first param of `action`
// instead of arguments.
item.args = modeCommand;
}
if (item.sync === true) {
// If we're running synchronous commands,
// we don't support piping.
var response;
var error;
try {
response = item.fn.call(new CommandInstance({
downstream: undefined,
commandWrapper: item,
commandObject: item.commandObject,
args: item.args
}), item.args);
} catch (e) {
error = e;
}
return callback(item, error, response);
}
// Builds commandInstance objects for every
// command and piped command included in the
// execution string.
// Build the instances for each pipe.
item.pipes = item.pipes.map(function (pipe) {
return new CommandInstance({
commandWrapper: item,
command: pipe.command._name,
commandObject: pipe.command,
args: pipe.args
});
});
// Reverse through the pipes and assign the
// `downstream` object of each parent to its
// child command.
for (var k = item.pipes.length - 1; k > -1; --k) {
var downstream = item.pipes[k + 1];
item.pipes[k].downstream = downstream;
}
item.session.execCommandSet(item, function (wrapper, err, data, argus) {
callback(wrapper, err, data, argus);
});
} else {
// If no command match, just return.
item.session.log(this._commandHelp(item.command));
return callback(item, undefined, 'Invalid command.');
}
};
/**
* Exits out of a give 'mode' one is in.
* Reverts history and delimiter back to
* regular vorpal usage.
*
* @api private
*/
vorpal._exitMode = function (options) {
var ssn = this.getSessionById(options.sessionId);
ssn._mode = false;
this.cmdHistory.exitMode();
ssn.modeDelimiter(false);
this.emit('mode_exit', this.cmdHistory.peek());
};
/**
* Registers a custom handler for SIGINT.
* Vorpal exits with 0 by default
* on a sigint.
*
* @param {Function} fn
* @return {Vorpal}
* @api public
*/
vorpal.sigint = function (fn) {
if (_.isFunction(fn)) {
ui.sigint(fn);
} else {
throw new Error('vorpal.sigint must be passed in a valid function.');
}
return this;
};
/**
* Returns the instance of given command.
*
* @param {String} cmd
* @return {Command}
* @api public
*/
vorpal.find = function (name) {
return _.find(this.commands, { _name: name });
};
/**
* Registers custom help.
*
* @param {Function} fn
* @return {Vorpal}
* @api public
*/
vorpal.help = function (fn) {
this._help = fn;
};
/**
* Returns help string for a given command.
*
* @param {String} command
* @api private
*/
vorpal._commandHelp = function (command) {
if (!this.commands.length) {
return '';
}
if (this._help !== undefined && _.isFunction(this._help)) {
return this._help(command);
}
var matches = [];
var singleMatches = [];
command = command ? String(command).trim().toLowerCase() : undefined;
for (var i = 0; i < this.commands.length; ++i) {
var parts = String(this.commands[i]._name).split(' ');
if (parts.length === 1 && parts[0] === command && !this.commands[i]._hidden && !this.commands[i]._catch) {
singleMatches.push(command);
}
var str = '';
for (var j = 0; j < parts.length; ++j) {
str = String(str + ' ' + parts[j]).trim();
if (str === command && !this.commands[i]._hidden && !this.commands[i]._catch) {
matches.push(this.commands[i]);
break;
}
}
}
var invalidString = command && matches.length === 0 && singleMatches.length === 0 ? ['', ' Invalid Command. Showing Help:', ''].join('\n') : '';
var commandMatch = matches.length > 0;
var commandMatchLength = commandMatch ? String(command).trim().split(' ').length + 1 : 1;
matches = matches.length === 0 ? this.commands : matches;
var skipGroups = !(matches.length + 6 > process.stdout.rows);
var commands = matches.filter(function (cmd) {
return !cmd._noHelp;
}).filter(function (cmd) {
return !cmd._catch;
}).filter(function (cmd) {
return !cmd._hidden;
}).filter(function (cmd) {
if (skipGroups === true) {
return true;
}
return String(cmd._name).trim().split(' ').length <= commandMatchLength;
}).map(function (cmd) {
var args = cmd._args.map(function (arg) {
return VorpalUtil.humanReadableArgName(arg);
}).join(' ');
return [cmd._name + (cmd._alias ? '|' + cmd._alias : '') + (cmd.options.length ? ' [options]' : '') + ' ' + args, cmd.description() || ''];
});
var width = commands.reduce(function (max, commandX) {
return Math.max(max, commandX[0].length);
}, 0);
var counts = {};
var groups = _.uniq(matches.filter(function (cmd) {
return String(cmd._name).trim().split(' ').length > commandMatchLength;
}).map(function (cmd) {
return String(cmd._name).split(' ').slice(0, commandMatchLength).join(' ');
}).map(function (cmd) {
counts[cmd] = counts[cmd] || 0;
counts[cmd]++;
return cmd;
})).map(function (cmd) {
var prefix = ' ' + VorpalUtil.pad(cmd + ' *', width) + ' ' + counts[cmd] + ' sub-command' + (counts[cmd] === 1 ? '' : 's') + '.';
return prefix;
});
groups = skipGroups ? [] : groups;
var descriptionWidth = process.stdout.columns - (width + 4);
commands = _.orderBy(commands, x => x[0]);
var commandsString = commands.length < 1 ? '' : '\n Commands:\n\n' + commands.map(function (cmd) {
var prefix = ' ' + VorpalUtil.pad(cmd[0], width) + ' ';
var suffix = wrap(cmd[1], descriptionWidth - 8).split('\n');
for (var _i = 0; _i < suffix.length; ++_i) {
if (_i !== 0) {
suffix[_i] = VorpalUtil.pad('', width + 6) + suffix[_i];
}
}
suffix = suffix.join('\n');
return prefix + suffix;
}).join('\n') + '\n\n';
var groupsString = groups.length < 1 ? '' : ' Command Groups:\n\n' + groups.join('\n') + '\n';
var results = String(this._helpHeader(!!invalidString) + invalidString + commandsString + '\n' + groupsString).replace(/\n\n\n/g, '\n\n').replace(/\n\n$/, '\n');
return results;
};
vorpal._helpHeader = function (hideTitle) {
var header = [];
if (this._banner) {
header.push(VorpalUtil.padRow(this._banner), '');
}
// Only show under specific conditions
if (this._title && !hideTitle) {
var title = this._title;
if (this._version) {
title += ' v' + this._version;
}
header.push(VorpalUtil.padRow(title));
if (this._description) {
var descWidth = process.stdout.columns * 0.75; // Only 75% of the screen
header.push(VorpalUtil.padRow(wrap(this._description, descWidth)));
}
}
// Pad the top and bottom
if (header.length) {
header.unshift('');
header.push('');
}
return header.join('\n');
};
/**
* Abstracts the logic for sending and
* receiving sockets upstream and downstream.
*
* To do: Has the start of logic for vorpal sessions,
* which I haven't fully confronted yet.
*
* @param {String} str
* @param {String} direction
* @param {String} data
* @param {Object} options
* @api private
*/
vorpal._send = function (str, direction, data, options) {
options = options || {};
data = data || {};
var ssn = this.getSessionById(data.sessionId);
if (!ssn) {
throw new Error('No Sessions logged for ID ' + data.sessionId + ' in vorpal._send.');
}
if (direction === 'upstream') {
if (ssn.client) {
ssn.client.emit(str, data);
}
} else if (direction === 'downstream') {
if (ssn.server) {
ssn.server.emit(str, data);
}
}
};
/**
* Handles the 'middleman' in a 3+-way vagrant session.
* If a vagrant instance is a 'client' and 'server', it is
* now considered a 'proxy' and its sole purpose is to proxy
* information through, upstream or downstream.
*
* If vorpal is not a proxy, it resolves a promise for further
* code that assumes one is now an end user. If it ends up
* piping the traffic through, it never resolves the promise.
*
* @param {String} str
* @param {String} direction
* @param {String} data
* @param {Object} options
* @api private
*/
vorpal._proxy = function (str, direction, data, options) {
var self = this;
return new Promise(function (resolve) {
var ssn = self.getSessionById(data.sessionId);
if (ssn && !ssn.isLocal() && ssn.client) {
self._send(str, direction, data, options);
} else {
resolve();
}
});
};
/**
* Returns session by id.
*
* @param {Integer} id
* @return {Session}
* @api public
*/
vorpal.getSessionById = function (id) {
if (_.isObject(id)) {
throw new Error('vorpal.getSessionById: id ' + JSON.stringify(id) + ' should not be an object.');
}
var ssn = _.find(this.server.sessions, { id: id });
ssn = this.session.id === id ? this.session : ssn;
if (!id) {
throw new Error('vorpal.getSessionById was called with no ID passed.');
}
if (!ssn) {
var sessions = {
local: this.session.id,
server: _.map(this.server.sessions, 'id')
};
throw new Error('No session found for id ' + id + ' in vorpal.getSessionById. Sessions: ' + JSON.stringify(sessions));
}
return ssn;
};
/**
* Kills a remote vorpal session. If user
* is running on a direct terminal, will kill
* node instance after confirmation.
*
* @param {Object} options
* @param {Function} cb
* @api private
*/
vorpal.exit = function (options) {
var ssn = this.getSessionById(options.sessionId);
this.emit('vorpal_exit');
if (ssn.isLocal()) {
process.exit(0);
} else {
ssn.server.emit('vantage-close-downstream', { sessionId: ssn.id });
}
};
Object.defineProperty(vorpal, 'activeCommand', {
get: function get() {
var result = this._command ? this._command.commandInstance : undefined;
return result;
}
});