appcenter-cli
Version:
Command line tool for Visual Studio App Center
172 lines (171 loc) • 6.92 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.setupAutoCompleteForShell = exports.executeAutoComplete = void 0;
const Path = require("path");
const Fs = require("fs");
const misc_1 = require("../misc");
const omelette = require("omelette");
const appName = misc_1.scriptName;
function getAutoCompleteObject() {
return omelette(appName);
}
function executeAutoComplete() {
const autoCompleteObject = getAutoCompleteObject();
autoCompleteObject.on("complete", function (fragment, data) {
const line = data.line;
const reply = data.reply;
const argsLine = line.substring(appName.length);
const args = argsLine.match(/\S+/g) || [];
const lineEndsWithWhitespaceChar = /\s{1}/.test(last(line));
const autocompleteTree = JSON.parse(Fs.readFileSync(Path.join(__dirname, "..", "..", "autocomplete-tree.json"), "utf8"));
const expandedAutoCompleteTree = getAutoCompleteTreeWithExpandedHelp(autocompleteTree);
const getReply = getReplyHandler(lineEndsWithWhitespaceChar);
reply(getReply(args, expandedAutoCompleteTree));
});
autoCompleteObject.init();
}
exports.executeAutoComplete = executeAutoComplete;
function setupAutoCompleteForShell(path, shell) {
const autoCompleteObject = getAutoCompleteObject();
let initFile = path;
if (shell) {
autoCompleteObject.shell = shell;
}
else {
autoCompleteObject.shell = autoCompleteObject.getActiveShell();
}
if (!initFile) {
initFile = autoCompleteObject.getDefaultShellInitFile();
}
let initFileContent;
try {
initFileContent = Fs.readFileSync(initFile, { encoding: "utf-8" });
}
catch (exception) {
throw `Can't read init file (${initFile}): ${exception}`;
}
try {
// For bash we need to enable bash_completion before appcenter cli completion
if (autoCompleteObject.shell === "bash" && initFileContent.indexOf("begin bash_completion configuration") === -1) {
const sources = `[ -f /usr/local/etc/bash_completion ] && . /usr/local/etc/bash_completion
[ -f /usr/share/bash-completion/bash_completion ] && . /usr/share/bash-completion/bash_completion
[ -f /etc/bash_completion ] && . /etc/bash_completion`;
const template = `
# begin bash_completion configuration for ${appName} completion
${sources}
# end bash_completion configuration for ${appName} completion
`;
Fs.appendFileSync(initFile, template);
}
if (initFileContent.indexOf(`begin ${appName} completion`) === -1) {
autoCompleteObject.setupShellInitFile(initFile);
}
}
catch (exception) {
throw `Can't setup autocomplete. Please make sure that init file (${initFile}) exist and you have write permissions: ${exception}`;
}
}
exports.setupAutoCompleteForShell = setupAutoCompleteForShell;
function getReplyHandler(lineEndsWithWhitespaceChar) {
return function getReply(args, autocompleteTree) {
const currentArg = head(args);
const commandsAndCategories = Object.keys(autocompleteTree);
if (currentArg === undefined) {
// no more args - show all of the items at the current level
return commandsAndCategories;
}
else {
// check what arg points to
const entity = autocompleteTree[currentArg];
if (entity) {
// arg points to an existing command or category
const restOfArgs = tail(args);
if (restOfArgs.length || lineEndsWithWhitespaceChar) {
if (entity instanceof Array) {
// it is command
const getCommandReply = getCommandReplyHandler(lineEndsWithWhitespaceChar);
return getCommandReply(restOfArgs, entity);
}
else {
// it is category
return getReply(restOfArgs, entity);
}
}
else {
// if last arg has no trailing whitespace, it should be added
return [currentArg];
}
}
else {
// arg points to nothing specific - return commands and categories which start with arg
return commandsAndCategories.filter((commandOrCategory) => commandOrCategory.startsWith(currentArg));
}
}
};
}
function getCommandReplyHandler(lineEndsWithWhitespaceChar) {
return function getCommandReply(args, optionsNames) {
const currentArg = head(args);
if (currentArg === undefined) {
// no more args, returning remaining optionsNames
return optionsNames.map((option) => option.long || option.short);
}
else {
const restOfArgs = tail(args);
if (restOfArgs.length || lineEndsWithWhitespaceChar) {
const filteredOptions = optionsNames.filter((option) => option.long !== currentArg && option.short !== currentArg);
return getCommandReply(restOfArgs, filteredOptions);
}
else {
const candidates = [];
for (const option of optionsNames) {
if (option.long && option.long.startsWith(currentArg)) {
candidates.push(option.long);
}
else if (option.short && option.short.startsWith(currentArg)) {
candidates.push(option.short);
}
}
return candidates;
}
}
};
}
function getAutoCompleteTreeWithExpandedHelp(originalTree) {
// "help" command prefixes command path to show help for it
const helpTree = cloneDeep(originalTree, (entry) => (entry instanceof Array ? cloneDeep(originalTree["help"]) : undefined));
delete helpTree["help"];
const expandedTree = cloneDeep(originalTree);
expandedTree["help"] = helpTree;
return expandedTree;
}
// utility functions (to avoid loading lodash for performance reasons)
function last(line) {
return line.substr(-1, 1);
}
function head(array) {
return array[0];
}
function tail(array) {
return array.slice(1);
}
function cloneDeep(...args) {
const item = args[0];
const handler = args[1];
const handlerResult = handler && handler(item);
if (handlerResult !== undefined) {
return handlerResult;
}
if (item instanceof Array) {
return item.map((subItem) => cloneDeep(subItem, handler));
}
if (item instanceof Object) {
const cloneObject = {};
const keys = Object.keys(item);
for (const key of keys) {
cloneObject[key] = cloneDeep(item[key], handler);
}
return cloneObject;
}
return item;
}