factory-engine
Version:
Template Engine
173 lines (143 loc) • 5.46 kB
JavaScript
const APP_ROOT = require("app-root-path") + "/";
const XRegExp = require("xregexp");
const FS = require("fs");
async function parseFile(file, vars) {
return await parse(FS.readFileSync(PATH.normalize(APP_ROOT + file)).toString(), vars);
}
async function parse(content, vars) {
const COMMAND_BLOCKS = XRegExp.matchRecursive(content, '<<', '>>', "gs", {
valueNames: ["outside", "left", "inside", "right"],
unbalanced: 'skip'
});
let start = 0, end = 0, inside = undefined;
let modifier = 0;
for(const I in COMMAND_BLOCKS) {
const PROPERTY = COMMAND_BLOCKS[I];
if(PROPERTY.name === "left") start = PROPERTY.start;
if(PROPERTY.name === "inside") inside = PROPERTY.value;
if(PROPERTY.name === "right") {
end = PROPERTY.end;
const FORM = await format(inside, vars);
const REPLACEMENT = (await parseCommands(FORM[1], FORM[0], vars)).join("");
content = replaceAt(content, start + modifier, end + modifier, REPLACEMENT);
modifier += REPLACEMENT.length - inside.length - 4;
inside = undefined;
}
}
content = await replaceAsync(content, /{{\s*ovar\s*:\s*(\w+?)\s*}}/g, (full, arg) => {
return !vars[arg] ? "" : vars[arg]
});
return content;
}
const COMMAND_REGEX = /^(?<type>[A-Z]+):(?<content>.*)/s;
const PARSE_TYPE = async (command, files, vars) => {
const ARR = COMMAND_REGEX.exec(command);
if(ARR.groups.type == "FILE") {
const PARSED = parseInt(ARR.groups.content);
if(isNaN(PARSED)) return await parseFile(ARR.groups.content, vars);
const FILE = files[PARSED];
const VARS = {
...vars,
...await parseFileCommands(FILE.ivars, files, vars)
};
return await parseFile(FILE.file, VARS);
}
else if(ARR.groups.type == "PUT") return await parse(ARR.groups.content, vars);
return command;
}
async function parseCommands(commands, files, vars) {
for (let i = 0; i < commands.length; i++) {
if(!commands[i]) {
commands[i] = "";
continue;
}
if(commands[i].constructor === Array) {
commands[i] = await parseCommands(commands[i], files, vars);
continue;
}
commands[i] = await PARSE_TYPE(commands[i], files, vars);
}
return commands;
}
async function parseFileCommands(commands, files, vars) {
for(k in commands) {
if(!commands[k]) {
commands[k] = "";
continue;
}
commands[k] = await PARSE_TYPE(commands[k], files, vars);
}
return commands;
}
async function format(code, vars, files={}) {
const TMP_COMMANDS = [];
let FILES = files;
const TMP_INDEXES = [];
const TO_PASS = [];
code = await replaceAsync(code, /<(#|-).*?-?#>/gs, () => { return "" });
const RULES = [
[/IF\s*\((?<condition>.*?)\)\s*:\s*\{(?<code1>.*?)\}!(\s*ELSE\s*\{(?<code2>.*?)\}!)?/gs, "condition"],
[/((\$(?<id>[0-9]+))\s*=\s*)?INSERT\s+(?<type>ovar|file|block)(\(.*?\))?\s*:\s*(?<value>.+?)\s*;/gs, "insert"],
[/SET\s+(\$(?<id>[0-9]+)::)?(?<inside_var>.*)\s+BY\s+block(\(.*?\))?:\{(?<content>.*)\}/gs, "reb"],
[/SET\s+(\$(?<id>[0-9]+)::)?(?<inside_var>.*)\s+BY\s+file:\'(?<file>.*?)\'\s*;/g, "ref"],
[/SET\s+(\$(?<id>[0-9]+)::)?(?<inside_var>.*)\s+BY\s+ovar:(?<ovar>.*?)\s*;/g, "reo"],
[/EXECUTE\s*\(\s*(?<type>req|file|id):'(?<type_value>.*?)'\)\s*(?<tpl>\s+WITH\s+(?<tpl_type>file|block(\(.*?\))?):(?<tpl_value>'(.*?)';|\{.*\})|;)/gs, "execute"],
];
for (let i = 0; i < RULES.length; i++) {
let array;
while((array = RULES[i][0].exec(code)) !== null) {
if(isDuplication(TO_PASS, array.index)) continue;
const INDEX = setIndex(array.index, TMP_INDEXES);
TO_PASS.push({
index: array.index,
size: array[0].length
});
TMP_INDEXES.insert(INDEX, array.index);
const PARSED = await require("./syntax/" + RULES[i][1] + ".js").parse(array, vars, FILES);
if (!FILES[PARSED.id]) FILES[PARSED.id] = {
file: undefined,
ivars: {}
};
if(PARSED.merge) FILES = JSON.merge(FILES, PARSED.merge);
if(PARSED.to) FILES[PARSED.to].ivars[PARSED.key] = PARSED.value;
if(PARSED.file) FILES[PARSED.id].file = PARSED.file;
if(PARSED.ivars.length !== 0) FILES[PARSED.id].ivars = JSON.merge(FILES[PARSED.id].ivars, PARSED.ivars);
TMP_COMMANDS.insert(INDEX, PARSED.result);
}
}
return [FILES, TMP_COMMANDS];
}
function isDuplication(array, index) {
for (let i = 0; i < array.length; i++) {
if(index >= array[i].index && index <= array[i].index + array[i].size) return true;
}
return false;
}
function setIndex(index, indexes) {
let i = 0;
while(i != indexes.length) {
if(indexes[i + 1]) {
if(indexes[i] <= index) return i + 1;
else return i;
} else if(indexes[i] <= index && indexes[i + 1] >= index) return i + 1;
i++;
}
return i;
}
async function replaceAsync(str, regex, async_callback) {
const PROMISES = [];
str.replace(regex, (match, ...args) => {
const PROMISE = async_callback(match, ...args);
PROMISES.push(PROMISE);
});
const DATA = await Promise.all(PROMISES);
return str.replace(regex, () => DATA.shift());
}
function replaceAt(str, start, end, replacement) {
return str.substring(0, start) + replacement + str.substring(start + end - start);
}
module.exports.onDBRequest = async () => {};
module.exports.parse = parse;
module.exports.parseFile = parseFile;
module.exports.format = format;
module.exports.APP_ROOT = APP_ROOT;