virtual-star/lib/render/renderer.js
2025-04-21 21:46:30 -04:00

150 lines
4.8 KiB
JavaScript

import { Context } from "../struct/context.js";
import { Fragment, fragmentFormats } from "../struct/fragment.js";
import { BasePaths } from "./base-paths.js";
import { FileHelper } from "./file-helper.js";
import { FragmentManager } from "./fragment-manager.js";
import { SettingsReader } from "./settings-reader.js";
import { TemplateManager } from "./template-manager.js";
import { tokenTypes } from "./token.js";
import { Tokenizer } from "./tokenizer.js";
import fs, { write } from 'fs';
export class Renderer {
path;
context;
constructor(path, context) {
this.path = path;
this.context = context;
}
readFolder() {
return `${BasePaths.contentRoot()}/${this.path}`;
}
writeFolder() {
return `${BasePaths.targetRoot()}/${this.path}`;
}
renderAll() {
// Create output folder
fs.mkdirSync(this.writeFolder());
// Get all files
const entries = fs.readdirSync(this.readFolder(), { encoding: 'utf-8', withFileTypes: true });
const files = entries.filter(
(e) => e.isFile()
);
for(let i = 0; i < files.length; i++) {
// Render content files, copy non-content files.
if(FileHelper.isContent(files[i])) {
this.renderPage(files[i]);
} else if(FileHelper.isSettingsFile(files[i])) {
// pass
} else {
this.copyFile(files[i]);
}
}
// Get all subdirectories.
const subdirs = entries.filter(
(e) => e.isDirectory()
);
for(let i = 0; i < subdirs.length; i++) {
this.renderSubdirectory(subdirs[i]);
}
}
copyFile(fileEnt) {
const readPath = `${this.readFolder()}/${fileEnt.name}`;
const writePath = `${this.writeFolder()}/${fileEnt.name}`;
fs.copyFileSync(readPath, writePath);
}
renderPage(fileEnt) {
const type = FileHelper.getFragmentType(fileEnt);
const readPath = `${this.readFolder()}/${fileEnt.name}`;
const content = fs.readFileSync(readPath, { encoding: 'utf-8' });
const vars = SettingsReader.readSettingsFromContent(content);
const strippedContent = SettingsReader.trimSettingsFromContent(content);
const fileContext = new Context(vars);
const fullContext = this.context.mergeFrom(fileContext);
const contentFragment = new Fragment(type, strippedContent);
let root = contentFragment;
const templateKey = fullContext.get(`template`);
if(templateKey) {
const template = new TemplateManager().get(templateKey);
if(template) {
root = template;
}
}
const pageOutput = this.renderFragment(root, fullContext, new FragmentManager(contentFragment));
const writePath = `${this.writeFolder()}/${FileHelper.getOutputFileName(fileEnt)}`;
fs.writeFileSync(writePath, pageOutput);
}
renderFragment(fragment, localContext, fragmentManager) {
if(!fragment) {
return '';
}
const tokenizer = new Tokenizer();
const fragmentAsHtml = fragment.toHtml().sourceContent;
const tokensByVar = tokenizer.tokensByVariable(fragmentAsHtml);
const replacedVarTokens = tokensByVar.map(
(t) => {
if(t.type === tokenTypes.TEXT) {
return t.content;
} else {
const key = t.content.trim();
const value = localContext.get(key);
return value;
}
}
);
const fragmentContentAfterVariableReplace = replacedVarTokens.reduce(
(a, b) => `${a}${b}`
);
const tokensByFragment = tokenizer.tokensByFragment(fragmentContentAfterVariableReplace);
const self = this;
const replacedFragmentTokens = tokensByFragment.map(
(t) => {
if(t.type === tokenTypes.TEXT) {
return t.content;
} else {
const key = t.content.trim();
const value = fragmentManager.get(key);
return self.renderFragment(value, localContext, fragmentManager);
}
}
);
const final = replacedFragmentTokens.reduce(
(a, b) => `${a}${b}`
);
return final;
}
renderSubdirectory(subDirEnt) {
const subPath = `${this.readFolder()}/${subDirEnt.name}`;
const subdirContext = SettingsReader.readDirectorySettings(subPath);
const nextContext = this.context.copy().mergeFrom(subdirContext);
const subRenderer = new Renderer(`${this.path}/${subDirEnt.name}`, nextContext);
subRenderer.renderAll();
}
}