You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
456 lines
19 KiB
456 lines
19 KiB
/**@license
|
|
* __ _____ ________ __
|
|
* / // _ /__ __ _____ ___ __ _/__ ___/__ ___ ______ __ __ __ ___ / /
|
|
* __ / // // // // // _ // _// // / / // _ // _// // // \/ // _ \/ /
|
|
* / / // // // // // ___// / / // / / // ___// / / / / // // /\ // // / /__
|
|
* \___//____ \\___//____//_/ _\_ / /_//____//_/ /_/ /_//_//_/ /_/ \__\_\___/
|
|
* \/ /____/
|
|
* http://terminal.jcubic.pl
|
|
*
|
|
* This is object enchancment that will add pipe operator and redirects to commands
|
|
*
|
|
* Copyright (c) 2014-2021 Jakub Jankiewicz <https://jcubic.pl/me>
|
|
* Released under the MIT license
|
|
*
|
|
*/
|
|
/* global define */
|
|
(function(factory) {
|
|
var root = typeof window !== 'undefined' ? window : global;
|
|
if (typeof define === 'function' && define.amd) {
|
|
// AMD. Register as an anonymous module.
|
|
// istanbul ignore next
|
|
define(['jquery', 'jquery.terminal'], factory);
|
|
} else if (typeof module === 'object' && module.exports) {
|
|
// Node/CommonJS
|
|
module.exports = function(root, jQuery) {
|
|
if (jQuery === undefined) {
|
|
// require('jQuery') returns a factory that requires window to
|
|
// build a jQuery instance, we normalize how we use modules
|
|
// that require this pattern but the window provided is a noop
|
|
// if it's defined (how jquery works)
|
|
if (typeof window !== 'undefined') {
|
|
jQuery = require('jquery');
|
|
} else {
|
|
jQuery = require('jquery')(root);
|
|
}
|
|
}
|
|
if (!jQuery.fn.terminal) {
|
|
if (typeof window !== 'undefined') {
|
|
require('jquery.terminal');
|
|
} else {
|
|
require('jquery.terminal')(jQuery);
|
|
}
|
|
}
|
|
factory(jQuery);
|
|
return jQuery;
|
|
};
|
|
} else {
|
|
// Browser
|
|
// istanbul ignore next
|
|
factory(root.jQuery);
|
|
}
|
|
})(function($) {
|
|
// -----------------------------------------------------------------------------------
|
|
// :: split over array - returns array of arrays
|
|
// -----------------------------------------------------------------------------------
|
|
function array_split(array, splitter) {
|
|
var test_fn;
|
|
if (typeof splitter === 'function') {
|
|
test_fn = splitter;
|
|
} else if (splitter instanceof RegExp) {
|
|
test_fn = function(item) {
|
|
if (item instanceof RegExp) {
|
|
item = item.source;
|
|
} else if (typeof item !== 'string') {
|
|
item = item.toString();
|
|
}
|
|
var m = item.match(splitter);
|
|
if (m) {
|
|
if (m.length > 1) {
|
|
return m.slice(1);
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
} else {
|
|
test_fn = function(item) {
|
|
return splitter === item;
|
|
};
|
|
}
|
|
var output = [];
|
|
var sub = [];
|
|
array.forEach(function(item, i) {
|
|
var check = test_fn(item, i);
|
|
if (check) {
|
|
output.push(sub);
|
|
sub = [];
|
|
if (check instanceof Array) {
|
|
output.push(check);
|
|
}
|
|
} else {
|
|
sub.push(item);
|
|
}
|
|
});
|
|
output.push(sub);
|
|
return output;
|
|
}
|
|
// -----------------------------------------------------------------------------------
|
|
function is_function(obj) {
|
|
return typeof obj === 'function';
|
|
}
|
|
// -----------------------------------------------------------------------------------
|
|
$.terminal.defaults.strings.pipeNestedInterpreterError = 'You can\'t pipe nested ' +
|
|
'interpreter';
|
|
// -----------------------------------------------------------------------------------
|
|
$.terminal.pipe = function pipe(interpreter, options) {
|
|
var settings = $.extend({
|
|
processArguments: true,
|
|
overwrite: undefined,
|
|
redirects: {}
|
|
}, options);
|
|
var overwrite_buffer;
|
|
var term;
|
|
var orig;
|
|
var command_index;
|
|
var process_redirect = false;
|
|
// -------------------------------------------------------------------------------
|
|
function parse_redirect(args, re) {
|
|
var redirects = [];
|
|
if (args.length) {
|
|
var cmd_redirect, settings_redirect;
|
|
for (var i = 0; i < args.length; ++i) {
|
|
var operator = null;
|
|
if (args[i].length === 1 && args[i][0].match(re)) {
|
|
operator = args[i][0];
|
|
}
|
|
if (operator) {
|
|
for (var j = settings.redirects.length; j--;) {
|
|
if (operator === settings.redirects[j].name) {
|
|
settings_redirect = settings.redirects[j];
|
|
break;
|
|
}
|
|
}
|
|
cmd_redirect = {
|
|
fn: settings_redirect.callback,
|
|
args: []
|
|
};
|
|
redirects.push(cmd_redirect);
|
|
} else {
|
|
cmd_redirect.args = args[i];
|
|
}
|
|
}
|
|
}
|
|
return redirects;
|
|
}
|
|
// -------------------------------------------------------------------------------
|
|
function is_pipe(cmd) {
|
|
var quotes = [''].concat(cmd.args_quotes);
|
|
return function(string, i) {
|
|
return string === '|' && !quotes[i];
|
|
};
|
|
}
|
|
// -------------------------------------------------------------------------------
|
|
function tokens(cmd) {
|
|
return [cmd.name].concat(cmd.args);
|
|
}
|
|
// -------------------------------------------------------------------------------
|
|
function parse_command(command) {
|
|
var cmd;
|
|
if (term.settings().processArguments) {
|
|
cmd = $.terminal.parse_command(command);
|
|
} else {
|
|
cmd = $.terminal.split_command(command);
|
|
}
|
|
var index = 1;
|
|
return array_split(tokens(cmd), is_pipe(cmd)).map(function(args, i) {
|
|
var result = {
|
|
redirects: []
|
|
};
|
|
var len = args.length;
|
|
if (settings.redirects.length) {
|
|
var redirect_names = settings.redirects.map(function(redirect) {
|
|
return $.terminal.escape_regex(redirect.name);
|
|
});
|
|
var re = new RegExp('^(' + redirect_names.join('|') + ')$');
|
|
var split_args = array_split(args, re);
|
|
if (split_args.length > 1) {
|
|
args = split_args[0];
|
|
result.redirects = parse_redirect(split_args.slice(1), re);
|
|
}
|
|
}
|
|
if (i === 0) {
|
|
result.args_quotes = args.map(function(_, i) {
|
|
return cmd.args_quotes[i];
|
|
});
|
|
} else {
|
|
result.args_quotes = args.map(function(_, i) {
|
|
return cmd.args_quotes[i + index];
|
|
}).slice(1);
|
|
}
|
|
index += len;
|
|
result.name = args[0];
|
|
result.args = args.slice(1);
|
|
return result;
|
|
});
|
|
}
|
|
// -------------------------------------------------------------------------------
|
|
function overwrite(command) {
|
|
return command.overwrite === true ||
|
|
typeof command.overwrite === 'undefined' ||
|
|
settings.overwrite === true;
|
|
}
|
|
// -------------------------------------------------------------------------------
|
|
function redirects(command) {
|
|
var defer = $.Deferred();
|
|
if (overwrite(command)) {
|
|
overwrite_buffer = true;
|
|
}
|
|
function resolve() {
|
|
overwrite_buffer = false;
|
|
defer.resolve();
|
|
}
|
|
if (command.redirects.length) {
|
|
var i = 0;
|
|
(function loop() {
|
|
var redirect = command.redirects[i++];
|
|
if (redirect) {
|
|
var fn = redirect.fn;
|
|
var args = redirect.args;
|
|
continuation(fn.apply(term, args), function(value) {
|
|
echo_non_empty(value);
|
|
loop();
|
|
}, 'redirect loop');
|
|
} else {
|
|
resolve();
|
|
}
|
|
})();
|
|
} else {
|
|
resolve();
|
|
}
|
|
return defer.promise();
|
|
}
|
|
// -------------------------------------------------------------------------------
|
|
function continuation(promise, callback) {
|
|
if (promise && promise.then) {
|
|
promise.then(callback);
|
|
return promise;
|
|
} else {
|
|
callback();
|
|
}
|
|
}
|
|
// -------------------------------------------------------------------------------
|
|
function echo_non_empty(value) {
|
|
if (typeof value !== 'undefined') {
|
|
term.echo(value);
|
|
}
|
|
}
|
|
// -------------------------------------------------------------------------------
|
|
function single_command(commands) {
|
|
return commands.length === 1 && !commands[0].redirects.length;
|
|
}
|
|
// -------------------------------------------------------------------------------
|
|
function make_tty() {
|
|
var tty = {
|
|
read: function(message, callback) {
|
|
// in case we return read() call from interpreter
|
|
// we can't access force_awake flag in term (it's invoked later)
|
|
if (term.paused()) {
|
|
term.resume();
|
|
}
|
|
if ((command_index === 0 && !tty.buffer) || process_redirect) {
|
|
term.push = orig.push;
|
|
term.echo = orig.echo;
|
|
var ret = orig.read.apply(term, arguments);
|
|
return ret.then(function(value) {
|
|
term.push = tty.push;
|
|
term.echo = tty.echo;
|
|
return value;
|
|
});
|
|
} else {
|
|
var text;
|
|
if (tty.buffer) {
|
|
text = tty.buffer.replace(/\n$/, '');
|
|
delete tty.buffer;
|
|
}
|
|
var d = new $.Deferred();
|
|
if (is_function(callback)) {
|
|
callback(text);
|
|
}
|
|
d.resolve(text);
|
|
return d.promise();
|
|
}
|
|
},
|
|
echo: function(string) {
|
|
if (overwrite_buffer) {
|
|
overwrite_buffer = false;
|
|
tty.buffer = '';
|
|
}
|
|
tty.buffer = (tty.buffer || '') + string + '\n';
|
|
},
|
|
push: function() {
|
|
this.error(strings(this).pipeNestedInterpreterError);
|
|
}
|
|
};
|
|
return tty;
|
|
}
|
|
// -------------------------------------------------------------------------------
|
|
function strings(term) {
|
|
var term_settings = term.settings();
|
|
return $.extend(
|
|
{},
|
|
$.terminal.defaults.strings,
|
|
term_settings && term_settings.strings || {}
|
|
);
|
|
}
|
|
// -------------------------------------------------------------------------------
|
|
function error(message) {
|
|
var echo = term.echo;
|
|
term.echo = orig.echo;
|
|
term.error(message);
|
|
term.echo = echo;
|
|
}
|
|
// -------------------------------------------------------------------------------
|
|
function unparse(string) {
|
|
if (term.settings().processArguments) {
|
|
return string.replace(/(['"()])/g, '\\$1');
|
|
} else {
|
|
return string.replace(/ /g, '\\ ');
|
|
}
|
|
}
|
|
// -------------------------------------------------------------------------------
|
|
function stringify(cmd) {
|
|
return cmd.name + ' ' + cmd.args.map(function(arg, i) {
|
|
if (cmd.args_quotes[i]) {
|
|
var quote = cmd.args_quotes[i];
|
|
return quote + unparse(arg) + quote;
|
|
}
|
|
return arg.replace(/ /g, '\\ ');
|
|
}).join(' ');
|
|
}
|
|
// -------------------------------------------------------------------------------
|
|
return function(command, t) {
|
|
command_index = -1;
|
|
term = t; // for echo_non_empty and function that call it
|
|
var term_settings = term.settings();
|
|
if (!orig) {
|
|
orig = {
|
|
echo: term.echo,
|
|
read: term.read,
|
|
push: term.push
|
|
};
|
|
}
|
|
var tty = make_tty();
|
|
var commands = parse_command(command);
|
|
function loop(callback) {
|
|
var i = 0;
|
|
var d = new $.Deferred();
|
|
return (function inner() {
|
|
var cmd = commands[i++];
|
|
if (cmd) {
|
|
process_redirect = true;
|
|
redirects(cmd).then(function() {
|
|
process_redirect = false;
|
|
if (!commands[i]) {
|
|
$.extend(term, {echo: orig.echo, push: orig.push});
|
|
}
|
|
command_index++;
|
|
var ret = callback(cmd);
|
|
if (ret === false) {
|
|
return inner();
|
|
} else {
|
|
return continuation(ret, inner, 'inner');
|
|
}
|
|
});
|
|
} else {
|
|
d.resolve();
|
|
}
|
|
return d.promise();
|
|
})();
|
|
}
|
|
if (single_command(commands)) {
|
|
var cmd = commands[0];
|
|
if (is_function(interpreter)) {
|
|
return interpreter.call(term, stringify(cmd), term);
|
|
} else if (is_function(interpreter[cmd.name])) {
|
|
return interpreter[cmd.name].apply(term, cmd.args);
|
|
} else if ($.isPlainObject(interpreter[cmd.name])) {
|
|
term.push(interpreter[cmd.name], {
|
|
prompt: cmd.name + '> ',
|
|
name: cmd.name,
|
|
completion: Object.keys(interpreter[cmd.name])
|
|
});
|
|
} else if (is_function(term_settings.onCommandNotFound)) {
|
|
term_settings.onCommandNotFound.call(term, command, term);
|
|
} else {
|
|
error(sprintf(strings(term).commandNotFound, cmd.name));
|
|
}
|
|
} else {
|
|
$.extend(term, tty);
|
|
var stop_error = false;
|
|
var promise = loop(function(cmd) {
|
|
var ret;
|
|
if (is_function(interpreter)) {
|
|
// this branch will be always invoked with new API
|
|
// using pipe option
|
|
var str = stringify(cmd);
|
|
// this resume and exec is needed for rpc
|
|
// because it use pause/resume and not promises
|
|
// TODO: refactor RPC in terminal
|
|
term.resume();
|
|
ret = term.exec(str, true);
|
|
return continuation(ret, echo_non_empty);
|
|
} else {
|
|
var inter = interpreter[cmd.name];
|
|
if (is_function(inter)) {
|
|
ret = inter.apply(term, cmd.args);
|
|
return continuation(ret, echo_non_empty);
|
|
} else {
|
|
if ($.isPlainObject(inter)) {
|
|
error(strings(term).pipeNestedInterpreterError);
|
|
} else if (is_function(term_settings.onCommandNotFound)) {
|
|
if (!stop_error) {
|
|
term_settings.onCommandNotFound.call(
|
|
term,
|
|
command,
|
|
term
|
|
);
|
|
stop_error = true;
|
|
}
|
|
} else {
|
|
error(sprintf(strings(term).commandNotFound, cmd.name));
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
});
|
|
return promise.then(function() {
|
|
$.extend(term, orig);
|
|
});
|
|
}
|
|
};
|
|
};
|
|
// -------------------------------------------------------------------------
|
|
var init = $.fn.terminal;
|
|
$.fn.terminal = function(interpreter, options) {
|
|
if (options && options.pipe) {
|
|
var settings = $.extend({}, options, {
|
|
onInit: function() {
|
|
var defer = $.Deferred();
|
|
if (options && options.pipe) {
|
|
var interpreter = $.terminal.pipe(this.commands(), options);
|
|
this.set_interpreter(interpreter).then(defer.resolve);
|
|
} else {
|
|
defer.resolve();
|
|
}
|
|
if (options && is_function(options.onInit)) {
|
|
var self = this;
|
|
defer.then(function() {
|
|
options.onInit.call(self, self);
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
return init.call(this, interpreter, settings || options);
|
|
};
|
|
});
|
|
|