@haystacks/async
Version:
A framework to build any number or any kind of native application or automation solution.
792 lines (774 loc) • 52.4 kB
JavaScript
/**
* @file commandBroker.js
* @module commandBroker
* @description Executes commands by calling the appropriate command-function from the commandLibrary,
* which will actually be stored functions on the D-Data structure.
* @requires module:ruleBroker
* @requires module:commandsLibrary
* @requires module:colorizer
* @requires module:configurator
* @requires module:loggers
* @requires module:data
* @requires module:queue
* @requires module:stack
* @requires {@link https://www.npmjs.com/package/@haystacks/constants|@haystacks/constants}
* @requires {@link https://www.npmjs.com/package/path|path}
* @author Seth Hollingsead
* @date 2022/02/02
* @copyright Copyright © 2022-… by Seth Hollingsead. All rights reserved
*/
// Internal imports
import ruleBroker from './ruleBroker.js';
import commandsLibrary from '../commandsBlob/commandsLibrary.js';
import colorizer from '../executrix/colorizer.js';
import configurator from '../executrix/configurator.js';
import loggers from '../executrix/loggers.js';
import D from '../structures/data.js';
import queue from '../structures/queue.js';
import stack from '../structures/stack.js';
// External imports
import hayConst from '@haystacks/constants';
import path from 'path';
const {bas, biz, clr, cfg, gen, msg, num, sys, wrd} = hayConst;
const baseFileName = path.basename(import.meta.url, path.extname(import.meta.url));
// framework.brokers.commandBroker.
const namespacePrefix = wrd.cframework + bas.cDot + wrd.cbrokers + bas.cDot + baseFileName + bas.cDot;
/**
* @function bootStrapCommands
* @description Captures all of the commands string-to-function cal map data in the commandsLibrary and migrates that dat a to the D-data structure.
* This is important now because we are going to allow the client to define their own commands separate from the system defined commands.
* So we need a way to merge al the client defined and system defined commands into one location.
* Then the command broker will execute commands rom the D-Data structure and not the commands library per-say.
* This will allow the system to expand much more dynamically and even be user-defined & flexible to client needs.
* @return {void}
* @author Seth Hollingsead
* @date 2022/02/02
*/
async function bootStrapCommands() {
let functionName = bootStrapCommands.name;
await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
await commandsLibrary.initCommandsLibrary();
await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
}
/**
* @function resetCommands
* @description Clears out and reinitializes the commands.
* @return {void}
* @date 2023/02/12
*/
async function resetCommands() {
let functionName = resetCommands.name;
await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
// await commandsLibrary.clearCommandsLibrary();
await commandsLibrary.initCommandsLibrary();
await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
}
/**
* @function addClientCommands
* @description Merges client defined commands with the system defined commands.
* @param {array<object>} clientCommands The client rules that should be merged with the system rules.
* @return {void}
* @author Seth Hollingsead
* @date 2022/02/02
*/
async function addClientCommands(clientCommands) {
let functionName = addClientCommands.name;
await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
// Object.assign(D[wrd.cCommands], clientCommands);
// D[wrd.cCommands] = {...D[wrd.cCommands], Object.keys(clientCommands): clientCommands[Object.keys(clientCommands)]};
for (const [key, value] of Object.entries(clientCommands)) {
// console.log('%%%%%%%%%%%%%%%%%% ---- >>>>>>>>> key is: ' + key);
D[wrd.cCommands] = {...D[wrd.cCommands], [`${key}`]: value};
} // End-for (const [key, value] of Object.entries(clientCommands))
// D-command stack is:
// console.log(namespacePrefix + functionName + bas.cColon + bas.cSpace + msg.cdCommandStackIs, D[wrd.cCommands]);
await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
}
/**
* @function addPluginCommands
* @description Merges plugin defined commands with the system defined commands.
* @param {string} pluginName The name of the current plugin these commands belong to.
* @param {array<object>} pluginCommands The plugin commands that should be merged with the system commands.
* @return {boolean} True or False to indicate if the merge was successful or not.
* @author Seth Hollingsead
* @date 2022/10/24
*/
async function addPluginCommands(pluginName, pluginCommands) {
let functionName = addPluginCommands.name;
await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
// pluginName is:
await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginNameIs + pluginName);
// pluginCommands is:
await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginCommandsIs + JSON.stringify(pluginCommands));
let returnData = false;
try {
// if (D[wrd.cCommands][wrd.cplugins] === undefined) {
// D[wrd.cCommands][wrd.cplugins] = {};
// }
// D[wrd.cCommands][wrd.cplugins][pluginName] = {};
// for (const [key, value] of Object.entries(pluginCommands)) {
// console.log('&&&&&&&&&&&&&&&&& ---- >>>>>>>> key is: ' + key);
// D[wrd.cCommands][wrd.cplugins][pluginName] = {...D[wrd.cplugin + wrd.cCommands], [`${key}`]: value};
// } // End-for (const [key, value] of Object.entries(pluginCommands));
// NOTE: The commands system was never designed to have a hierarchy storage, so when calling commands,
// its basically calling a flat list. So rather than adding the plugin commands according to the above structure.
// We will need to just add them to the flat list. If a plugin is unloaded,
// then each of its commands will need to be individually searched out and removed from the flat list.
// D-command stack before merge is:
// console.log(namespacePrefix + functionName + bas.cColon + bas.cSpace + msg.cdCommandStackBeforeMergeIs, D[wrd.cCommands]);
for (const [key, value] of Object.entries(pluginCommands[wrd.ccommands])) {
// console.log('&&&&&&&&&&&&&&&&& ---- >>>>>>>> key is: ' + key);
D[wrd.cCommands] = {...D[wrd.cCommands], [`${key}`]: value};
} // End-for (const [key, value] of Object.entries(pluginCommands))
returnData = true;
// D-command stack after merge is:
// console.log(namespacePrefix + functionName + bas.cColon + bas.cSpace + msg.cdCommandStackAfterMergeIs, D[wrd.cCommands]);
} catch (err) {
// ERROR: Failure to merge the plugin commands for plugin:
console.log(msg.cErrorAddPluginCommandsMessage01 + pluginName);
console.log(msg.cERROR_Colon + err);
}
await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + returnData);
await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
return returnData;
}
/**
* @function addPluginCommandAliases
* @description Merges plugin defined command aliases with the system defined command aliases.
* @param {string} pluginName The name of the current plugin these command aliases belong to.
* @param {object} pluginCommandAliases A JSON object that contains the plugin command aliases that
* should be merged with the system command aliases.
* @return {boolean} True or False to indicate if the merge was successful or not.
* @author Seth Hollingsead
* @date 2022/10/24
*/
async function addPluginCommandAliases(pluginName, pluginCommandAliases) {
let functionName = addPluginCommandAliases.name;
await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
// pluginName is:
await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginNameIs + pluginName);
// pluginCommandAliases is:
await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginCommandAliasesIs + JSON.stringify(pluginCommandAliases));
let returnData = false;
try {
if (D[sys.cCommandsAliases][wrd.cPlugins] === undefined) {
D[sys.cCommandsAliases][wrd.cPlugins] = {};
}
D[sys.cCommandsAliases][wrd.cPlugins][pluginName] = {};
D[sys.cCommandsAliases][wrd.cPlugins][pluginName] = pluginCommandAliases;
returnData = true;
} catch (err) {
// ERROR: Failure to merge the plugin command aliases for plugin:
console.log(msg.cErrorAddPluginCommandAliasesMessage01 + pluginName);
console.log(msg.cERROR_Colon + err);
}
await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + returnData);
await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
return returnData;
}
/**
* @function getValidCommand
* @description Parses the command string and returns an array that can be used to
* enqueue or execute that command. Useful for determining if a command is a valid command and
* working with multiple levels of delimiters for nested command calls, looking up a command alias, etc...
* @param {string} commandString The command string that should be parsed for a valid command.
* @param {string} commandDelimiter The delimiter that should be used to parse the command line.
* @return {boolean|string} False if the command is not valid, otherwise it returns the command string.
* @author Seth Hollingsead
* @date 2022/02/02
*/
async function getValidCommand(commandString, commandDelimiter) {
let functionName = getValidCommand.name;
await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
// commandString is:
await loggers.consoleLog(namespacePrefix + functionName, msg.ccommandStringIs + commandString);
// commandDelimiter is:
await loggers.consoleLog(namespacePrefix + functionName, msg.ccommandDelimiterIs + commandDelimiter);
let returnData = false;
let foundValidCommand = false;
let commandToExecute, commandArgs;
let commandArgsDelimiter = commandDelimiter;
if (commandDelimiter === null || commandDelimiter !== commandDelimiter || commandDelimiter === undefined) {
commandArgsDelimiter = bas.cSpace;
}
if (commandString && typeof commandString === wrd.cstring && commandString.includes(commandArgsDelimiter) === true) {
commandArgs = commandString.split(commandArgsDelimiter);
commandToExecute = commandArgs[0];
} else {
commandToExecute = commandString;
}
// commandString is:
await loggers.consoleLog(namespacePrefix + functionName, msg.ccommandStringIs + commandString);
// commandToExecute is:
await loggers.consoleLog(namespacePrefix + functionName, msg.ccommandToExecuteIs + commandToExecute);
if (commandString) {
if (D[wrd.cCommands][commandToExecute] != undefined) {
returnData = commandToExecute;
} else { // else-clause if (D[wrd.cCommands][commandToExecute] != undefined)
// else-clause looking for command aliases.
await loggers.consoleLog(namespacePrefix + functionName, msg.celseClauseLookingForCommandAliases);
// NOTE: It could be that the user entered a command alias, so we will need to search through all of the command aliases,
// to see if we can find a match, then get the actual command that should be executed.
let allCommandAliases = D[sys.cCommandsAliases];
// allCommandAliases is:
await loggers.consoleLog(namespacePrefix + functionName, msg.callCommandAliasesIs + JSON.stringify(allCommandAliases));
// Search through the data structure recursively to see if we can find the command or command alias.
foundValidCommand = await searchCommandAlias(allCommandAliases, commandToExecute);
// foundValidCommand is:
await loggers.consoleLog(namespacePrefix + functionName, msg.cfoundValidCommandIs + JSON.stringify(foundValidCommand));
// Check if we found a valid command and return it if we did,
// or pop a message to indicate the command was not found.
if (foundValidCommand === false) {
// WARNING: The specified command:
// does not exist, please try again!
console.log(msg.cWarningTheSpecifiedCommand + commandToExecute + msg.cdoesNotExistPleaseTryAgain + bas.cSpace + num.c1);
} else {
returnData = foundValidCommand[wrd.cName];
}
} // End-else
} else {
// Looks like the user entered something undefined: Pop the standard error message:
// WARNING: The specified command:
// does not exist, please try again!
console.log(msg.cWarningTheSpecifiedCommand + commandToExecute + msg.cdoesNotExistPleaseTryAgain);
}
await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData));
await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
return returnData;
}
/**
* @function countMatchingCommandAlias
* @description This is a recursive function that searches through all teh command aliases
* data structures and counts the number of command aliases that match the input alias.
* @param {object} commandAliasData The command alias data that should be searched recursively for the specified command alias.
* @param {string} commandAliasName The command alias name/string that should be searched for and counted when matches are found.
* @return {integer} The count of the number of command aliases that match the given input alias.
* @author Seth Hollingsead
* @date 2022/06/06
*/
async function countMatchingCommandAlias(commandAliasData, commandAliasName) {
let functionName = countMatchingCommandAlias.name;
await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
// commandAliasData is:
await loggers.consoleLog(namespacePrefix + functionName, msg.ccommandAliasDataIs + JSON.stringify(commandAliasData));
// commandAliasName is:
await loggers.consoleLog(namespacePrefix + functionName, msg.ccommandAliasNameIs + commandAliasName);
let commandAliasCount = 0;
let blackColorArray = await colorizer.getNamedColorData(clr.cBlack, [0,0,0]);
let redColorArray = await colorizer.getNamedColorData(clr.cRed, [255,0,0]);
if (typeof commandAliasData === wrd.cobject) {
for (let commandAliasEntity in commandAliasData) {
// commandAliasEntity is:
await loggers.consoleLog(namespacePrefix + functionName, msg.ccommandAliasEntityIs + JSON.stringify(commandAliasEntity));
// commandAliasEntityValue is:
await loggers.consoleLog(namespacePrefix + functionName, msg.ccommandAliasEntityValueIs + JSON.stringify(commandAliasData[commandAliasEntity]));
if (commandAliasEntity.toUpperCase() != commandAliasName.toUpperCase()) {
if (commandAliasData[commandAliasEntity] != undefined && commandAliasData[commandAliasEntity][wrd.cAliases] != undefined) {
let aliasList = commandAliasData[commandAliasEntity][wrd.cAliases];
let arrayOfAliases = aliasList.split(bas.cComa);
for (const element of arrayOfAliases) {
let currentAlias = element;
await loggers.consoleLog(namespacePrefix + functionName, msg.ccurrentAliasIs + currentAlias);
await loggers.consoleLog(namespacePrefix + functionName, msg.ccommandAliasNameIs + commandAliasName);
if (commandAliasName === currentAlias) {
// Found a matching alias entry! 1
await loggers.consoleLog(namespacePrefix + functionName, msg.cFoundMatchingAliasEntry1);
commandAliasCount = commandAliasCount + 1;
// Don't break, continue searching, so we get a full count of any duplicates found.
}
} // End-for (const element of arrayOfAliases)
} else {
if (commandAliasData[commandAliasEntity] != undefined) {
let tempCommandAliasCount = await countMatchingCommandAlias(commandAliasData[commandAliasEntity], commandAliasName);
// tempCommandAliasCount is:
await loggers.consoleLog(namespacePrefix + functionName, msg.ctempCommandAliasCountIs + tempCommandAliasCount);
if (tempCommandAliasCount > 0) {
// adding commandAliasCount:
await loggers.consoleLog(namespacePrefix + functionName, msg.caddingCommandAliasCount + commandAliasCount);
commandAliasCount = commandAliasCount + tempCommandAliasCount;
// After adding commandAliasCount and tempCommandAliasCount:
await loggers.consoleLog(namespacePrefix + functionName, msg.cAfterAddingCommandAliasCountAndTempCommandAliasCount + commandAliasCount);
// Don't break, continue searching, so we get a full count of any duplicates found.
} // End-if (tempCommandAliasCount > 0)
} else {
// ERROR: A command is missing command aliases definitions. Data:
let errorMessage = msg.cErrorCountMatchingCommandAliasMessage01 + JSON.stringify(commandAliasData);
errorMessage = await colorizer.colorizeMessageSimple(errorMessage, blackColorArray, true);
errorMessage = await colorizer.colorizeMessageSimple(errorMessage, redColorArray, false);
console.log(errorMessage);
// Its an error, but not a duplicate error, however, we need to report it some how.
commandAliasCount = commandAliasCount + 1; // We've console logged it as best we can, we need to raise the flag anyway.
}
}
} else if (commandAliasEntity.toUpperCase() === commandAliasName.toUpperCase()) {
// Found a matching entry! 2
await loggers.consoleLog(namespacePrefix + functionName, msg.cFoundMatchingAliasEntry2);
commandAliasCount = commandAliasCount + 1;
// Don't break, continue searching, so we get a full count of any duplicates found.
}
} // End-for (let commandAliasEntity in commandAliasData)
} // End-if (typeof commandAliasData === wrd.cobject)
// commandAliasCount is:
await loggers.consoleLog(namespacePrefix + functionName, msg.ccommandAliasCountIs + JSON.stringify(commandAliasCount));
await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
return commandAliasCount;
}
/**
* @function searchCommandAlias
* @description This is a recursive function that searches through all the command aliases
* data structures and returns the one command data object that matches the input name.
* @param {object} commandAliasData The command alias data that should be searched recursively for the specified command alias.
* @param {string} commandAliasName The command alias name/string that should be found.
* @return {object} The command object that corresponds to the input command alias name.
* @author Seth Hollingsead
* @date 2022/05/27
*/
async function searchCommandAlias(commandAliasData, commandAliasName) {
let functionName = searchCommandAlias.name;
await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
// commandAliasData is:
await loggers.consoleLog(namespacePrefix + functionName, msg.ccommandAliasDataIs + JSON.stringify(commandAliasData));
// commandAliasName is:
await loggers.consoleLog(namespacePrefix + functionName, msg.ccommandAliasNameIs + commandAliasName);
let commandAliasObject = false;
if (typeof commandAliasData === wrd.cobject && typeof commandAliasName === wrd.cstring) {
for (let commandAliasEntity in commandAliasData) {
// commandAliasEntity is:
await loggers.consoleLog(namespacePrefix + functionName, msg.ccommandAliasEntityIs + JSON.stringify(commandAliasEntity));
// commandAliasEntityValue is:
await loggers.consoleLog(namespacePrefix + functionName, msg.ccommandAliasEntityValueIs + JSON.stringify(commandAliasData[commandAliasEntity]));
if (commandAliasEntity.toUpperCase() != commandAliasName.toUpperCase() && commandAliasData[commandAliasEntity]) {
if (commandAliasData[commandAliasEntity][wrd.cAliases] != undefined) {
let aliasList = commandAliasData[commandAliasEntity][wrd.cAliases];
let arrayOfAliases = aliasList.split(bas.cComa);
for (const element of arrayOfAliases) {
let currentAlias = element;
if (commandAliasName === currentAlias ||
commandAliasName === bas.cDash + currentAlias ||
commandAliasName === bas.cDoubleDash + currentAlias ||
commandAliasName === bas.cForwardSlash + currentAlias ||
commandAliasName === bas.cBackSlash + currentAlias ||
commandAliasName.toUpperCase() === currentAlias.toUpperCase() ||
commandAliasName.toUpperCase() === bas.cDash + currentAlias.toUpperCase() ||
commandAliasName.toUpperCase() === bas.cDoubleDash + currentAlias.toUpperCase() ||
commandAliasName.toUpperCase() === bas.cForwardSlash + currentAlias.toUpperCase() ||
commandAliasName.toUpperCase() === bas.cBackSlash + currentAlias.toUpperCase() ||
commandAliasName.toLowerCase() === currentAlias.toLowerCase() ||
commandAliasName.toLowerCase() === bas.cDash + currentAlias.toLowerCase() ||
commandAliasName.toLowerCase() === bas.cDoubleDash + currentAlias.toLowerCase() ||
commandAliasName.toLowerCase() === bas.cForwardSlash + currentAlias.toLowerCase() ||
commandAliasName.toLowerCase() === bas.cBackSlash + currentAlias.toLowerCase()) {
// Found a matching alias entry!
await loggers.consoleLog(namespacePrefix + functionName, msg.cFoundMatchingAliasEntry1);
commandAliasObject = commandAliasData[commandAliasEntity];
break;
}
} // End-for (const element of arrayOfAliases)
} else {
let commandAliasesObjectTemp = await searchCommandAlias(commandAliasData[commandAliasEntity], commandAliasName);
// commandAliasesObjectTemp is:
await loggers.consoleLog(namespacePrefix + functionName, msg.ccommandAliasesObjectTempIs + JSON.stringify(commandAliasesObjectTemp));
if (commandAliasesObjectTemp) {
commandAliasObject = commandAliasesObjectTemp;
break;
} // End-if (commandAliasesObjectTemp)
}
} else if (commandAliasEntity.toUpperCase() === commandAliasName.toUpperCase()) {
// Found a matching entry!
await loggers.consoleLog(namespacePrefix + functionName, msg.cFoundMatchingAliasEntry2);
commandAliasObject = commandAliasData[commandAliasEntity];
break;
}
} // End-for (let commandAliasEntity in commandAliasData)
} // End-if (typeof commandAliasData === wrd.cobject)
// commandAliasObject is:
await loggers.consoleLog(namespacePrefix + functionName, msg.ccommandAliasObjectIs + JSON.stringify(commandAliasObject));
await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
return commandAliasObject;
}
/**
* @function getAllCommandAliasData
* @description Recursively gets all of the commands alias data from all levels and flattens them into a single array for printing out to the help command.
* @param {object} commandAliasDataStructure The command alias data structure that should be recursively flattened into a single array for output.
* If the input is undefined then the main CommandsAliases data structure will be used at the root of the command aliases data hive.
* @return {array<string>|boolean} An array of all the command aliases currently needing to be flattened or
* a boolean True or False to indicate that a leaf-node has been found by the recursive caller.
* @author Seth Hollingsead
* @date 2022/05/27
*/
async function getAllCommandAliasData(commandAliasDataStructure) {
let functionName = getAllCommandAliasData.name;
await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
// commandAliasDataStructure is:
await loggers.consoleLog(namespacePrefix + functionName, msg.ccommandAliasDataStructureIs + JSON.stringify(commandAliasDataStructure));
let allCommandsData = false;
let internalCommandAliasDataStructure;
if (commandAliasDataStructure === undefined) {
internalCommandAliasDataStructure = JSON.parse(JSON.stringify(D[sys.cCommandsAliases]));
} else {
internalCommandAliasDataStructure = JSON.parse(JSON.stringify(commandAliasDataStructure));
}
// internalCommandAliasDataStructure is:
await loggers.consoleLog(namespacePrefix + functionName, msg.cinternalCommandAliasDataStructureIs + JSON.stringify(internalCommandAliasDataStructure));
if (typeof internalCommandAliasDataStructure === wrd.cobject) {
allCommandsData = [];
for (let commandAliasEntity in internalCommandAliasDataStructure) {
// commandAliasEntity is:
await loggers.consoleLog(namespacePrefix + functionName, msg.ccommandAliasEntityIs + JSON.stringify(commandAliasEntity));
// internalCommandAliasDataStructure[commandAliasEntity] is:
await loggers.consoleLog(namespacePrefix + functionName, msg.cinternalCommandAliasDataStructureCommandAliasEntityIs + JSON.stringify(internalCommandAliasDataStructure[commandAliasEntity]));
if (typeof internalCommandAliasDataStructure[commandAliasEntity] === wrd.cobject) {
// internalCommandAliasDataStructure[commandAliasEntity] is of type object!
await loggers.consoleLog(namespacePrefix + functionName, msg.cgetAllCommandAliasDataMessage01);
let allCommandAliasesTemp;
allCommandAliasesTemp = await getAllCommandAliasData(internalCommandAliasDataStructure[commandAliasEntity]);
// allCommandAliasesTemp returned from the recursive call is:
await loggers.consoleLog(namespacePrefix + functionName, msg.callCommandAliasesTempReturnedFromRecursiveCallIs + JSON.stringify(allCommandAliasesTemp));
if (allCommandAliasesTemp === false) {
// The recursive call returned false, so push the current entity to the output array!
await loggers.consoleLog(namespacePrefix + functionName, msg.cgetAllCommandAliasDataMessage02);
allCommandsData.push(internalCommandAliasDataStructure);
// allCommandsData after pushing to the array is:
await loggers.consoleLog(namespacePrefix + functionName, msg.callCommandsDataAfterPushingToTheArrayIs + JSON.stringify(allCommandsData));
break;
} else {
allCommandsData = await ruleBroker.processRules([allCommandsData, allCommandAliasesTemp], [biz.cobjectDeepMerge]);
}
} else {
if (Array.isArray(allCommandsData) === true && allCommandsData.length === 0) {
// NOTE: Only reset it if it does not already contain some data. We could loose data if we didn't check first.
allCommandsData = false; // Reset it, because it was reinitialized to an array.
}
}
} // End-for (let commandAliasEntity in internalCommandAliasDataStructure)
} // End-if (typeof internalCommandAliasDataStructure === wrd.cobject)
// allCommandsData is:
await loggers.consoleLog(namespacePrefix + functionName, msg.callCommandsDataIs + JSON.stringify(allCommandsData));
await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
return allCommandsData;
}
/**
* @function getCommandNamespaceDataObject
* @description Recursively scans through the entire command alias data structure looking for an object that matches the input namespace name.
* When that namespace is found, the entire object is returned.
* @param {object} commandAliasDataStructure The command alias data structure that should be recursively searched for the namespace specified.
* if the input is undefined then the main cCommandsAliases data structure will be used at the root of the CommandAliases data hive.
* @param {string} namespaceToFind The namespace to look for in the command alias metaData data structure.
* @return {object|boolean} The namespace object if it is found, or False if the namespace object was not found.
* @author Seth Hollingsead
* @date 2022/05/27
*/
async function getCommandNamespaceDataObject(commandAliasDataStructure, namespaceToFind) {
let functionName = getCommandNamespaceDataObject.name;
await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
// commandAliasDataStructure is:
await loggers.consoleLog(namespacePrefix + functionName, msg.ccommandAliasDataStructureIs + JSON.stringify(commandAliasDataStructure));
// namespaceToFind is:
await loggers.consoleLog(namespacePrefix + functionName, msg.cnamespaceToFindIs + namespaceToFind);
let namespaceCommandsObject = false;
if (commandAliasDataStructure === undefined) {
commandAliasDataStructure = D[sys.cCommandsAliases];
}
for (let commandAliasEntity in commandAliasDataStructure) {
// commandAliasEntity is:
await loggers.consoleLog(namespacePrefix + functionName, msg.ccommandAliasEntityIs + JSON.stringify(commandAliasEntity));
// commandAliasDataStructure[commandAliasEntity] is:
await loggers.consoleLog(namespacePrefix + functionName, msg.ccommandAliasDataStructureCommandAliasEntityIs + JSON.stringify(commandAliasDataStructure[commandAliasEntity]));
if (commandAliasEntity === namespaceToFind) {
namespaceCommandsObject = commandAliasDataStructure[commandAliasEntity];
break;
} else if (typeof commandAliasDataStructure[commandAliasEntity] === wrd.cobject) {
// Search recursively
let namespaceCommandsTempObject = await getCommandNamespaceDataObject(commandAliasDataStructure[commandAliasEntity], namespaceToFind);
if (namespaceCommandsTempObject !== false) {
// Then we must have found the namespace object we were looking for in the recursion call.
// Just return it, and skip out of the loop.
namespaceCommandsObject = namespaceCommandsTempObject;
break;
}
}
} // End-for (let commandAliasEntity in commandAliasDataStructure)
// namespaceCommandsObject is:
await loggers.consoleLog(namespacePrefix + functionName, msg.cnamespaceCommandsObjectIs + JSON.stringify(namespaceCommandsObject));
await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
return namespaceCommandsObject;
}
/**
* @function getCommandArgs
* @description Gets the arguments of the current command.
* @param {string} commandString The command string that should be parsed fro command arguments.
* @param {string} commandDelimiter The delimiter that should be used to parse the command line.
* @return {array<boolean|string|integer>} Any array of arguments, some times these might actually be nested command calls.
* @author Seth Hollingsead
* @date 2022/02/02
*/
async function getCommandArgs(commandString, commandDelimiter) {
let functionName = getCommandArgs.name;
await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
// commandString is:
await loggers.consoleLog(namespacePrefix + functionName, msg.ccommandStringIs + commandString);
// commandDelimiter is:
await loggers.consoleLog(namespacePrefix + functionName, msg.ccommandDelimiterIs + commandDelimiter);
let returnData = false;
let commandArgsDelimiter = commandDelimiter;
let isOddRule = [biz.cisOdd];
let replaceCharacterAtIndexRule = [biz.creplaceCharacterAtIndex];
let replaceTildesWithSingleQuoteRule = [biz.creplaceCharacterWithCharacter];
let stringLiteralCommandDelimiterAdded = false;
let secondaryCommandArgsDelimiter = await configurator.getConfigurationSetting(wrd.csystem, cfg.csecondaryCommandDelimiter);
if (commandDelimiter === null || commandDelimiter !== commandDelimiter || commandDelimiter === undefined) {
commandArgsDelimiter = bas.cSpace;
}
if (typeof commandString === wrd.cstring && commandString.includes(commandArgsDelimiter) === true) {
// NOTE: All commands that enqueue or execute commands need to pass through this function.
// There is a case where the user might pass a string with spaces or other code/syntax.
// So we need to split first by single character string delimiters and parse the
// non-string array elements to parse command arguments without accidentally parsing string literal values as command arguments.
if (commandString.includes(bas.cBackTickQuote) === true) {
// commandString contains either a singleQuote or a backTickQuote
await loggers.consoleLog(namespacePrefix + functionName, msg.ccommandStringContainsEitherSingleQuoteOrBackTickQuote);
let preSplitCommandString;
if (commandString.includes(bas.cBackTickQuote) === true) {
// commandString contains a singleQuote!
await loggers.consoleLog(namespacePrefix + functionName, msg.ccommandStringContainsSingleQuote);
// NOTE: We cannot actually just replace ach single quote, we need to tag each single quote in pairs of 2.
// The first one should be post-tagged, i.e. replace "'" with "'~" and the second should be pre-tagged i.e. replace "'" with "~'".
// Then if there are more single quotes, the thirst post-tagged, i.e. replace "'" with "'~", etc...
let numberOfSingleQuotes = commandString.split(bas.cBackTickQuote).length - 1;
// Determine if the number of single quotes is odd or even?
// About to call the rule broker to process on the number of single quotes and determine if it-be even or odd.
await loggers.consoleLog(namespacePrefix + functionName, msg.cgetCommandArgsMessage1 + sys.cgetCommandArgsMessage2);
if (numberOfSingleQuotes >= 2 && await ruleBroker.processRules([numberOfSingleQuotes, ''], isOddRule) === false) {
// numberOfSingleQuotes is >= 2 & the numberOfSingleQuotes is EVEN! YAY!
await loggers.consoleLog(namespacePrefix + functionName, msg.cnumberOfSingleQuotesIsEven);
let indexOfStringDelimiter;
for (let i = 0; i < numberOfSingleQuotes; i++) {
// Iterate over each one and if they are even or odd we will change how we replace ach single quote character as described above.
if (i === 0) {
// Get the index of the first string delimiter.
indexOfStringDelimiter = commandString.indexOf(bas.cBackTickQuote, 0);
// First index is:
await loggers.consoleLog(namespacePrefix + functionName, msg.cFirstIndexIs + indexOfStringDelimiter);
// commandString.replace(bas.cBackTickQuote, bas.cBackTickQuote + bas.cTilde);
// Rather than use the above, we will make a business rule o replace at index, the above replaces all isntances and we don't want that!
commandString = await ruleBroker.processRules([commandString, [indexOfStringDelimiter, bas.cBackTickQuote + bas.cTilde]], replaceCharacterAtIndexRule);
stringLiteralCommandDelimiterAdded = true;
// commandString after tagging the first string delimiter:
await loggers.consoleLog(namespacePrefix + functionName, msg.ccommandStringAfterTaggingTheFirstStringDelimiter + commandString);
} else {
indexOfStringDelimiter = commandString.indexOf(bas.cBackTickQuote, indexOfStringDelimiter + 1);
// Additional index is:
await loggers.consoleLog(namespacePrefix + functionName, msg.cAdditionalIndexIs + indexOfStringDelimiter);
// Determine if it is odd or even.
// NOTE: We start our count with 0 which would technically be our odd, then 1 should be even, but 1 is an odd number, so the logic here should actually be backwards.
// an even value for "i" would be the odd i-th delimiter value.
if (await ruleBroker.processRules([i.toString(), ''], isOddRule) === true) {
// We are on the odd index, 1, 3, 5, etc...
// odd index
await loggers.consoleLog(namespacePrefix + functionName, msg.coddIndex);
commandString = await ruleBroker.processRules([commandString, [indexOfStringDelimiter, bas.cTilde + bas.cBackTickQuote]], replaceCharacterAtIndexRule);
stringLiteralCommandDelimiterAdded = true;
// commandString after tagging an odd string delimiter:
await loggers.consoleLog(namespacePrefix + functionName, msg.ccommandStringAfterTaggingAnOddStringDelimiter + commandString);
} else {
// We are on the even index 2, 4, 6, etc...
// even index
await loggers.consoleLog(namespacePrefix + functionName, msg.cevenIndex);
commandString = await ruleBroker.processRules([commandString, [indexOfStringDelimiter, bas.cBackTickQuote + bas.cTilde]], replaceCharacterAtIndexRule);
stringLiteralCommandDelimiterAdded = true;
// commandString after tagging an even string delimiter:
await loggers.consoleLog(namespacePrefix + functionName, msg.ccommandStringAfterTaggingAnEvenStringDelimiter + commandString);
}
}
} // End-for (let i = 0; i < numberOfSingleQuotes; i++)
preSplitCommandString = commandString.split(bas.cBackTickQuote);
// Now we can check which segments of the array contain our Tilde character, since we used that to tag our single quotes.
// And the array element that contains the Tilde tag we wil not split.
// Ultimately everything needs to be returned as an array, make sure we trim the array elements so we don't get any empty array elements.
// preSpitCommandString is:
await loggers.consoleLog(namespacePrefix + functionName, msg.cpreSplitCommandStringIs + JSON.stringify(preSplitCommandString));
for (let j = 0; j < preSplitCommandString.length; j++) {
let preSplitCommandStringElement = preSplitCommandString[j];
preSplitCommandStringElement = preSplitCommandStringElement.trim(); // Make sure to get rid of any white space on the ends of the string.
let postSplitCommandString;
if (j === 0) { // Make sure we re-initialize our return value to an array, since it was set first to a boolean value.
returnData = [];
}
if (preSplitCommandStringElement.includes(bas.cTilde) === false) {
postSplitCommandString = preSplitCommandStringElement.split(commandArgsDelimiter);
for (const element of postSplitCommandString) {
if (element !== '') {
// postSplitCommandString[k] is:
await loggers.consoleLog(namespacePrefix + functionName, msg.cpostSplitCommandStringIs + JSON.stringify(element));
returnData.push(element);
await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData));
} // End-if (postSplitCommandString[k] !== '')
} // End-for (const element of postSplitCommandString)
} else {
// NOTE: We cannot just push the quoted string array back onto the array. Well we might be able to,
// but if the last character on the last element of the returnData array is a secondaryCommandArgsDelimiter
// then we need to just append our string to that array element, after we remove the tilde string tags,
// and replace them with our single quotes again.
if (returnData[returnData.length - 1].slice(-1) === secondaryCommandArgsDelimiter) {
preSplitCommandStringElement = await ruleBroker.processRules([preSplitCommandStringElement, [/~/g, bas.cBackTickQuote]], replaceTildesWithSingleQuoteRule);
returnData[returnData.length - 1] = returnData[returnData.length - 1] + preSplitCommandStringElement;
} else {
// preSplitCommandSringElement is:
await loggers.consoleLog(namespacePrefix + functionName, msg.cpreSplitCommandStringElementIs + JSON.stringify(preSplitCommandStringElement));
returnData.push(preSplitCommandStringElement); // Add the string now.
}
await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData));
}
} // End-for (let j = 0; j < preSplitCommandString.length; j++)
} // End-if (numberOfSingleQuotes >= 2 && ruleBroker.processRules(numberOfSingleQuotes, '', isOddRule) === false)
} // End-if (commandString.includes(bas.cBackTickQuote) === true)
// We might need much additional logic to manage the case that the string contains multiple levels of commands with strings....in that case:
// The command system will probably need to implement a re-assignment of the string delimiter, also using the bas.cBackTickQuote.
// I have started to lay out some of that logic above, but we are FAR from it, and there isn't any business need for it right now.
// So I will handle that case if & when I come to it.
} else {
// Doing a straight split of the commandString:
await loggers.consoleLog(namespacePrefix + functionName, msg.cDoingStraightSplitCommandString + commandString);
returnData = commandString.split(commandArgsDelimiter);
await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData));
}
} // End-if (commandString.includes(commandArgsDelimiter) === true)
if (stringLiteralCommandDelimiterAdded === true) {
// This means we need to remove some bas.cTilde from one or more of the command args.
await ruleBroker.processRules([returnData, ''], [biz.cremoveStringLiteralTagsFromArray]);
}
await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData));
await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
return returnData;
}
/**
* @function executeCommand
* @description Takes a command string with all its associated arguments, data & meta-data.
* This function will parse all of that out of the command lien variable that is passed in.
* And finally pass all of that data on the execution of the actual command.
* @param {string} commandString The command to execute along with all the associated command arguments, data & meta-data.
* @return {array<boolean,string|integer|boolean|object|array>} An array with a boolean True or False value to
* indicate if the application should exit or not exit, followed by the command output.
* @author Seth Hollingsead
* @date 2022/02/02
*/
async function executeCommand(commandString) {
// Here we need to do all of the parsing for the command.
// Might be a good idea to rely on business rules to do much of the parsing for us!
// Also don't forget this is where we will need to implement the command performance
// tracking & command results processing such as pass-fail,
// so that when a chain of commands has completed execution we can evaluate command statistics and metrics.
let functionName = executeCommand.name;
await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
// commandString is:
await loggers.consoleLog(namespacePrefix + functionName, msg.ccommandStringIs + commandString);
let returnData = [];
let commandToExecute = await getValidCommand(commandString, await configurator.getConfigurationSetting(wrd.csystem, cfg.cprimaryCommandDelimiter));
// commandToExecute is:
await loggers.consoleLog(namespacePrefix + functionName, msg.ccommandToExecuteIs + commandToExecute);
let commandArgs = await getCommandArgs(commandString, await configurator.getConfigurationSetting(wrd.csystem, cfg.cprimaryCommandDelimiter));
// commandArgs is:
await loggers.consoleLog(namespacePrefix + functionName, msg.ccommandArgsIs + commandArgs);
let commandMetricsEnabled = await configurator.getConfigurationSetting(wrd.csystem, cfg.cenableCommandPerformanceMetrics);
let commandStartTime = '';
let commandEndTime = '';
let commandDeltaTime = '';
await loggers.consoleLog(namespacePrefix + functionName, msg.ccommandQueueIs + await queue.queuePrint(sys.cCommandQueue));
if (commandMetricsEnabled === true) {
// Here we will capture the start time of the command we are about to execute.
// After executing we wil capture the end time and then
// compute the difference to determine how many milliseconds it took to run the command.
commandStartTime = await ruleBroker.processRules([gen.cYYYYMMDD_HHmmss_SSS, ''], [biz.cgetNowMoment]);
// Command Start time is:
await loggers.consoleLog(namespacePrefix + functionName, msg.cCommandStartTimeIs + commandStartTime);
} // End-if (commandMetricsEnabled === true)
try {
if (commandToExecute !== false && commandArgs !== false) {
// console.log('commandToExecute is: ' + commandToExecute);
returnData = await D[wrd.cCommands][commandToExecute](commandArgs, '');
} else if (commandToExecute !== false && commandArgs === false) {
// This could be a command without any arguments.
// console.log('This could be a command without any arguments.');
returnData = await D[wrd.cCommands][commandToExecute]('', '');
} else {
// This command does not exist, nothing to execute, but we don't want the application to exit.
// An error message should have already been thrown, but we should throw another one here.
// WARNING: Command does not exist, please enter a valid command and try again!
console.log(msg.cexecuteCommandMessage1);
returnData = [true, false];
}
} catch (err) {
console.log(msg.cERROR_Colon + bas.cSpace + err);
console.log(msg.cexecuteCommandMessage1);
returnData = [true, false];
}
if (commandMetricsEnabled === true && commandToExecute !== '' && commandToExecute !== false) {
let performanceTrackingObject = {};
commandEndTime = await ruleBroker.processRules([gen.cYYYYMMDD_HHmmss_SSS, ''], [biz.cgetNowMoment]);
// Command End time is:
await loggers.consoleLog(namespacePrefix + functionName, msg.cCommandEndTimeIs + commandEndTime);
// Now compute the delta tme so we know how long it took to run that command.
commandDeltaTime = await ruleBroker.processRules([commandStartTime, commandEndTime], [biz.ccomputeDeltaTime]);
// Command run-time is:
await loggers.consoleLog(namespacePrefix + functionName, msg.cCommandRunTimeIs + commandDeltaTime);
// Check to make sure the command performance tracking stack exists or does not exist.
if (D[cfg.ccommandsPerformanceTrackingStack] === undefined) {
await stack.initStack(cfg.ccommandsPerformanceTrackingStack);
}
if (D[cfg.ccommandNamesPerformanceTrackingStack] === undefined) {
await stack.initStack(cfg.ccommandNamesPerformanceTrackingStack);
}
performanceTrackingObject = {Name: commandToExecute, RunTime: commandDeltaTime};
if (await stack.contains(cfg.ccommandNamesPerformanceTrackingStack, commandToExecute) === false) {
await stack.push(cfg.ccommandNamesPerformanceTrackingStack, commandToExecute);
}
await stack.push(cfg.ccommandsPerformanceTrackingStack, performanceTrackingObject);
// stack.print(cfg.ccommandNamesPerformanceTrackingStack);
// stack.print(cfg.ccommandsPerformanceTrackingStack);
} // End-if (commandMetricsEnabled === true && commandToExecute !== '' && commandToExecute !== false)
await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData));
await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
return returnData;
}
/**
* @function removePluginCommands
* @description Parses through the commands and finds the commands associated with the named plugin.
* Then removes that data shredding it from existence at the edge of a black hole.
* @param {string} pluginName The name of the plugin that should have its commands removed from the D-data structure.
* @return {boolean} True or False to indicate if the removal of the data was completed successfully or not.
* @author Seth Hollingsead
* @date 2023/02/01
*/
async function removePluginCommands(pluginName) {
let functionName = removePluginCommands.name;
await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
// pluginName is:
await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginNameIs + pluginName);
let returnData = false;
let allCommands = D[wrd.cCommands];
// NOTE: We are going to have to get the names of the individual commands for the plugin,
// from the plugin constants validation for the commands,
// then iterate over them to remove all of the plugin business rules one by one.
let pluginConstantsValidation = D[sys.cConstantsValidationData][wrd.cPlugins][pluginName];
let pluginConstantsValidationCommands = {};
if (pluginConstantsValidation) {
pluginConstantsValidationCommands = pluginConstantsValidation[sys.cpluginCommandConstantsValidation];
} else {
// ERROR: Constants validation data for the specified plugin was not found. Plugin:
console.log(msg.cremovePluginBusinessRulesMessage01 + pluginName);
}
if (pluginConstantsValidationCommands) {
try {
for (const pluginCommandsRuleKey in pluginConstantsValidationCommands) {
let pluginCommandConstValidationObject = pluginConstantsValidationCommands[pluginCommandsRuleKey];
// pluginCommandConstValidationObject is:
await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginCommandsConstValidationObjectIs + JSON.stringify(pluginCommandConstValidationObject));
// Removing plugin command:
await loggers.consoleLog(namespacePrefix + functionName, msg.cremovePluginCommandsMessage01 + pluginCommandConstValidationObject[wrd.cActual]);
delete allCommands[pluginCommandConstValidationObject[wrd.cActual]];
}
returnData = true;
} catch (err) {
// ERROR: Failure attempting to delete the plugin commands for plugin:
console.log(msg.cremovePluginCommandsMessage02 + pluginName);
console.