UNPKG

ocat-lang

Version:

A programming language for the web design and development

486 lines (443 loc) 17.9 kB
import { CustomError, ErrorType, Warning } from "../error"; import { Memory, Function, DCollection } from "../memory/"; import { print } from "../print/"; import { init, rund } from "../lang"; import { Node, NodeType } from "./parser"; import { getValue, readFile } from "./utils"; import { Route } from "./types"; import { process404, processCSS, processHTML, processLayout, processRoute, processRouteTemplate, } from "./adapters"; import express from "express"; import path from "path"; import fs from "fs"; import { exec } from "child_process"; import { config, code404, codeRouteTemplate, preJs, preStyles, globalStyles, defhead, } from "./constants"; import { View, viewAdapt, viewAdapter } from "./adapters/view"; import { processPath } from "./adapters/path"; const memory: Memory = new Memory(); const app = express(); export const getThemes = () => { if (fs.existsSync("./themes") && config.themes) { fs.readdirSync("./themes").forEach((file) => { const name = file.replace(".json", ""); const content = fs.readFileSync(`./themes/${file}`, "utf-8"); const properties = JSON.parse(content); memory.declareTheme(name, properties); }); } }; export const save = () => { getThemes(); const properties = config.properties; if (properties) { properties.provided.forEach((key: string) => { memory.addProperty(key); }); if (properties.defined) { Object.entries(properties.defined).forEach(([name, value]) => { memory.addProperty(name); memory.setOrder(name, value as string); }); } } if (config.collections) { Object.entries(config.collections).forEach(([name, value]) => { const folder = path.join("./collections", value as string); if (fs.existsSync(folder)) { const collectionItems: DCollection[] = []; const items = fs.readdirSync(folder, { withFileTypes: true }); const files = items.filter((item) => item.isFile()); files.forEach((file) => { const name = file.name; const params: { [key: string]: string } = {}; const content = fs .readFileSync(path.join(folder, name), "utf8") .replace( /---\s+(.*?)\s+---/gs, (_match: string, paramContent: string) => { const paramMatch = /(.*?)="(.*?)"/gs; const matchs = paramContent.match(paramMatch); matchs?.forEach(([match, key, valuex]) => { params[key] = valuex; }); return ""; } ); collectionItems.push({ content, params, title: name, }); }); memory.declareCollection(name, collectionItems); } }); } }; export const run = (nodes: Node[]): Memory => { save(); const routes: Route[] = []; const views: View[] = []; let index = 0; let styles = preStyles + globalStyles; let head = defhead; let lastConditionValue: boolean = false; let title: string = "", description: string = "", layout: string = ""; nodes.forEach((node) => { const display = (err: any) => { if (err instanceof CustomError) { err.display(node.line); } else if (err instanceof Warning) { err.display(node.line); } }; try { switch (node.type) { case NodeType.OUTPUT: if (node.params.content?.startsWith("\\")) { const name = node.params.content.replace("\\", ""); const value = memory.getVar<string>(name); print(value?.value ?? ""); } else { print(node.params.content); } break; case NodeType.ERR: throw new CustomError( node.params.cause ?? config.DERR, ErrorType.RuntimeError ); break; case NodeType.DECLARE: try { memory.declareVar( node.params.name, node.params.value, node.params.type ); } catch (e) { display(e); } break; case NodeType.IF: const condition = node.params.condition; const cond = condition?.cond; const body = condition?.body; if (cond && body) { try { lastConditionValue = commanditeCond(cond); if (lastConditionValue) { rund(false, false, body); } } catch (e) { display(e); } } break; case NodeType.SHOW: const route = "/" + (node.params.route ?? `404/${++index}`); const processedHTMLContent = processHTML( memory, processLayout( layout, title, description, node.params.content ?? "" ) ); const processedContent = ` <head>${head}</head> <body>${processedHTMLContent}</body> `; routes.push({ name: route, content: processedContent }); break; case NodeType.EXPORTW: memory.setOrder("export", "true"); break; case NodeType.STYLE: styles += node.params.content || "* { box-sizing: border-box; }"; break; case NodeType.IMPORT: const mem: Memory = init( false, false, "./" + node.params.name || "./lib/main.ocat" ); memory.copyFrom(mem); break; case NodeType.UseStrict: memory.setStrict(); break; case NodeType.ORDER: memory.setOrder(node.params.name, node.params.content); break; case NodeType.CDECLARE: memory.declareComponent( node.params.name, processHTML(memory, node.params.content ?? "") ); break; case NodeType.META: const { type, content } = node.params; if (!(type && content)) { break; } if (type.startsWith("\\")) { switch (type.replace(/\\/g, "")) { case "title": head += `<title>${content}</title>`; title = content; break; } } else { if (type === "description") { description = content; } head += `<meta name="${type}" content="${content}" />`; } break; case NodeType.EXEC: exec(node.params.content || `echo ${config.ENCFE}`); break; case NodeType.Function: memory.declareFunction( node.params.name, node.params.content ); break; case NodeType.FCall: const func: Function = memory.getFunction( node.params.name ) ?? { name: node.params.name ?? "", content: "" }; rund(false, false, func.content); break; case NodeType.Create: const dtype = node.params.type; const dname = node.params.name; const contentx = node.params.content; if (dtype === "file") { fs.writeFileSync(dname ?? ".ocat", contentx ?? "p"); } else if (dtype === "folder" || dtype === "dir") { fs.mkdirSync(dname ?? "./.ocat"); } break; case NodeType.Layout: const tag = node.params.content; layout = processHTML(memory, tag ?? ""); break; case NodeType.LOAD: const _type = node.params.type ?? ""; const _route = processPath(node.params.route ?? "", config); const pathName = _route.split("/").pop() ?? ""; const readed = readFile(_route + `/${pathName}.component.html`) ?? ""; const cssReaded = readFile(_route + `/${pathName}.component.css`) ?? ""; const name = ( readed.match(/<title>\w+<\/title>/g)?.[0] ?? "udef" ) .replace("<title>", "") .replace("</title>", ""); if (_type === "component" || _type === "template") { const prcontent = readed .split(/\n/g) .splice(1) .join("\n"); const joinedContent = prcontent + `<style>${processCSS( cssReaded, name, memory )}</style>`; if (_type === "component") { memory.declareComponent(name, joinedContent); } else { memory.declareTemplate(name, joinedContent); } } else if (_type === "layout") { layout = processHTML(memory, readed); } break; } } catch (e) { if (e instanceof Warning || e instanceof CustomError) { e.display(node.line); } else { console.log("INTERNAL ERROR"); } } }); if ( routes.length !== 0 || config.views || memory.getOrder("export") === "true" || memory.getOrder("web") ) { if (config.views) { const viewsFolder = "./views/"; if (!fs.existsSync(viewsFolder)) { fs.mkdirSync(viewsFolder); } const items = fs.readdirSync(viewsFolder, { withFileTypes: true }); const files = items.filter((item) => item.isFile()); const dirs = items.filter((item) => item.isDirectory()); files.forEach((file) => { if (file.name.endsWith(".html")) { const content = fs.readFileSync( path.join(viewsFolder, file.name), "utf-8" ); views.push(viewAdapter(file.name, content)); } }); dirs.forEach((dir) => { const dirPath = path.join(viewsFolder, dir.name); const dirItems = fs.readdirSync(dirPath, { withFileTypes: true, }); const files = dirItems.filter((item) => item.isFile()); files.forEach((file) => { if (file.name.endsWith(".html")) { const name = path.join(dirPath, file.name); const _name = `${dir.name}/${file.name}`; const content = fs.readFileSync(name, "utf-8"); const view = viewAdapter(_name, content); views.push(view); } }); }); views.forEach((view) => { app.get(view.name, (req, res) => { if (view.useKey) { res.send( viewAdapt(view, memory, req.params.key ?? null) ); } else { res.send(viewAdapt(view, memory)); } }); }); } const routeTemplate = processRouteTemplate( codeRouteTemplate, routes, views ); routes.forEach((route) => { app.get(route.name, (req, res) => { res.send( processRoute( memory, preJs, route.content, route.name, routeTemplate, styles ) ); }); }); app.all("*", (req, res) => { res.status(404).send(process404(code404, { routeTemplate })); }); if (memory.getOrder("export") === "true") { if (fs.existsSync("./out")) fs.rmSync("./out", { recursive: true }); fs.mkdirSync("./out"); fs.mkdirSync("./out/css"); fs.writeFileSync("./out/css/style.css", styles); routes.forEach((route) => { const routePath = route.name === "/" ? "./out/index.html" : `./out/${route.name.replace(/^\//, "")}/index.html`; const dir = path.dirname(routePath); fs.mkdirSync(dir, { recursive: true }); const cssPath = route.name === "/" ? "./css/style.css" : "../".repeat(route.name.split("/").length - 1) + "css/style.css"; const contentWithCSS = `<link href="${cssPath}" rel="stylesheet" />${route.content}`; fs.writeFileSync(routePath, contentWithCSS); }); } try { app.listen(config.port, () => { console.log( `Server running at http://localhost:${config.port}/` ); console.log(`Quit the server with (CTRL or CMD) + C`); }); } catch (e) { console.log("The port is already in use"); } } return memory; }; const commanditeCond = (condition: string): boolean => { const args_ = condition.split("\\s+"); const args = args_.map((arg) => arg.trim()); const firstValue = getValue(args[0], memory); const operator = args[1]; const secondValue = getValue(args[2], memory); if ( operator.match(/\b(<>|<=|>=|<|>)\b/) && (firstValue.type !== "int" || secondValue.type !== "int") ) { const message = `Cannot compare ${firstValue.type} and ${secondValue.type} with a number operator`; if (memory.isStrict) { throw new CustomError(message, ErrorType.ExecutionError); } else { throw new Warning(message); } } switch (operator) { case "=": return firstValue.value === secondValue.value; break; case "!=": return firstValue.value !== secondValue.value; break; case "<>": return Number(firstValue.value) !== Number(secondValue.value); break; case "<=": return Number(firstValue.value) <= Number(secondValue.value); break; case ">=": return Number(firstValue.value) >= Number(secondValue.value); break; case "<": return Number(firstValue.value) < Number(secondValue.value); break; case ">": return Number(firstValue.value) > Number(secondValue.value); break; default: const message = `Unknown condition: ${operator}`; if (memory.isStrict) { throw new CustomError(message, ErrorType.ExecutionError); } else { throw new Warning(message); } break; } };