-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtemplateEngine.ts
More file actions
137 lines (109 loc) · 3.85 KB
/
Copy pathtemplateEngine.ts
File metadata and controls
137 lines (109 loc) · 3.85 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
import Handlebars from 'handlebars';
export type TemplateContext = Record<string, any>;
export interface IncludedFile {
template: string;
output: string;
prettier?: boolean;
transpile?: boolean;
}
interface FileState {
imports: Set<string>;
includedFiles: IncludedFile[];
}
export class TemplateEngine {
private context: TemplateContext;
private templateRoot: string;
private currentFile: FileState | null = null;
private handlebars: typeof Handlebars;
constructor(context: TemplateContext, templateRoot: string) {
this.context = context;
this.templateRoot = templateRoot;
this.handlebars = Handlebars.create();
this.handlebars.Utils.escapeExpression = (str: any) => str;
this.registerHelpers();
}
private registerHelpers() {
this.handlebars.registerHelper('addImport', (statement: string) => {
if (this.currentFile) {
this.currentFile.imports.add(statement);
}
return '';
});
this.handlebars.registerHelper('list', function (this: any, options: any) {
if (!options.fn) return '';
const content = options.fn(this);
const lines = content
.split('\n')
.map((line: string) => line.trim())
.filter((line: string) => line && !line.startsWith('//'));
if (lines.length === 0) return '';
const result = lines
.map((item: string, index: number) => {
const isLast = index === lines.length - 1;
const hasSeparator = item.trimEnd().endsWith(',');
if (!isLast && !hasSeparator) {
return ' ' + item + ',';
} else if (isLast && hasSeparator) {
return ' ' + item.trimEnd().slice(0, -1);
}
return ' ' + item;
})
.join('\n');
return new Handlebars.SafeString(result + '\n');
});
this.handlebars.registerHelper('eq', (a: any, b: any) => {
return a === b;
});
this.handlebars.registerHelper('includeFile', (options: any) => {
if (!this.currentFile) return '';
const templatePath = options.hash.template || '';
const outputTemplate = options.hash.output || '';
const prettier = options.hash.prettier;
const transpile = options.hash.transpile;
if (templatePath && outputTemplate) {
// Process both the template path and output path as templates
const template = this.handlebars.compile(templatePath)(this.context);
const output = this.handlebars.compile(outputTemplate)(this.context);
this.currentFile.includedFiles.push({
template,
output,
prettier,
transpile,
});
}
return '';
});
}
async processTemplate(
templatePath: string,
): Promise<{content: string; includedFiles: IncludedFile[]}> {
const fs = await import('fs/promises');
const path = await import('path');
const fullPath = path.join(this.templateRoot, templatePath);
const rawContent = await fs.readFile(fullPath, 'utf-8');
this.currentFile = {
imports: new Set<string>(),
includedFiles: [],
};
// Register partials (for includeFile)
await this.registerPartial('partial.hbs');
const template = this.handlebars.compile(rawContent);
const result = template(this.context);
const imports = Array.from(this.currentFile.imports);
const includedFiles = [...this.currentFile.includedFiles];
let content = result;
if (imports.length > 0) {
content = imports.join('\n') + '\n\n' + result;
}
return {content, includedFiles};
}
private async registerPartial(partialPath: string) {
try {
const fs = await import('fs/promises');
const path = await import('path');
const fullPath = path.join(this.templateRoot, partialPath);
const content = await fs.readFile(fullPath, 'utf-8');
this.handlebars.registerPartial(partialPath, content);
} catch {}
}
}