aboutsummaryrefslogtreecommitdiff
path: root/deno/tools
diff options
context:
space:
mode:
Diffstat (limited to 'deno/tools')
-rw-r--r--deno/tools/deno.json1
-rw-r--r--deno/tools/generate-geosite-rules.ts (renamed from deno/tools/gen-geosite-rules.ts)0
-rw-r--r--deno/tools/manage-service.ts39
-rw-r--r--deno/tools/template.ts122
4 files changed, 162 insertions, 0 deletions
diff --git a/deno/tools/deno.json b/deno/tools/deno.json
index 3597182..355046a 100644
--- a/deno/tools/deno.json
+++ b/deno/tools/deno.json
@@ -3,5 +3,6 @@
"tasks": {
},
"imports": {
+ "mustache": "npm:mustache@^4.2.0"
}
}
diff --git a/deno/tools/gen-geosite-rules.ts b/deno/tools/generate-geosite-rules.ts
index c59d34f..c59d34f 100644
--- a/deno/tools/gen-geosite-rules.ts
+++ b/deno/tools/generate-geosite-rules.ts
diff --git a/deno/tools/manage-service.ts b/deno/tools/manage-service.ts
new file mode 100644
index 0000000..45e09ec
--- /dev/null
+++ b/deno/tools/manage-service.ts
@@ -0,0 +1,39 @@
+import { parseArgs } from "@std/cli";
+import { TemplateDir } from "./template.ts";
+import { join } from "@std/path";
+
+if (import.meta.main) {
+ const args = parseArgs(Deno.args, {
+ string: ["project-dir"],
+ boolean: ["no-dry-run"],
+ });
+
+ if (args._.length === 0) {
+ throw new Error("You must specify a command.");
+ }
+
+ const projectDir = args["project-dir"];
+ if (projectDir == null) {
+ throw new Error("You must specify project-dir.");
+ }
+
+ const command = String(args._[0]);
+
+ switch (command) {
+ case "gen-tmpl":
+ new TemplateDir(
+ join(projectDir, "services/templates"),
+ ).generateWithVariableFiles(
+ [
+ join(projectDir, "data/config"),
+ join(projectDir, "services/config.template"),
+ ],
+ args["no-dry-run"] === true
+ ? join(projectDir, "services/generated")
+ : undefined,
+ );
+ break;
+ default:
+ throw new Error(command + " is not a valid command.");
+ }
+}
diff --git a/deno/tools/template.ts b/deno/tools/template.ts
new file mode 100644
index 0000000..0b043a1
--- /dev/null
+++ b/deno/tools/template.ts
@@ -0,0 +1,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);
+ }
+}