@itentialopensource/adapter-nokia_vitalqip
Version:
This adapter integrates with system described as: Nokia Vital QIP
299 lines (266 loc) • 10.3 kB
JavaScript
/* eslint global-require:warn */
/* eslint import/no-dynamic-require:warn */
/* eslint no-param-reassign:warn */
const fs = require('fs-extra');
const acorn = require('acorn');
// Getting the base directory:
let adaptdir = __dirname;
if (adaptdir.endsWith('/utils')) {
adaptdir = adaptdir.substring(0, adaptdir.length - 6);
}
function createObjectForFunction(
funcName,
funcArgs,
entityPath,
description,
workflow
) {
const funcObject = {};
funcObject.method_signature = `${funcName}(${funcArgs.join(', ')})`;
funcObject.path = entityPath !== undefined ? entityPath : '';
if (description === undefined) {
funcObject.description = '';
funcObject.workflow = 'No';
} else {
funcObject.description = description;
funcObject.workflow = workflow;
}
return funcObject;
}
function getPathFromEntity(entity, funcName) {
let epath;
if (entity === undefined || entity === '.generic') {
epath = undefined;
} else {
// Access the action.js file for the certain entity to get the path
const entityPath = `${adaptdir}/entities/${entity}/action.json`;
const actionJSON = require(entityPath);
actionJSON.actions.forEach((action) => {
if (action.name === funcName) {
if (typeof action.entitypath === 'object') {
epath = '';
const keys = Object.keys(action.entitypath);
for (let k = 0; k < keys.length; k += 1) {
epath += `${keys[k]}:${action.entitypath[keys[k]]} <br /> `;
}
epath = epath.substring(0, epath.length - 8);
} else {
epath = action.entitypath;
}
}
});
}
return epath;
}
function recurseCallExpressions(statement, callList) {
// Recursively finds all CallExpressions in the syntax tree
if (statement.type === 'CallExpression') callList.push(statement);
const keys = Object.keys(statement);
for (let k = 0; k < keys.length; k += 1) {
if (typeof statement[keys[k]] === 'object' && statement[keys[k]] !== null) {
recurseCallExpressions(statement[keys[k]], callList);
}
}
}
function readFileUsingLib(filename, descriptionObj, workflowObj, functionList) {
// read the file
const aFile = fs.readFileSync(filename, 'utf8');
// parsing the file to get the function and class declarations.
const aFileFuncArgs = acorn.parse(aFile, { ecmaVersion: 2020 });
// Track all method names that can be called (identifyRequest + any proxy methods)
const allowedCallNames = new Set(['identifyRequest']);
// Looping through all the declarations parsed:
aFileFuncArgs.body.forEach((e) => {
// Getting only the class declaration as it has our required functions.
if (e.type === 'ClassDeclaration') {
const methodDefinition = e.body;
methodDefinition.body.forEach((method) => {
// Getting method name and its params in the class.
const funcName = method.key.name;
const funcArgs = [];
method.value.params.forEach((param) => {
if (param.type === 'Identifier') {
funcArgs.push(param.name);
} else if (param.type === 'RestElement') {
funcArgs.push(`...${param.argument.name}`);
} else {
const args = `${param.left.name} = ${param.right.raw}`;
funcArgs.push(args);
}
});
// Getting the entity for the method:
const callList = [];
method.value.body.body.forEach((statement) => {
recurseCallExpressions(statement, callList);
});
// Find calls to identifyRequest OR any discovered proxy methods
const requests = [];
for (let i = 0; i < callList.length; i += 1) {
if (callList[i].callee.property && allowedCallNames.has(callList[i].callee.property.name)) {
requests.push(callList[i]);
}
}
if (requests.length > 0) {
const expr = requests[0];
if (expr.arguments.length < 2) {
throw new Error(`Bad inputs in method ${funcName}`);
}
const arg0Type = expr.arguments[0].type;
const arg1Type = expr.arguments[1].type;
let entityPath;
if (arg0Type === 'Literal' && arg1Type === 'Literal') {
const entity = expr.arguments[0].value;
const actionName = expr.arguments[1].value;
entityPath = getPathFromEntity(entity, actionName);
} else {
// Non-standard format: anything that's not both literals
// Special handling for proxy methods (both args are identifiers matching first 2 params)
if (arg0Type === 'Identifier' && arg1Type === 'Identifier') {
const param1 = method.value.params[0];
const param2 = method.value.params[1];
const arg0Name = expr.arguments[0].name;
const arg1Name = expr.arguments[1].name;
// Check if this is a valid proxy method
const isValidProxy = param1 && param2
&& param1.type === 'Identifier' && param2.type === 'Identifier'
&& param1.name === arg0Name && param2.name === arg1Name;
// Only process valid proxy methods
if (isValidProxy) {
const calledMethod = expr.callee.property.name;
// Skip nested proxies (proxies calling other proxies)
if (calledMethod !== 'identifyRequest') {
console.log(`Skipping nested proxy method: ${funcName} (calls ${calledMethod})`);
return;
}
// First-level proxy: calls identifyRequest directly
// Add this proxy to the set so other methods can call it
allowedCallNames.add(funcName);
}
}
// All non-standard formats get documented without path
entityPath = undefined;
}
// Creating and storing the object for the method.
functionList.push(
createObjectForFunction(
funcName,
funcArgs,
entityPath,
descriptionObj[funcName],
workflowObj[funcName]
)
);
}
});
}
});
}
function readJSONFile(filename, descriptionObj, workflowObj) {
// Accessing the JSON file.
const phJSON = require(filename);
// Getting the methods array.
const methodArray = phJSON.methods;
methodArray.forEach((methodName) => {
// Getting the method description and workflow:
const funcName = methodName.name;
descriptionObj[funcName] = methodName.summary ? methodName.summary : methodName.description;
workflowObj[funcName] = methodName.task ? 'Yes' : 'No';
});
}
function readMDFile(filename, functionList) {
// Reading in the .md file and creating an array with each line as an element.
const mdFile = fs.readFileSync(filename, 'utf-8');
const fileSplit = mdFile.split('\n');
// Storing the data that should added later to the updated data.
const linesToAddLater = [];
let index = fileSplit.length - 1;
// Removing all the blank lines at the end of the file.
if (fileSplit[index] === '') {
while (fileSplit[index] === '') {
linesToAddLater.push(fileSplit.pop());
index -= 1;
}
}
// Checking if the last 2 lines are <br> and </table>. If not, the file is corrupted and the
// data at the end of the file should be fixed.
if (fileSplit[index] === '<br>' || fileSplit[index - 1] === '</table>') {
// Storing <br> and </table> to add later.
linesToAddLater.push(fileSplit.pop());
linesToAddLater.push(fileSplit.pop());
index -= 2;
} else {
console.log('The file has bad content at the end.');
return;
}
// if (fileSplit[index] !== '<br>' && fileSplit[index - 1] !== '</table>') {
// console.log('The file has bad content at the end.');
// return;
// } else {
// // Storing <br> and </table> to add later.
// linesToAddLater.push(fileSplit.pop());
// linesToAddLater.push(fileSplit.pop());
// index -= 2;
// }
// Removing all the lines until the header tags are reached.
while (!fileSplit[index].includes('<th')) {
fileSplit.pop();
index -= 1;
}
// Adding </tr> for the header row, because it got removed in the above loop.
fileSplit.push(' </tr>');
// Creating the tags for each method to be appended to the file.
const tdBeginTag = ' <td style="padding:15px">';
const tdEndTag = '</td>';
functionList.forEach((func) => {
const signCommand = `${tdBeginTag}${func.method_signature}${tdEndTag}`;
const descCommand = `${tdBeginTag}${func.description}${tdEndTag}`;
const pathCommand = `${tdBeginTag}${func.path}${tdEndTag}`;
const workflowCommand = `${tdBeginTag}${func.workflow}${tdEndTag}`;
fileSplit.push(' <tr>');
fileSplit.push(signCommand);
fileSplit.push(descCommand);
fileSplit.push(pathCommand);
fileSplit.push(workflowCommand);
fileSplit.push(' </tr>');
});
// Adding </table> and <br> at the end of the file to complete the table and the file.
while (linesToAddLater.length > 0) {
fileSplit.push(linesToAddLater.pop());
}
// Writing all the content back into the file.
fs.writeFileSync(filename, fileSplit.join('\n'), {
encoding: 'utf-8',
flag: 'w'
});
}
function getFileInfo() {
// If files don't exist:
if (!fs.existsSync(`${adaptdir}/adapter.js`)) {
console.log('Missing - utils/adapter.js');
return;
}
if (!fs.existsSync(`${adaptdir}/pronghorn.json`)) {
console.log('Missing - pronghorn.json');
return;
}
if (!fs.existsSync(`${adaptdir}/CALLS.md`)) {
console.log('Missing - CALLS.md');
return;
}
const descriptionObj = {};
const workflowObj = {};
// Get the method descriptions and the workflow values from pronghorn.json file.
readJSONFile(`${adaptdir}/pronghorn.json`, descriptionObj, workflowObj);
// Get the method signature, entity path and create an object that contains all the info regarding
// the method and push it to the functionList array.
const functionList = [];
readFileUsingLib(
`${adaptdir}/adapter.js`,
descriptionObj,
workflowObj,
functionList
);
// createMarkDown(functionList);
readMDFile(`${adaptdir}/CALLS.md`, functionList);
}
getFileInfo();