UNPKG

@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
/** * @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.