diff options
Diffstat (limited to 'deno/service-manager')
| -rw-r--r-- | deno/service-manager/deno.json | 13 | ||||
| -rw-r--r-- | deno/service-manager/main.ts | 39 | ||||
| -rw-r--r-- | deno/service-manager/template.ts | 122 | 
3 files changed, 174 insertions, 0 deletions
diff --git a/deno/service-manager/deno.json b/deno/service-manager/deno.json new file mode 100644 index 0000000..2ba8394 --- /dev/null +++ b/deno/service-manager/deno.json @@ -0,0 +1,13 @@ + +{ +  "version": "0.1.0", +  "tasks": { +    "run": "deno run -A main.ts", +    "compile": "deno compile -o out/manage -A main.ts" +  }, +  "imports": { +    "@std/dotenv": "jsr:@std/dotenv@^0.225.5", +    "@std/fs": "jsr:@std/fs@^1.0.18", +    "mustache": "npm:mustache@^4.2.0" +  } +} diff --git a/deno/service-manager/main.ts b/deno/service-manager/main.ts new file mode 100644 index 0000000..93f4c1b --- /dev/null +++ b/deno/service-manager/main.ts @@ -0,0 +1,39 @@ +import { parseArgs } from "@std/cli"; +import { loadVariables, 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/service-manager/template.ts b/deno/service-manager/template.ts new file mode 100644 index 0000000..be6cb60 --- /dev/null +++ b/deno/service-manager/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); +  } +}  | 
