From f8b61b80b62d107f72be35034a2e3cdf794a52b0 Mon Sep 17 00:00:00 2001 From: lumijiez <59575049+lumijiez@users.noreply.github.com> Date: Thu, 29 May 2025 12:01:30 +0300 Subject: [PATCH] Engine base --- .gitignore | 7 +++ inkject.js | 155 ++++++++++++++++++++++++++++++++++++++++++++++++++- package.json | 7 ++- 3 files changed, 165 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index e69de29..e9cb831 100644 --- a/.gitignore +++ b/.gitignore @@ -0,0 +1,7 @@ +/.idea/modules.xml +/.idea/inspectionProfiles/Project_Default.xml +/.idea/templeton.iml +/.idea/vcs.xml +/.idea/workspace.xml +/.idea/jsLibraryMappings.xml +/.idea/.gitignore diff --git a/inkject.js b/inkject.js index 04bf26e..01f1f92 100644 --- a/inkject.js +++ b/inkject.js @@ -1 +1,154 @@ -console.log('Happy developing ✨') +class inkject { + render(template, data = {}, delimiter = '&&') { + const [openDelim, closeDelim] = this.parseDelimiter(delimiter); + const escapeRegex = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + const openPattern = escapeRegex(openDelim); + const closePattern = escapeRegex(closeDelim); + + let result = template; + result = this.processConditionals(result, data, openPattern, closePattern); + result = this.processVariables(result, data, openPattern, closePattern); + + return result; + } + + parseDelimiter(delimiter) { + const len = delimiter.length; + if (len % 2 === 0) { + const mid = len / 2; + return [delimiter.slice(0, mid), delimiter.slice(mid)]; + } else { + return [delimiter, delimiter]; + } + } + + processConditionals(template, data, openPattern, closePattern) { + const conditionalRegex = new RegExp( + `${openPattern}#if\\s+([^${closePattern.charAt(0)}]+?)${closePattern}([\\s\\S]*?)(?:${openPattern}#elseif\\s+([^${closePattern.charAt(0)}]+?)${closePattern}([\\s\\S]*?))*(?:${openPattern}#else${closePattern}([\\s\\S]*?))?${openPattern}/if${closePattern}`, + 'g' + ); + + return template.replace(conditionalRegex, (fullMatch) => { + return this.processComplexConditional(fullMatch, data, openPattern, closePattern); + }); + } + + processComplexConditional(match, data, openPattern, closePattern) { + const blocks = []; + const ifMatch = new RegExp(`${openPattern}#if\\s+([^${closePattern.charAt(0)}]+?)${closePattern}`).exec(match); + if (ifMatch) { + blocks.push({ type: 'if', condition: ifMatch[1].trim() }); + } + + const elseifRegex = new RegExp(`${openPattern}#elseif\\s+([^${closePattern.charAt(0)}]+?)${closePattern}`, 'g'); + let elseifMatch; + while ((elseifMatch = elseifRegex.exec(match)) !== null) { + blocks.push({ type: 'elseif', condition: elseifMatch[1].trim() }); + } + + if (match.includes(`${this.unescapeRegex(openPattern)}#else${this.unescapeRegex(closePattern)}`)) { + blocks.push({ type: 'else' }); + } + + const content = this.extractConditionalContent(match, openPattern, closePattern, blocks); + + for (let i = 0; i < blocks.length; i++) { + const block = blocks[i]; + if (block.type === 'else' || this.evaluateCondition(block.condition, data)) { + return content[i] || ''; + } + } + + return ''; + } + + extractConditionalContent(match, openPattern, closePattern, blocks) { + const unescapedOpen = this.unescapeRegex(openPattern); + const unescapedClose = this.unescapeRegex(closePattern); + + const parts = match.split(new RegExp(`${openPattern}(?:#(?:if|elseif|else)|/if)(?:\\s+[^${closePattern.charAt(0)}]*?)?${closePattern}`)); + return parts.slice(1, -1); + } + + unescapeRegex(str) { + return str.replace(/\\(.)/g, '$1'); + } + + processVariables(template, data, openPattern, closePattern) { + const varRegex = new RegExp(`${openPattern}([^#/][^${closePattern.charAt(0)}]*?)${closePattern}`, 'g'); + + return template.replace(varRegex, (match, variable) => { + const value = this.getValue(variable.trim(), data); + return value !== undefined ? String(value) : ''; + }); + } + + evaluateCondition(condition, data) { + if (!condition) return false; + + condition = condition.trim(); + + if (condition.startsWith('!')) { + return !this.evaluateCondition(condition.slice(1), data); + } + + const operators = ['===', '!==', '==', '!=', '>=', '<=', '>', '<']; + for (const op of operators) { + if (condition.includes(op)) { + const [left, right] = condition.split(op).map(s => s.trim()); + const leftValue = this.getValue(left, data); + const rightValue = this.parseValue(right); + + switch (op) { + case '===': return leftValue === rightValue; + case '!==': return leftValue !== rightValue; + case '==': return leftValue == rightValue; + case '!=': return leftValue != rightValue; + case '>=': return leftValue >= rightValue; + case '<=': return leftValue <= rightValue; + case '>': return leftValue > rightValue; + case '<': return leftValue < rightValue; + } + } + } + + const value = this.getValue(condition, data); + return Boolean(value); + } + + getValue(path, data) { + const keys = path.split('.'); + let current = data; + + for (const key of keys) { + if (current === null || current === undefined) { + return undefined; + } + current = current[key]; + } + + return current; + } + + parseValue(value) { + value = value.trim(); + + if ((value.startsWith('"') && value.endsWith('"')) || + (value.startsWith("'") && value.endsWith("'"))) { + return value.slice(1, -1); + } + + if (!isNaN(value) && !isNaN(parseFloat(value)) && value !== '') { + return parseFloat(value); + } + + if (value === 'true') return true; + if (value === 'false') return false; + if (value === 'null') return null; + if (value === 'undefined') return undefined; + + return value; + } +} + +module.exports = inkject; \ No newline at end of file diff --git a/package.json b/package.json index 20c7ef7..a281512 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,11 @@ { - "name": "${PROJECT_NAME}", + "name": "inkject", "version": "1.0.0", "description": "", - "main": "index.js", + "main": "inkject.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, - "private": true + "keywords": ["template", "engine", "templating", "render", "conditional"], + "private": false }