commit ee893faea9fdc906abc0b3bd008eeac186a72f2b Author: versustunez Date: Thu Oct 28 21:36:21 2021 +0200 Here Save me diff --git a/examples/playlist.tpl b/examples/playlist.tpl new file mode 100644 index 0000000..320f517 --- /dev/null +++ b/examples/playlist.tpl @@ -0,0 +1,19 @@ + + +

Playlist

+ {include(includes/input;type="search";id="playlist-search";classes="hid-input";label="Search";attributeString="data-tool='search'")} +
+ + Looks like nothing is here :( + {include(includes/button-icon;icon="upload";classes="upload hid-item";type="primary";attributeString="data-tool='upload'")} + + + 0 / 0 + + {include(includes/button-icon;icon="chevron-left";classes="hid-item";type="primary";attributeString="data-tool='pagination' data-value='-1'")} + + + {include(includes/button-icon;icon="chevron-right";classes="hid-item";type="primary";attributeString="data-tool='pagination' data-value='1'")} + + +
diff --git a/examples/select.tpl b/examples/select.tpl new file mode 100644 index 0000000..993b857 --- /dev/null +++ b/examples/select.tpl @@ -0,0 +1,9 @@ + + + + {foreach(options as item)} + // VTepL has a getEqual hack! == check variable 1 and 2 + ${item.name} + {/for} + + diff --git a/src/Core.js b/src/Core.js new file mode 100644 index 0000000..d5c7941 --- /dev/null +++ b/src/Core.js @@ -0,0 +1,56 @@ +class VTepLCore { + constructor({path, suffix, template, cache} = {}) { + this.templates = {}; + this.dir = path || '/tpl/'; + this.suffix = suffix || 'tpl'; + this.path = template || `${this.dir}%s.${this.suffix}`; + this.cache = cache === undefined ? true : cache; + } + + // Add download queue! + async loadTemplate(name) { + if (this.templates[name]) { + return null; + } + this.templates[name] = true; // small hack to prevent double loading :) + let path = this.path.replace('%s', name); + let data = await Network.requestUrl(path) + this.addTpl(name, data); + loader.addLoadedItems(1); + return null; + } + + async loadArray(names) { + loader.addToLoadItem(names.length) + const promises = [] + for (const name of names) { + promises.push(this.loadTemplate(name)); + } + await Promise.all(promises) + } + + addTpl(name, content) { + let temp = this.templates[name] = new VTepLTemplate(name, content, this); + temp.parseContent(this.cache); + } + + async renderOn(name, data) { + if (this.templates[name]) { + return await this.templates[name].render(data); + } + PrettyConsole.warn("VTepLRender", `Template: "${name}" dont exist`); + return ''; + } + + renderTo(element, name, data = {}) { + this.renderOn(name, data).then(html => { + element.innerHTML = html; + }) + } + + async renderToAsync(element, name, data = {}) { + return this.renderOn(name, data).then(html => { + element.innerHTML = html; + }) + } +} diff --git a/src/Interpreter.js b/src/Interpreter.js new file mode 100644 index 0000000..bb2752e --- /dev/null +++ b/src/Interpreter.js @@ -0,0 +1,220 @@ +class VTepLInterpreter { + constructor(parser, core) { + this.parser = parser; + this.data = []; + this.content = ''; + this.core = core; + } + + async render(data) { + let self = this; + self.data = data; + let newData = await self.interpreter(self.parser.parsed); + self.data = []; + return newData[0]; + } + + // This stuff is slow as fuck xD Optimize parser to create faster interpreter + async interpreter(parsed, index = 0) { + let self = this; + let types = VParserTypes; + let tplCont = ''; + for (let i = index; i < parsed.length; i++) { + let item = parsed[i], + content = item.content; + switch (item.type) { + case types.content: + tplCont += content; + break; + case types.variable: + tplCont += self.getVariable(content) + break; + case types.assign: + let data = content.split("="), + key = data.shift(); + self.setVariable(data.join("=").trim().replace(/"/g, ""), key.trim()); + break; + case types.forEach: + let d = await this.handleForEach(item, parsed, i); + i = d[0]; + tplCont += d[1]; + break; + case types.for: + let fd = await this.handleFor(item, parsed, i); + i = fd[0]; + tplCont += fd[1]; + break; + case types.if: + let id = await this.handleIf(item, parsed, i); + i = id[0]; + tplCont += id[1]; + break; + case types.include: + tplCont += await this.handleInclude(item); + break; + case types.ifEnd: + tplCont += content; + return [tplCont, i]; + case types.forEnd: + tplCont += content; + return [tplCont, i]; + default: + console.warn("Invalid Type found"); + break; + } + } + return [tplCont, parsed.length]; + } + + getVariable(variable, isEqualCheck = false) { + variable = variable.toString(); + if (!isEqualCheck) { + let v = variable.split("=="); + if (v.length > 1) { + return this.getEqual(v[0].trim(), v[1].trim()); + } + v = variable.split("??"); + if (v.length > 1) { + return this.getBool(v[0].trim(), v[1].trim()); + } + v = variable.split("?"); + if (v.length > 1) { + return this.getIsDefined(v[0].trim(), v[1].trim()); + } + v = variable.split("||"); + if (v.length > 1) { + return this.getDefault(v[0].trim(), v[1].trim()); + } + } + if (this.data[variable]) { + return this.data[variable]; + } + let split = variable.split("."), + prevVar = this.data; + for (let i = 0; i < split.length; i++) { + prevVar = prevVar[split[i]] || prevVar; + } + if (typeof prevVar === 'string') { + return prevVar; + } + if (typeof prevVar === 'number') { + return prevVar.toString(); + } + if (typeof prevVar === 'boolean') { + return prevVar.toString(); + } + return ''; + } + + getEqual(variable1, variable2) { + let split = variable2.split("?"); + let var1 = this.getVariable(variable1, true); + let var2 = this.getVariable(split[0].trim(), true); + if (split.length > 1) { + let newSplit = split[1].split(":"); + let right = newSplit[1] || ''; + return var1 === var2 ? newSplit[0].trim() : right.trim(); + } + return var1 === var2 ? 'true' : 'false'; + } + + setVariable(value, variable) { + let c = this.getVariable(value); + if (c !== '') { + value = c; + } + this.data[variable] = value; + } + + async handleForEach(item, parsed, i) { + let content = item.content.split(" as "); + let root = this.getVariable(content[0].trim()); + let addTo = 0, + isInvalid = false; + if (root === '') { + isInvalid = true; + root = {invalid: "true"}; + } + if (typeof root === 'string') { + root = JSON.parse(root); + } + if (Array.isArray(root) && root.length === 0) { + root.push(""); + isInvalid = true; + } + let d = Object.keys(root), + raw = '', + varContent = content[1].trim().split(","); + for (let x of d) { + if (varContent.length === 2) { + this.setVariable(x, varContent[1]); + } + this.setVariable(root[x], varContent[0]); + let data = await this.interpreter(parsed, i + 1); + addTo = data[1]; + raw += data[0]; + } + if (isInvalid) { + raw = ''; + } + return [addTo, raw]; + } + + async handleInclude(item) { + let split = item.content.split(";"), + name = split.shift(), + data = {}; + await this.core.loadTemplate(name); + for (let item of split) { + let d = item.split("="), + index = d.shift(), + dat = d.join("="); + if (!dat.startsWith("\"") && !dat.startsWith("\'")) { + dat = this.getVariable(dat); + } else { + if (dat.startsWith("\"")) { + dat = dat.replace(/"/g, ""); + } else { + dat = dat.replace(/'/g, ""); + } + } + + data[index] = dat; + } + return await this.core.renderOn(name, data); + } + + async handleFor(item, parsed, ind) { + let content = item.content.split(" as "), + addTo = 0, + count = content[0].trim().split(".."), + max = parseInt(count[1]), + min = parseInt(count[0]), + newContent = ''; + for (let i = min; i < max; i++) { + this.setVariable(i.toString(), content[1]); + let data = await this.interpreter(parsed, ind + 1); + addTo = data[1]; + newContent += data[0]; + } + return [addTo, newContent]; + } + + async handleIf(item, parsed, i) { + let data = await this.interpreter(parsed, i + 1); + return [data[1], data[0]]; + } + + getIsDefined(variable, value) { + return this.getVariable(variable, true) !== '' ? value : ''; + } + + getDefault(variable, value) { + let vars = this.getVariable(variable, true); + return vars !== '' ? vars : value; + } + + getBool(variable, value) { + return this.getVariable(variable, true) ? value : ''; + } +} diff --git a/src/Parser.js b/src/Parser.js new file mode 100644 index 0000000..d2e977a --- /dev/null +++ b/src/Parser.js @@ -0,0 +1,208 @@ +const VParserTypes = { + content: 0, + variable: 1, + for: 2, + forEach: 3, + forContent: 4, + forEnd: 5, + if: 6, + ifContent: 7, + ifEnd: 8, + assign: 9, + include: 10, + none: -1, +}; + + +class VTpeLParser { + constructor(name, content) { + let self = this; + self.name = name; + self.legex = content.trim(); + self.index = 0; + self.content = ''; + self.parsed = []; + self.contexts = [0]; + self.line = 1; + self.charPos = 1; + } + + tokenize() { + let self = this; + for (self.index = 0; self.index < self.legex.length; self.index++) { + let i = self.index, + char = self.legex.charAt(i); + self.charPos++; + if (char === "\n") { + self.line++; + self.charPos = 0; + } + if (self.nextContains('/* ', i, true)) { + self.extract(' */', VParserTypes.none) + } else if (self.nextContains('// ', i, true)) { + self.extract("\n", VParserTypes.none); + } else if (self.nextContains('', VParserTypes.none); + } else if (self.nextContains('{for(', i, true)) { + self.extract(')}', VParserTypes.for); + self.contexts.push(VParserTypes.for); + } else if (self.nextContains('{include(', i, true)) { + self.extract(')}', VParserTypes.include); + self.contexts.push(VParserTypes.include); + } else if (self.nextContains('{foreach(', i, true)) { + self.extract(')}', VParserTypes.forEach); + self.contexts.push(VParserTypes.forEach); + } else if (self.nextContains('{/for}', i, true)) { + self.addType(VParserTypes.forEnd); + self.contexts.pop(); + } else if (self.nextContains('{if(', i, true)) { + self.extract(')}', VParserTypes.if); + self.contexts.push(VParserTypes.if); + } else if (self.nextContains('{/if}', i, true)) { + self.addType(VParserTypes.ifEnd); + self.contexts.pop(); + } else if (self.nextContains('$${', i, true)) { + self.extract('}', VParserTypes.assign); + } else if (self.nextContains('${', i, true)) { + self.extract('}', VParserTypes.variable); + } else { + self.content += char; + } + } + self.addType(VParserTypes.content); + return self.parsed; + } + + addType(type) { + let self = this; + let content = self.content.replace(/^\n+|\n+$/g, ''), + instructions = self.findInstructions(type); + self.content = ''; + if (type !== VParserTypes.none) { + if (type === VParserTypes.content && content === '') { + return null; + } + return self.parsed.push({ + content: content, + type: type, + context: self.contexts[self.contexts.length - 1], + instructions: instructions + }); + } + return null; + } + + nextContains(find, index, add = false) { + let count = this.nextContainsRaw(this.legex, find, index); + if (add && count > 0) { + this.index += count; + } + return count > 0 || count === -1; + } + + nextContainsRaw(raw, find, index) { + if (typeof find === "string") { + find = find.split(""); + } + let count = find.length; + if (count < 1) { + return -1; + } + for (let i = 0; i < count; i++) { + let nc = raw.charAt(index + i); + if ((find[i] === '\n' && nc === undefined)) { + return count; + } + if (find[i] !== nc) { + return 0; + } + } + return count; + } + + extract(findUntil = '}', type = 1) { + let self = this; + self.addType(0); + findUntil = findUntil.split("") + let content = '', + index = self.index, + legex = self.legex, + firstFind = findUntil.shift(); + for (let i = self.index; i < legex.length; i++) { + let char = legex.charAt(i); + if (char === firstFind && self.nextContains(findUntil, i + 1)) { + // PrettyConsole.debug("Parser", `[${index} > ${i}] >> ${content}`); + // console.debug(`[Parser][${index} > ${i}] >> ${content}`); + self.index = i + findUntil.length; + self.content = content.trim(); + self.addType(type); + return; + } + content += char; + } + if (firstFind === "\n") { + self.index = legex.length; + self.content = content.trim(); + self.addType(type); + return + } + throw `Template "${self.name}" Parsing Failed because variable at Position: ${index} <${self.line}:${self.charPos}> not closed!`; + } + + // @todo implement split operator Splitter + getOperators(string) { + let statements = []; + let innerCon = ''; + for (let i = 0; i < string.length; i++) { + let c = string.charAt(i); + if (innerCon === '' && c === ' ') { + continue; + } + if (c === '(') { + if (innerCon !== '') { + statements.push(this.parseOperator(innerCon)); + } + innerCon = ''; + for (let x = 1; x < string.length; x++) { + let char = string.charAt(i + x); + if (char === ')') { + i = i + x; + break; + } + innerCon += char; + } + statements.push(this.parseOperator(innerCon)); + innerCon = ''; + } else { + innerCon += c; + } + } + if (innerCon !== '') { + statements.push(this.parseOperator(innerCon)); + } + return statements; + } + + parseOperator(operatorString) { + return this.operator(operatorString); + } + + findInstructions(type) { + if (type === VParserTypes.if) { + return this.getOperators(this.content); + } + // @todo add support for assign, for, foreach and variables... can optimize interpreter times because we dont need to find it then + return []; + } + + // right needs to be a string or a operator and should not be null! + // left cant be null! this is the case if it's ! operator + operator(op, left, right) { + return { + type: op, + lvalue: left, + r: right + } + } + +} diff --git a/src/Template.js b/src/Template.js new file mode 100644 index 0000000..ef9772b --- /dev/null +++ b/src/Template.js @@ -0,0 +1,26 @@ +class VTepLTemplate { + constructor(name, content, core) { + this.name = name; + this.tpl = content; + this.parser = new VTpeLParser(name, content); + this.core = core; + } + + async render(data = {}) { + return await new VTepLInterpreter(this.parser, this.core).render(data); + } + + parseContent(cache) { + if (cache) { + let storage = localStorage.getItem("vtepl-" + this.name); + if (storage) { + this.parser.parsed = JSON.parse(storage); + return; + } + } + this.parser.tokenize(); + if (cache) { + localStorage.setItem("vtepl-" + this.name, JSON.stringify(this.parser.parsed)); + } + } +}