php-htx-cli
Version:
A cli to convert .htx (different components) files to .php
313 lines (312 loc) • 15 kB
JavaScript
;
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;