@mdfriday/foundry
Version:
The core engine of MDFriday. Convert Markdown and shortcodes into fully themed static sites – Hugo-style, powered by TypeScript.
361 lines • 12.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TemplateEngine = void 0;
exports.newTemplateEngine = newTemplateEngine;
const type_1 = require("../type");
const info_1 = require("../vo/info");
const info_2 = require("../vo/info");
const log_1 = require("../../../../pkg/log");
const paths_1 = require("../../../domain/paths");
// Create domain-specific logger for template operations
const log = (0, log_1.getDomainLogger)('template', { component: 'template-engine' });
/**
* Template entity - main template engine with separated template management
* TypeScript version of Go's Template struct
*/
class TemplateEngine {
constructor(executor, lookup, parser, templateNamespace, partialNamespace, shortcodeNamespace, fs) {
this.executor = executor;
this.lookup = lookup;
this.parser = parser;
this.templateNamespace = templateNamespace;
this.partialNamespace = partialNamespace;
this.shortcodeNamespace = shortcodeNamespace;
this.fs = fs;
}
/**
* Mark template engine as ready
*/
async markReady() {
try {
await this.parser.markReady();
}
catch (error) {
throw new type_1.TemplateError(`Failed to mark template engine ready: ${error.message}`, 'TEMPLATE_ENGINE_READY_FAILED');
}
}
/**
* Get regular template by name with baseof dependency support
*/
async getTemplate(name) {
// First try to find template with no dependencies
const [tmpl, found, err] = this.lookup.findTemplate(name, this.templateNamespace);
if (err) {
return [null, false, err];
}
if (found && tmpl) {
return [tmpl, true, null];
}
// Try to find template with dependencies
const [overlay, base, hasDependent] = this.lookup.findDependentInfo(name);
if (hasDependent && overlay) {
try {
const [ts, parseFound, parseErr] = await this.parser.parseOverlap(overlay, base || overlay, this.lookup.newTemplateLookup(this.templateNamespace));
if (parseErr) {
return [null, false, parseErr];
}
if (parseFound && ts) {
return [ts.template, true, null];
}
}
catch (error) {
return [null, false, error];
}
}
return [null, false, null];
}
/**
* Get partial template by name (simple lookup)
*/
async getPartial(name) {
// Ensure name includes partials prefix if not already present
let partialName = name.startsWith(type_1.PARTIALS_PREFIX) ? name : `${type_1.PARTIALS_PREFIX}${name}`;
partialName = name.endsWith(".html") ? partialName : `${partialName}.html`;
return this.lookup.findPartial(partialName, this.partialNamespace);
}
/**
* Get shortcode template by name
*/
async getShortcode(name) {
return this.lookup.findShortcode(name, this.shortcodeNamespace);
}
/**
* Get template by name (unified method that routes to appropriate namespace)
*/
async get(name) {
const templateType = (0, info_2.resolveTemplateType)(name);
switch (templateType) {
case type_1.TemplateType.TypeShortcode:
return this.getShortcode(name);
case type_1.TemplateType.TypePartial:
return this.getPartial(name);
default:
return this.getTemplate(name);
}
}
/**
* Find first available template from a list of template names by priority
* Returns the first template found, its name, whether it was found, and any error
*/
async findFirst(names) {
if (!names || names.length === 0) {
return [null, null, false, null];
}
for (const name of names) {
try {
const [tmpl, found, err] = await this.get(name);
if (err) {
// Log error but continue to try next template
continue;
}
if (found && tmpl) {
return [tmpl, name, true, null];
}
}
catch (error) {
log.error("Error finding template:", error);
}
}
return [null, null, false, null];
}
/**
* Check if a shortcode exists
* @param name - The shortcode name
* @returns true if the shortcode exists, false otherwise
*/
hasShortcode(name) {
return this.shortcodeNamespace.hasShortcode(name);
}
/**
* Get all available shortcode names
* @returns Array of shortcode names
*/
getShortcodeNames() {
return this.shortcodeNamespace.getShortcodeNames();
}
/**
* Get shortcode count
* @returns Number of loaded shortcodes
*/
getShortcodeCount() {
return this.getShortcodeNames().length;
}
/**
* Load templates from file system (unified for all template types)
*/
async loadTemplates() {
const walker = async (filePath, fi) => {
if (fi.isDir()) {
return;
}
// Convert to relative path
const name = filePath.startsWith(paths_1.PATH_CONSTANTS.SYSTEM_PATH_SEPARATOR) ? filePath.substring(1) : filePath;
const normalizedName = paths_1.PATH_CONSTANTS.normalizePath(name);
try {
await this.addTemplateFileInfo(normalizedName, fi);
}
catch (error) {
throw new type_1.TemplateError(`Failed to add template ${normalizedName}: ${error.message}`, 'LOAD_TEMPLATE_FAILED');
}
};
try {
await this.fs.walk('', {
walkFn: walker,
}, {});
}
catch (error) {
// Check if it's a "not exist" error
if (!error.message?.includes('ENOENT') && !error.message?.includes('no such file')) {
throw error;
}
// If directory doesn't exist, that's okay - just continue
}
}
/**
* Add template from file info (routes to appropriate namespace)
*/
async addTemplateFileInfo(name, fim) {
try {
const tinfo = await (0, info_1.loadTemplate)(name, fim);
await this.addTemplate(tinfo.name, tinfo);
}
catch (error) {
throw new type_1.TemplateError(`Failed to load template info for ${name}: ${error.message}`, 'LOAD_TEMPLATE_INFO_FAILED');
}
}
/**
* Add template to appropriate namespace based on type
*/
async addTemplate(name, tinfo) {
try {
// Check if it's a base template
if (this.lookup.getBaseOf().isBaseTemplatePath(name)) {
this.lookup.getBaseOf().addBaseOf(name, tinfo);
return;
}
// Check if it needs a base template
if (this.lookup.getBaseOf().needsBaseOf(name, tinfo.template)) {
this.lookup.getBaseOf().addNeedsBaseOf(name, tinfo);
return;
}
// Determine template type and add to appropriate namespace
const templateType = (0, info_2.resolveTemplateType)(name);
// Parse single template (no dependencies)
const state = await this.parser.parse(tinfo);
switch (templateType) {
case type_1.TemplateType.TypeShortcode:
this.shortcodeNamespace.addShortcodeTemplate(name, state);
break;
case type_1.TemplateType.TypePartial:
this.partialNamespace.addPartialTemplate(name, state);
break;
default:
this.templateNamespace.addTemplate(name, state);
break;
}
}
catch (error) {
throw new type_1.TemplateError(`Failed to add template ${name}: ${error.message}`, 'ADD_TEMPLATE_FAILED');
}
}
/**
* Execute template with data
*/
async execute(templateName, data) {
const [tmpl, found, err] = await this.get(templateName);
if (err) {
throw err;
}
if (!found || !tmpl) {
throw new type_1.TemplateError(`Template not found: ${templateName}`, 'TEMPLATE_NOT_FOUND');
}
return await this.executor.execute(tmpl, data);
}
async executeRaw(templateName, rawContent, data) {
const tmpl = await this.parser.parseWithLock(templateName, rawContent);
if (!tmpl) {
throw new type_1.TemplateError(`Raw Template parse error: ${templateName}`, 'TEMPLATE_PARSE_ERROR');
}
return await this.executor.execute(tmpl, data);
}
/**
* Execute shortcode template with data
*/
async executeShortcode(shortcodeName, data) {
const tmpl = this.shortcodeNamespace.getShortcode(shortcodeName);
if (!tmpl) {
throw new type_1.TemplateError(`Shortcode template '${shortcodeName}' not found`, 'SHORTCODE_NOT_FOUND');
}
try {
return await this.executor.execute(tmpl, data);
}
catch (error) {
throw new type_1.TemplateError(`Error executing shortcode template '${shortcodeName}': ${error.message}`, 'SHORTCODE_EXECUTION_FAILED');
}
}
/**
* Execute template safely
*/
async executeSafely(templateName, data) {
try {
const result = await this.execute(templateName, data);
return { result, error: null };
}
catch (error) {
if (error instanceof type_1.TemplateError) {
return { result: null, error };
}
return {
result: null,
error: new type_1.TemplateError(`Execute safely failed: ${error.message}`, 'EXECUTE_SAFELY_FAILED')
};
}
}
/**
* Execute shortcode safely
*/
async executeShortcodeSafely(shortcodeName, data) {
try {
const result = await this.executeShortcode(shortcodeName, data);
return { result, error: null };
}
catch (error) {
if (error instanceof type_1.TemplateError) {
return { result: null, error };
}
return {
result: null,
error: new type_1.TemplateError(`Execute shortcode safely failed: ${error.message}`, 'EXECUTE_SHORTCODE_SAFELY_FAILED')
};
}
}
/**
* Check if template exists
*/
async hasTemplate(name) {
const [, found] = await this.get(name);
return found;
}
/**
* Get all template names
*/
getTemplateNames() {
return this.templateNamespace.getTemplateNames();
}
/**
* Get all partial template names
*/
getPartialTemplateNames() {
return this.partialNamespace.getTemplateNames();
}
/**
* Get all template names from all namespaces
*/
getAllTemplateNames() {
return [
...this.templateNamespace.getTemplateNames(),
...this.partialNamespace.getTemplateNames(),
...this.shortcodeNamespace.getTemplateNames()
];
}
/**
* Get partial namespace (for internal use by partial function)
*/
getPartialNamespace() {
return this.partialNamespace;
}
/**
* Get shortcode namespace (for internal use)
*/
getShortcodeNamespace() {
return this.shortcodeNamespace;
}
/**
* Get template namespace (for internal use)
*/
getTemplateNamespace() {
return this.templateNamespace;
}
/**
* Clear all templates including shortcodes
*/
clear() {
this.templateNamespace.clear();
this.lookup.getBaseOf().clear();
}
/**
* Get template by pattern
*/
getTemplatesByPattern(pattern) {
const templates = this.templateNamespace.getTemplatesByPattern(pattern);
return templates.map(t => t.info.name);
}
}
exports.TemplateEngine = TemplateEngine;
/**
* Create a new TemplateEngine instance
*/
function newTemplateEngine(executor, lookup, parser, templateNamespace, partialNamespace, shortcodeNamespace, fs) {
return new TemplateEngine(executor, lookup, parser, templateNamespace, partialNamespace, shortcodeNamespace, fs);
}
//# sourceMappingURL=template.js.map