UNPKG

@warp-works/core

Version:

Core library for WarpWorks

369 lines (315 loc) 12.1 kB
// const debug = require('debug')('W2:models:base'); const fs = require('fs'); const path = require('path'); const Promise = require('bluebird'); // BasicTypes is used in the eval()! // eslint-disable-next-line no-unused-vars const BasicTypes = require('./../basic-types'); const ComplexTypes = require('./../complex-types'); const config = require('./../config'); const utils = require('./../utils'); function isValidName(name) { return name && !/\W/i.test(name) && name.length > 1; } class Base { constructor(type, parent, id, name, desc) { // Set basic attributes first (needed for validation below) this.type = type; this.parent = parent; this.id = id; this.name = name ? name.replace(/ /g, '') : name; // Remove whitespaces this.desc = desc; // Validate name if (!isValidName(this.name)) { throw new Error("Invalid name: '" + name + "'. Please use only a-z, A-Z, 0-9 or _!"); } /* TBD: Decide if we want to enforce unique names within a domain? var duplicate = this.getDomain().findElementByName(name); if (duplicate && duplicate != this) throw "Error creating element of type '"+type+"'! Name '"+name+"' already used by element of type '"+duplicate.type+"' in same domain!"; */ // This is the top-level element; it will be resolved dynamically this.warpworks = (parent && parent.constructor.name === 'WarpWorks') ? parent : null; } getWarpWorks() { if (!this.warpworks) { this.warpworks = this.parent.getWarpWorks(); } return this.warpworks; } getDomain() { if (this.type !== ComplexTypes.Domain) { return this.parent.getDomain(); } return this; } isOfType(t) { return this.type === t; } compareToMyID(id) { return this.getDomain().compareIDs(this.id, id); } idToJSON() { return this.id; } findElementByID(id) { var allElems = this.getAllElements(true); for (var i in allElems) { if (this.getDomain().compareIDs(id, allElems[i].id)) { var r = allElems[i]; return r; } } return null; } findElementByName(name, type) { var allElems = this.getAllElements(true); for (var i in allElems) { if ((allElems[i].name === name) && (!type || allElems[i].type === type)) { return allElems[i]; } } return null; } getParent(persistence, instance) { return Promise.resolve() .then(() => this.getDomain().getParentEntityByParentBaseClassName(instance)) .then((parentEntity) => { if (parentEntity) { return parentEntity.getInstance(persistence, instance.parentID) .then((parentInstance) => { return { entity: parentEntity, instance: parentInstance }; }); } return { entity: parentEntity, instance: null }; }); } getPath() { return (this.parent ? this.parent.getPath() : '') + `/${this.name}`; } getInstancePath(persistence, instance) { return Promise.resolve() .then(() => this.getParent(persistence, instance)) .then((parent) => { if (parent && parent.instance) { return parent.entity.getInstancePath(persistence, parent.instance); } return []; }) .then((ancestors) => { return ancestors.concat({ type: instance.type, id: instance.id, name: instance.Name || instance.type }); }); } _topNonAbstractClassName() { return (this.getBaseClass) ? this.getBaseClass().name : this.name; } getInstance(persistence, id) { const name = this._topNonAbstractClassName(this); return persistence.findOne(name, id, true); } getDocuments(persistence, query, withCount) { const name = this._topNonAbstractClassName(this); return persistence.documents(name, query, withCount); } getDocumentsLimit(persistence, withCount) { const name = this._topNonAbstractClassName(this); return persistence.documentsLimit(name, withCount); } /** * Gets all children of the given parent. * * @param {objec} persistence: Persistance instance. * @param {string|object} parentData: Search criteria. If a string, it's * expected to be the `parentID`. * @returns {Promise} The list of documents for a given parent. */ getChildren(persistence, parentData) { if (typeof parentData === 'string') { return persistence.documents(this.name, { parentID: parentData }, true); } return persistence.documents(this.name, parentData, true); } // // Template Processing // processLocalTemplateFunctions(template) { // Evaluate "condition" in "{{Options}}" if (!this.evaluateTemplateCondition(template)) { return null; } // Process if/then/else tags template = this.processIfThenElse(template); // Execute scriptlets: template = this.processScripts(template); // Perform actions in options, e.g. "saveAs" template = this.processOptions(template); return template; } processTemplateWithChildElements(template, children) { var i; for (var idx in children) { var child = children[idx]; var type = child[0]; var elements = child[1]; var beginTag = utils.createBeginTag(type); var endTag = utils.createEndTag(type); var tokenSeq = utils.getTokenSeq(template, beginTag, endTag); for (i in tokenSeq) { if (tokenSeq[i].isTagValue) { var ts = ""; var itemPos = 0; var hasExecutedAtLeastOnce = false; if (elements) { elements.forEach(function(childElem) { // Declare some temporary variables that can be used in the template`s java scriptlets: childElem.itemPos = itemPos; childElem.itemIsFirst = itemPos === 0; // Apply template to child element: var res = childElem.processLocalTemplateFunctions(tokenSeq[i].value); if (res) { ts += res; itemPos++; hasExecutedAtLeastOnce = true; } // And now remove temporary variables: delete childElem.itemPos; delete childElem.itemIsFirst; }); } tokenSeq[i].value = ts; } } // Re-construct template from token sequence template = ""; for (i in tokenSeq) { template += tokenSeq[i].value; } // If no elements available, remove conditional tags, e.g. {{Entity?}} ... {{/Entity?}} template = this.processConditionalTagValues(template, type, !hasExecutedAtLeastOnce); } return template; } evaluateTemplateCondition(template) { if (!template.includes("{{Options}}")) { return true; } var a = utils.extractTagValue(template, "{{Options}}", "{{/Options}}"); var options = JSON.parse(a[1]); if (!options.condition) { return true; } return this.evalWithContext(options.condition); } processOptions(template) { if (template.includes("{{Options}}")) { var a = utils.extractTagValue(template, "{{Options}}", "{{/Options}}"); // Re-construct template template = a[0] + a[2]; if (template.includes("{{Options}}")) { throw new Error("{{Options}} should only be included once!"); } var options = JSON.parse(a[1]); if (options.saveAs) { var target = options.saveAs; this.saveTemplateResults(template, target); } } return template; } processIfThenElse(template) { while (template.includes("{{if}}")) { var beforeIteAfter = utils.extractTagValue(template, "{{if}}", "{{/if}}"); var ifthenelse = null; if (beforeIteAfter[1].includes("{{else}}")) { ifthenelse = utils.extractTagValue(beforeIteAfter[1], "{{then}}", "{{else}}"); } else { ifthenelse = utils.splitBySeparator(beforeIteAfter[1], "{{then}}"); } var ifexp = ifthenelse[0]; var condition = this.evalWithContext(ifexp); var res = ""; if (condition) { // keep the 'if' part res = ifthenelse[1]; } if (!condition && ifthenelse.length === 3) { // else was defined, keep else part res = ifthenelse[2]; } template = beforeIteAfter[0] + res + beforeIteAfter[2]; } return template; } processConditionalTagValues(template, type, removeContent) { var begin = utils.createBeginTag(type, true); var end = utils.createEndTag(type, true); while (template.includes(begin)) { var res = utils.extractTagValue(template, begin, end); if (res.length !== 3) { throw new Error("Error"); } if (removeContent) { // Remove content template = res[0] + res[2]; } else { // Return everything (minus the tags) template = res[0] + res[1] + res[2]; } } return template; } processScripts(template) { if (template === null) { throw new Error("Internal error"); } var begin = "{{js:"; var end = "/}}"; while (template.includes(begin)) { var tokens = utils.extractTagValue(template, begin, end); // var pre = tokens[0]; var script = tokens[1]; // var post = tokens[2]; try { var scriptResult = this.evalWithContext(script); } catch (err) { console.log("While processing scriptlet in template:"); console.log("Element level:" + this.name); console.log("Scriptlet:" + tokens); console.log("Stack:", err.stack); } template = template.replace(begin + script + end, scriptResult); } return template; } evalWithContext(js) { // Return the results of the in-line anonymous function we call with the passed context return (function() { // eslint-disable-next-line no-eval return eval(js); }.call(this)); } saveTemplateResults(result, target) { if (!target || target.length !== 2) { throw new Error("Invalid target in 'onSave'-option!"); } var fn = this.evalWithContext(target[1]); if (target[0] === 'public') { fn = path.join(process.cwd(), '..', '..', 'public', 'WarpWorks', fn); } else { fn = path.join(config.projectPath, target[0], fn); } fs.writeFileSync(fn, result); } } module.exports = Base;