UNPKG

php-htx-cli

Version:

A cli to convert .htx (different components) files to .php

313 lines (312 loc) 15 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.convertHtx = void 0; const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const regex_1 = __importDefault(require("./regex")); const parse_1 = require("./parse"); const scopes_1 = require("./scopes"); const tagDetection_1 = require("./tagDetection"); function usesFromImportSyntax(importParamaters) { return importParamaters.length >= 3 && importParamaters[1] == "from"; } function parseImportStatement(match, config) { let importParamaters = match.slice("<!--".length, -"-->".length).trim().slice("import ".length).split(" "); if (usesFromImportSyntax(importParamaters)) { return { name: importParamaters[0], file: importParamaters.slice(2).join(" ").slice(1, -1) + "." + config.extension.src, fileNameWithoutExtension: importParamaters.slice(2).join(" ").slice(1, -1) }; } let fileNameWithoutExtension = importParamaters.join(" ").slice(1, -1); return { name: path_1.default.basename(fileNameWithoutExtension), file: fileNameWithoutExtension + "." + config.extension.src, fileNameWithoutExtension: fileNameWithoutExtension }; } function convert(code, uid, props, children = "", config, filePath, parentComponentName, parentUid, isRoot = false) { const localComponentName = path_1.default.basename(filePath, "." + config.extension.src); const localUid = uid; let components = {}; let componentChildStack = [{ component: null, attr: {}, children: "" }]; const addToOutput = (...content) => { componentChildStack[componentChildStack.length - 1].children += content.join(""); }; let localFunctions = []; let possibleScopedCssFiles = new Set(); let possibleScopedJsFiles = new Set(); let manualScopedCssFiles = new Set(); let manualScopedJsFiles = new Set(); let isInPhp = false; let isInString = false; let skipNext = false; for (let charIndex = 0; charIndex < code.length; charIndex++) { let match; // handle php open and close if ((match = code.slice(charIndex).match(regex_1.default.php.open)) != null && isInString == false) { isInPhp = true; addToOutput(match[0]); charIndex += match[0].length - 1; continue; } if ((match = code.slice(charIndex).match(regex_1.default.php.close)) != null && isInString == false) { isInPhp = false; addToOutput(match[0]); charIndex += match[0].length - 1; continue; } // hanlde php string if (isInPhp && code[charIndex] == "\"" && !isInString) { isInString = "\""; addToOutput("\""); continue; } if (isInPhp && code[charIndex] == "\"" && isInString == "\"" && !skipNext) { isInString = false; addToOutput("\""); continue; } if (isInPhp && code[charIndex] == "'" && !isInString && !skipNext) { isInString = "'"; addToOutput("'"); continue; } if (isInPhp && code[charIndex] == "'" && isInString == "'" && !skipNext) { isInString = false; addToOutput("'"); continue; } // import statement if ((match = code.slice(charIndex).match(regex_1.default.importStatement)) != null && !isInPhp) { let { name: componentName, file: componentFileName, fileNameWithoutExtension } = parseImportStatement(match[0], config); const fullPathToFileBase = path_1.default.join(path_1.default.dirname(path_1.default.isAbsolute(filePath) ? filePath : path_1.default.join(process.cwd(), filePath)), fileNameWithoutExtension); components[componentName] = { relativePath: path_1.default.relative(process.cwd(), fullPathToFileBase), contents: fs_1.default.readFileSync(fullPathToFileBase + "." + config.extension.src, 'utf8') }; possibleScopedCssFiles.add(fullPathToFileBase + ".css"); possibleScopedJsFiles.add(fullPathToFileBase + ".js"); charIndex += match[0].length - 1; continue; } // Component children if ((match = code.slice(charIndex).match(regex_1.default.componentTag.children)) != null && !isInPhp) { let childrenString = ""; if (config.environment == "dev") { childrenString += `\n<!-- DEBUG: Children start ${localComponentName} #${localUid} -->\n`; } childrenString += children; if (config.environment == "dev") { childrenString += `\n<!-- DEBUG: Children end ${localComponentName} #${localUid} -->\n`; } addToOutput(childrenString); charIndex += match[0].length - 1; continue; } // Component scopes if ((match = code.slice(charIndex).match(regex_1.default.componentTag.scopes)) != null && !isInPhp) { if (config.environment == "dev") addToOutput("\n<!-- DEBUG: Start scopes -->\n"); addToOutput(match[0]); if (config.environment == "dev") addToOutput("\n<!-- DEBUG: End scopes -->\n"); charIndex += match[0].length - 1; continue; } // Component scoped JS if ((match = (0, tagDetection_1.isTag)("/>", code, charIndex)) != false && match.tagname == "JS" && !isInPhp) { manualScopedJsFiles.add(match.attr.src); charIndex += match.size - 1; continue; } // TODO Component scoped CSS if ((match = (0, tagDetection_1.isTag)("/>", code, charIndex)) != false && match.tagname == "CSS" && !isInPhp) { manualScopedCssFiles.add(match.attr.src); charIndex += match.size - 1; continue; } // Component open if ((match = (0, tagDetection_1.isTag)(">", code, charIndex)) != false && !isInPhp) { componentChildStack.push({ component: match.tagname, attr: match.attr, children: "" }); charIndex += match.size - 1; continue; } // Component close if ((match = code.slice(charIndex).match(regex_1.default.componentTag.close)) != null && !isInPhp) { let currentChildStackContent = componentChildStack.pop(); uid++; if ((currentChildStackContent === null || currentChildStackContent === void 0 ? void 0 : currentChildStackContent.component) == null) throw new Error(`Cannot access component without a name`); if (!((currentChildStackContent === null || currentChildStackContent === void 0 ? void 0 : currentChildStackContent.component) in components)) throw new Error(`Unknown component "${currentChildStackContent === null || currentChildStackContent === void 0 ? void 0 : currentChildStackContent.component}"`); let { output: converted, uid: newUid, possibleScopes, manualScopes } = convert(components[currentChildStackContent === null || currentChildStackContent === void 0 ? void 0 : currentChildStackContent.component].contents, uid, currentChildStackContent === null || currentChildStackContent === void 0 ? void 0 : currentChildStackContent.attr, currentChildStackContent === null || currentChildStackContent === void 0 ? void 0 : currentChildStackContent.children, config, components[currentChildStackContent === null || currentChildStackContent === void 0 ? void 0 : currentChildStackContent.component].relativePath, localComponentName, localUid); possibleScopes.css.forEach(scope => { possibleScopedCssFiles.add(scope); }); possibleScopes.js.forEach(scope => { possibleScopedJsFiles.add(scope); }); manualScopes.css.forEach(scope => { manualScopedCssFiles.add(scope); }); manualScopes.js.forEach(scope => { manualScopedJsFiles.add(scope); }); uid = newUid; addToOutput(converted); charIndex += match[0].length - 1; continue; } // Component single if ((match = (0, tagDetection_1.isTag)("/>", code, charIndex)) != false && !isInPhp) { uid++; if (!(match.tagname in components)) { throw new Error(`Unknown component "${match.tagname}" in file "${filePath}"`); } let { output: converted, uid: newUid, possibleScopes, manualScopes } = convert(components[match.tagname].contents, uid, match.attr, "", config, components[match.tagname].relativePath, localComponentName, localUid); possibleScopes.css.forEach(scope => { possibleScopedCssFiles.add(scope); }); possibleScopes.js.forEach(scope => { possibleScopedJsFiles.add(scope); }); manualScopes.css.forEach(scope => { manualScopedCssFiles.add(scope); }); manualScopes.js.forEach(scope => { manualScopedJsFiles.add(scope); }); uid = newUid; addToOutput(converted); charIndex += match.size - 1; continue; } // Global variable if ((match = code.slice(charIndex).match(regex_1.default.variable.global)) != null && isInPhp && (isInString == false || skipNext == false)) { addToOutput("$" + match[0].slice("$GLOBAL_".length)); charIndex += match[0].length - 1; continue; } // Local variable if ((match = code.slice(charIndex).match(regex_1.default.variable.local)) != null && isInPhp && (isInString == false || skipNext == false)) { addToOutput("$" + config.constant.variable .replace("<component>", localComponentName) .replace("<uid>", localUid.toString()) .replace("<variable>", match[0].slice(1))); charIndex += match[0].length - 1; continue; } // Create local function if ((match = code.slice(charIndex).match(regex_1.default.function.define)) != null && isInPhp && isInString == false) { const functionName = match[0].slice("function ".length, -1).trim(); if (functionName.startsWith("GLOBAL_")) { addToOutput(`function ${functionName.slice("GLOBAL_".length)}(`); charIndex += match[0].length - 1; continue; } localFunctions.push(functionName); addToOutput(`function ${config.constant.variable .replace("<component>", localComponentName) .replace("<uid>", localUid.toString()) .replace("<variable>", functionName)}(`); charIndex += match[0].length - 1; continue; } if ((match = code.slice(charIndex).match(regex_1.default.function.call)) != null && isInPhp && isInString == false) { const functionName = match[0].slice(0, -1).trim(); if ((!functionName.startsWith("GLOBAL_")) && localFunctions.includes(functionName)) { addToOutput(config.constant.variable .replace("<component>", localComponentName) .replace("<uid>", localUid.toString()) .replace("<variable>", functionName) + "("); } else if (functionName.startsWith("GLOBAL_")) { addToOutput(functionName.slice("GLOBAL_".length) + "("); } else { addToOutput(functionName + "("); } charIndex += match[0].length - 1; continue; } if (isInPhp && isInString != false && code[charIndex] == "\\") { skipNext = true; addToOutput("\\"); continue; } else { skipNext = false; } addToOutput(code[charIndex]); } // auto close php at end of file if (isInString && isInPhp) { addToOutput(`";?>`); } else if (isInPhp) { addToOutput("?>"); } if (isRoot) { possibleScopedCssFiles.add(filePath.slice(0, -(("." + config.extension.src).length)) + ".css"); possibleScopedJsFiles.add(filePath.slice(0, -(("." + config.extension.src).length)) + ".js"); } const manualFileToScope = function (manualFile, scopeType) { return { type: scopeType, remote: true, path: manualFile }; }; let existingScopes; if (isRoot) { existingScopes = (0, scopes_1.checkForExistingScopes)(possibleScopedCssFiles, possibleScopedJsFiles); existingScopes === null || existingScopes === void 0 ? void 0 : existingScopes.push(...Array.from(manualScopedCssFiles) .map(manualFile => manualFileToScope(manualFile, "css"))); existingScopes === null || existingScopes === void 0 ? void 0 : existingScopes.push(...Array.from(manualScopedJsFiles) .map(manualFile => manualFileToScope(manualFile, "js"))); } let finalOutput = (config.environment == "dev") ? `\n<!-- DEBUG: Start ${localComponentName} #${localUid} -->\n` : ""; // add props variable finalOutput += `<?php ${"$" + config.constant.variable .replace("<component>", localComponentName) .replace("<uid>", localUid.toString()) .replace("<variable>", config.constant.props)} = ${(0, parse_1.toPhpFormat)(props, parentComponentName, parentUid, config)}; ?>`; // handle scopes (only in root, because it has to be called once and contain all local scripts and styles) finalOutput += isRoot ? (0, scopes_1.handleScopes)(componentChildStack[0].children, existingScopes, config.environment == "dev") : componentChildStack[0].children; if (config.environment == "dev") { finalOutput += `\n<!-- DEBUG: End ${localComponentName} #${localUid} -->\n`; } return { output: finalOutput, uid, possibleScopes: { css: possibleScopedCssFiles, js: possibleScopedJsFiles }, manualScopes: { css: manualScopedCssFiles, js: manualScopedJsFiles } }; } function removeEmptyLines(line) { return line.replace(/\r/g, "").trim() != ""; } function convertHtx(contents, filePath, config) { const converted = convert(contents, 0, {}, "", config, filePath, "", -1, true).output; return converted.split("\n").filter(removeEmptyLines).join("\n"); } exports.convertHtx = convertHtx;