module.exports = {
equals: equals,
parseParameters: parseFactPaths,
getContext: getContext,
compilePath: compilePath,
typeof: _typeof
};
//https://regex101.com/r/zYhguP/6/
const paramRegex = /(?:\/\*?@ *([\w/.]+?) *\*\/ *\w+,?)|[(,]? *(\w+) *[,)]?/g;
const singleParamLambdaRegex = /^((?:\/\*@[^*]*\*\/\s*)?[\w$]+)\s*=>/;
const trailingTermRemovalRegex = /[^/]+?$/;
const pathCompactionRegex = /\/[^/.]+\/\.\./;
const currentPathRemoval = /\.\//g;
/**
* Compare two values and return true if they are equal. Fix date comparison issue and insure
* both types are the same.
*
* @param {*} a The first value used to compare.
* @param {*} b The second value used to caompare.
*/
function equals(a, b) {
var aValue = a;
if (a instanceof Date) {
aValue = a.getTime();
}
var bValue = b;
if (b instanceof Date) {
bValue = b.getTime();
}
return (aValue === bValue);
}
function _typeof(o) {
if (o instanceof Date) {
return "date";
}
else if (o instanceof Array) {
return "array";
}
else {
return typeof o;
}
}
/**
* Generator that parses all fact paths derived from the given rule parameters. Either return the
* parameter name or the path contained by the optional attribute comment. Supports regular
* functions as well as lambda (arrow) functions, with or without parenthesis around a single
* parameter.
*
* @example
*
* Using an attribute comment:
*
* async function(/¤@ /path/to/fact ¤/ param1, param2) {}
* async (/¤@ /path/to/fact ¤/ param1, param2) => {}
* async /¤@ /path/to/fact ¤/ param1 => {}
*
* In the above example:
*
* 1) ¤ replaces * because using * and / one after the other ends the documentation comment.
* 2) returned fact paths would be ['/path/to/fact', 'param2'] (or ['/path/to/fact'] for the
* single parameter lambda).
*
* @param {AsyncFunction} rule The rule
*/
function* parseFactPaths(rule) {
let allParams = extractParameterList(rule.toString());
if (!allParams) return;
paramRegex.lastIndex = 0;
let paramMatch = paramRegex.exec(allParams);
while (paramMatch) {
let param = paramMatch[1] || paramMatch[2];
if (param) yield param;
paramMatch = paramRegex.exec(allParams);
}
}
/**
* Extracts the raw parameter list (without the surrounding parenthesis) from the source code of
* a function. Returns an empty string when the function has no parameter.
*
* @param {String} code The function source code.
*/
function extractParameterList(code) {
let header = code.replace(/^\s*async\b\s*/, "");
// Lambda with a single parameter not enclosed in parenthesis: 'x => ...' or
// '/*@ /path */ x => ...'
let singleParamMatch = singleParamLambdaRegex.exec(header);
if (singleParamMatch) {
return singleParamMatch[1].replace(/\s+/g, " ");
}
// Regular function, method or lambda with parenthesis: take what is inside the first pair.
let start = header.indexOf("(");
if (start < 0) return "";
let end = header.indexOf(")", start);
if (end < 0) return "";
return header.substring(start + 1, end).replace(/\s+/g, " ").trim();
}
/**
* Extracts the context from the fact or rule name. Must be a compiled path, not a path that
* contains "../" or "./".
*
* @example
*
* Given the rule path: '/path/to/some/rule'
* The returned value would be: '/path/to/some'
*
* @param {String} compiledPath The compiled path to extract the context from.
*/
function getContext(compiledPath) {
return compiledPath.replace(trailingTermRemovalRegex, "");
}
/**
* Create a compacted fact name from a given fact name. Path do not end with a trailing '/'.
*
* '../' Denote the parent term. Move up one term in the context or path.
* './' Denote the current term. Removed from the context or path.
*
* Examples:
*
* '/a/given/../correct/./path' is compiled to '/a/correct/path'
*
* @param {String} path The path or context to compile into a context.
*/
function compilePath(path) {
let current = path;
let next = path.replace(pathCompactionRegex, "");
while (next != current) {
current = next;
next = current.replace(pathCompactionRegex, "");
}
if (current.startsWith("/..")) {
throw new Error(`Unable to compile the path '${path}' properly.`);
}
return current.replace(currentPathRemoval, "");
}