Here Save me
This commit is contained in:
commit
ee893faea9
6 changed files with 538 additions and 0 deletions
19
examples/playlist.tpl
Normal file
19
examples/playlist.tpl
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<v-playlist>
|
||||||
|
<v-header>
|
||||||
|
<h3>Playlist</h3>
|
||||||
|
{include(includes/input;type="search";id="playlist-search";classes="hid-input";label="Search";attributeString="data-tool='search'")}
|
||||||
|
</v-header>
|
||||||
|
<v-content class="empty">
|
||||||
|
Looks like nothing is here :(
|
||||||
|
{include(includes/button-icon;icon="upload";classes="upload hid-item";type="primary";attributeString="data-tool='upload'")}
|
||||||
|
</v-content>
|
||||||
|
<v-footer id="pagination">
|
||||||
|
<span class="page-current">0 / 0</span>
|
||||||
|
<span class="page-prev">
|
||||||
|
{include(includes/button-icon;icon="chevron-left";classes="hid-item";type="primary";attributeString="data-tool='pagination' data-value='-1'")}
|
||||||
|
</span>
|
||||||
|
<span class="page-next">
|
||||||
|
{include(includes/button-icon;icon="chevron-right";classes="hid-item";type="primary";attributeString="data-tool='pagination' data-value='1'")}
|
||||||
|
</span>
|
||||||
|
</v-footer>
|
||||||
|
</v-playlist>
|
9
examples/select.tpl
Normal file
9
examples/select.tpl
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<v-select ${required ? required} ${multiple ?? multiple} name="${id}" class="${classes}" data-value="${value}" data-bind="${bind}" ${attributeString}>
|
||||||
|
<v-label empty="${name}"></v-label>
|
||||||
|
<v-options>
|
||||||
|
{foreach(options as item)}
|
||||||
|
// VTepL has a getEqual hack! == check variable 1 and 2
|
||||||
|
<v-option value="${item.value}">${item.name}</v-option>
|
||||||
|
{/for}
|
||||||
|
</v-options>
|
||||||
|
</v-select>
|
56
src/Core.js
Normal file
56
src/Core.js
Normal file
|
@ -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;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
220
src/Interpreter.js
Normal file
220
src/Interpreter.js
Normal file
|
@ -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 : '';
|
||||||
|
}
|
||||||
|
}
|
208
src/Parser.js
Normal file
208
src/Parser.js
Normal file
|
@ -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('<!--', i, true)) {
|
||||||
|
self.extract('-->', 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
26
src/Template.js
Normal file
26
src/Template.js
Normal file
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue