UNPKG

@mdfriday/foundry

Version:

The core engine of MDFriday. Convert Markdown and shortcodes into fully themed static sites – Hugo-style, powered by TypeScript.

332 lines 13.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Parser = void 0; exports.newParser = newParser; const text_template_1 = require("@mdfriday/text-template"); const text_template_2 = require("@mdfriday/text-template"); const type_1 = require("../type"); const info_1 = require("../vo/info"); const log_1 = require("../../../../pkg/log"); const cache_1 = require("../../../../pkg/cahce/cache"); // Create domain-specific logger for parser operations const log = (0, log_1.getDomainLogger)('template', { component: 'parser' }); /** * Parser entity for parsing templates * TypeScript version of Go's Parser struct */ class Parser { constructor(funcMap = new Map()) { this.readyInit = false; this.funcMap = funcMap; this.prototypeText = (0, text_template_1.New)(''); this.parseOverlapCache = new cache_1.ConcurrentCache(); // Apply function map to prototype if (this.funcMap.size > 0) { this.prototypeText.Funcs(this.funcMap); } } /** * Mark parser as ready and create clone */ async markReady() { if (!this.readyInit) { // Create clone for thread safety this.prototypeTextClone = this.prototypeText; this.readyInit = true; } } /** * Parse template info into template state */ async parse(info) { try { const tmpl = (0, text_template_1.New)(info.name); if (this.funcMap && this.funcMap.size > 0) { tmpl.Funcs(this.funcMap); } const [parsedTmpl, parseErr] = tmpl.Parse(info.template); if (parseErr) { throw new type_1.TemplateError(`Parse failed: ${parseErr.message}`, 'PARSE_FAILED'); } return { template: parsedTmpl, info, type: (0, info_1.resolveTemplateType)(info.name), }; } catch (error) { if (error instanceof type_1.TemplateError) { throw error; } const message = error instanceof Error ? error.message : String(error); throw new type_1.TemplateError(`Parse failed: ${message}`, 'PARSE_FAILED'); } } /** * Parse template with lock (for thread safety) */ async parseWithLock(name, tpl) { try { if (!this.prototypeTextClone) { await this.markReady(); } const tmpl = (0, text_template_1.New)(name); if (this.funcMap && this.funcMap.size > 0) { tmpl.Funcs(this.funcMap); } const [parsedTmpl, parseErr] = tmpl.Parse(tpl); if (parseErr) { throw new type_1.TemplateError(`Parse with lock failed: ${parseErr.message}`, 'PARSE_LOCK_FAILED'); } return parsedTmpl; } catch (error) { if (error instanceof type_1.TemplateError) { throw error; } const message = error instanceof Error ? error.message : String(error); throw new type_1.TemplateError(`Parse with lock failed: ${message}`, 'PARSE_LOCK_FAILED'); } } /** * Parse template with overlay and base template */ async parseOverlap(overlay, base, lookup) { // 创建缓存key:使用overlay和base的name组合 const cacheKey = `${overlay.name}::${base.name || 'empty'}`; return this.parseOverlapCache.getOrCreate(cacheKey, async () => { try { const tmpl = await this.applyBaseTemplate(overlay, base, lookup); const ts = { template: tmpl, info: overlay, type: (0, info_1.resolveTemplateType)(overlay.name), }; if (!base.isZero || base.name) { ts.baseInfo = base; } return [ts, true, null]; } catch (error) { const templateError = error instanceof type_1.TemplateError ? error : new type_1.TemplateError(`Parse overlap failed: ${error.message}`, 'PARSE_OVERLAP_FAILED'); log.error(`Failed to parse template overlap for key: ${cacheKey}: ${templateError.message}`); return [null, false, templateError]; } }); } /** * Apply base template to overlay */ async applyBaseTemplate(overlay, base, lookup) { try { const tmpl = (0, text_template_1.New)(overlay.name); if (this.funcMap && this.funcMap.size > 0) { tmpl.Funcs(this.funcMap); } // Parse base template first if it exists if (!base.isZero || base.name) { const [baseTmpl, baseErr] = tmpl.Parse(base.template); if (baseErr) { throw new type_1.TemplateError(`Base template parse failed: ${baseErr.message}`, 'BASE_PARSE_FAILED'); } } // Clone and parse overlay template (matching Go's template.Must(tmpl.Clone()).Parse pattern) const [clonedTmpl, cloneErr] = tmpl.Clone(); if (cloneErr) { throw new type_1.TemplateError(`Template clone failed: ${cloneErr.message}`, 'CLONE_FAILED'); } const [overlayTmpl, overlayErr] = clonedTmpl.Parse(overlay.template); if (overlayErr) { throw new type_1.TemplateError(`Overlay template parse failed: ${overlayErr.message}`, 'OVERLAY_PARSE_FAILED'); } // Get dependencies using syntax tree traversal (与Go版本一致的遍历方法) const dependencies = await this.getDependencies(overlayTmpl, new Map(), lookup); // Add dependencies to template using AddParseTree (matching Go implementation) for (const [name, state] of dependencies) { if (name === overlay.name) { continue; } try { // Use AddParseTree to associate the dependency template - check Tree is not null if (state.template.Tree) { const [, addErr] = overlayTmpl.AddParseTree(name, state.template.Tree); if (addErr) { log.error(`AddParseTree failed: ${addErr.message} with name ${name}`); throw new type_1.TemplateError(`AddParseTree failed: ${addErr.message}`, 'ADD_PARSE_TREE_FAILED'); } } } catch (error) { log.error(`Error adding dependency ${name} to template ${overlay.name}:`, error); throw new type_1.TemplateError(`Failed to add dependency ${name}: ${error.message}`, 'DEPENDENCY_ADD_FAILED'); } } return overlayTmpl; } catch (error) { if (error instanceof type_1.TemplateError) { throw error; } const message = error instanceof Error ? error.message : String(error); throw new type_1.TemplateError(`Apply base template failed: ${message}`, 'APPLY_BASE_FAILED'); } } /** * Get template dependencies using syntax tree traversal (与Go版本一致的遍历方法) */ async getDependencies(tmpl, discovered, lookup) { const resultMap = new Map(); if (discovered.has(tmpl.Name())) { return resultMap; } discovered.set(tmpl.Name(), tmpl); try { const depNames = new Set(); // 遍历语法树的所有节点(与Go版本一致的方法) const visit = (node) => { if (!node) return; const nodeType = node.Type(); if (nodeType === text_template_2.NodeType.NodeTemplate) { // 找到template引用 const templateNode = node; const name = templateNode.Name; if (name) { depNames.add(name); } } else if (nodeType === text_template_2.NodeType.NodeList) { // 递归处理ListNode中的所有节点 const listNode = node; if (listNode.Nodes) { for (const childNode of listNode.Nodes) { visit(childNode); } } } // 处理其他类型的节点,可能包含嵌套的Node // 例如:ActionNode、IfNode、RangeNode、WithNode等都可能包含Pipe或List // 这里需要根据具体的Node类型来递归访问其子节点 if (nodeType === text_template_2.NodeType.NodeAction) { const actionNode = node; if (actionNode.Pipe) { visit(actionNode.Pipe); } } else if (nodeType === text_template_2.NodeType.NodePipe) { const pipeNode = node; if (pipeNode.Cmds) { for (const cmd of pipeNode.Cmds) { visit(cmd); } } } else if (nodeType === text_template_2.NodeType.NodeCommand) { const commandNode = node; if (commandNode.Args) { for (const arg of commandNode.Args) { visit(arg); } } } else if (nodeType === text_template_2.NodeType.NodeIf || nodeType === text_template_2.NodeType.NodeRange || nodeType === text_template_2.NodeType.NodeWith) { // BranchNode包含Pipe、List和ElseList const branchNode = node; if (branchNode.Pipe) { visit(branchNode.Pipe); } if (branchNode.List) { visit(branchNode.List); } if (branchNode.ElseList) { visit(branchNode.ElseList); } } }; // 从Root开始遍历 if (tmpl.Tree && tmpl.Tree.Root) { visit(tmpl.Tree.Root); } // 递归解析依赖 for (const depName of depNames) { const state = lookup(depName); if (state) { resultMap.set(depName, state); // 递归获取依赖的依赖(但要避免循环依赖) if (!discovered.has(state.template.Name())) { const subDeps = await this.getDependencies(state.template, discovered, lookup); for (const [subName, subState] of subDeps) { if (!resultMap.has(subName)) { resultMap.set(subName, subState); } } } } } } catch (error) { log.error(`Error getting dependencies for template ${tmpl.Name()}:`, error); } return resultMap; } /** * Set function map */ setFuncMap(funcMap) { this.funcMap = new Map([...this.funcMap, ...funcMap]); // Apply to prototype if (this.funcMap.size > 0) { this.prototypeText.Funcs(this.funcMap); } } /** * Get function map */ getFuncMap() { return new Map(this.funcMap); } /** * Parse multiple templates */ async parseMultiple(templates) { const results = await Promise.allSettled(templates.map(info => this.parse(info))); const states = []; const errors = []; for (const result of results) { if (result.status === 'fulfilled') { states.push(result.value); } else { errors.push(result.reason); } } if (errors.length > 0) { throw new type_1.TemplateError(`Failed to parse ${errors.length} templates: ${errors.map(e => e.message).join(', ')}`, 'MULTIPLE_PARSE_FAILED'); } return states; } /** * Clear the parse overlap cache */ clearCache() { this.parseOverlapCache.clear(); } /** * Get cache statistics */ getCacheStats() { return this.parseOverlapCache.getStats(); } } exports.Parser = Parser; /** * Create a new Parser instance */ function newParser(funcMap = new Map()) { return new Parser(funcMap); } //# sourceMappingURL=parser.js.map