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
|
import { dirname, join, relative } from "@std/path";
import { copySync, existsSync, walkSync } from "@std/fs";
import { parse } from "@std/dotenv";
import { distinct } from "@std/collections"
// @ts-types="npm:@types/mustache"
import Mustache from "mustache"
Mustache.tags = ["@@", "@@"];
Mustache.escape = (value) => String(value);
function getVariableKeys(original: string): string[] {
return distinct(
Mustache.parse(original)
.filter(function (v) {
return v[0] === "name";
})
.map(function (v) {
return v[1];
}),
);
}
export function loadVariables(files: string[]): Record<string, string> {
const vars: Record<string, string> = {}
for (const file of files) {
const text = Deno.readTextFileSync(file);
for (const [key, valueText] of Object.entries(parse(text))) {
getVariableKeys(valueText).forEach((name) => {
if (!(name in vars)) {
throw new Error(
`Variable ${name} is not defined yet, perhaps due to typos or wrong order.`,
);
}
});
vars[key] = Mustache.render(valueText, vars);
}
}
return vars;
}
const TEMPLATE_FILE_EXT = ".template"
export class TemplateDir {
templates: { path: string; ext: string; text: string; vars: string[] }[] = [];
plains: { path: string }[] = [];
constructor(public dir: string) {
console.log("Scanning template dir:");
Array.from(
walkSync(dir, { includeDirs: false, followSymlinks: true }),
).forEach(({ path }) => {
path = relative(this.dir, path);
if (path.endsWith(TEMPLATE_FILE_EXT)) {
console.log(` (template) ${path}`);
const text = Deno.readTextFileSync(join(dir, path));
this.templates.push({
path,
ext: TEMPLATE_FILE_EXT,
text,
vars: getVariableKeys(text),
});
} else {
console.log(` (plain) ${path}`);
this.plains.push({ path });
}
});
console.log("Done scanning template dir.");
}
allNeededVars() {
return distinct(this.templates.flatMap((t) => t.vars));
}
generate(vars: Record<string, string>, generatedDir?: string) {
console.log(
`Generating, template dir: ${this.dir}, generated dir: ${generatedDir ?? "[dry-run]"}:`,
);
const undefinedVars = this.allNeededVars().filter((v) => !(v in vars));
if (undefinedVars.length !== 0) {
throw new Error(
`Needed variables are not defined: ${undefinedVars.join(", ")}`,
);
}
if (generatedDir != null) {
if (existsSync(generatedDir)) {
console.log(` delete old generated dir ${generatedDir}`);
Deno.removeSync(generatedDir, { recursive: true });
}
for (const file of this.plains) {
const [source, destination] = [
join(this.dir, file.path),
join(generatedDir, file.path),
];
console.log(` copy ${source} to ${destination} ...`);
Deno.mkdirSync(dirname(destination), { recursive: true });
copySync(source, destination);
}
for (const file of this.templates) {
const [source, destination] = [
join(this.dir, file.path),
join(generatedDir, file.path.slice(0, -file.ext.length)),
];
console.log(` generate ${source} to ${destination} ...`);
const rendered = Mustache.render(file.text, vars);
Deno.mkdirSync(dirname(destination), { recursive: true });
Deno.writeTextFileSync(destination, rendered);
}
}
console.log(`Done generating.`);
}
generateWithVariableFiles(varFiles: string[], generatedDir?: string) {
console.log("Scanning defined vars:");
const vars = loadVariables(varFiles);
Object.keys(vars).forEach((name) => console.log(` ${name}`));
console.log("Done scanning defined vars.");
this.generate(vars, generatedDir);
}
}
|