|
| 1 | +import { fromFileUrl, globToRegExp, join, relative } from "@std/path"; |
| 2 | +import { flatMap } from "@core/iterutil/async/flat-map"; |
| 3 | + |
| 4 | +const decoder = new TextDecoder(); |
| 5 | + |
| 6 | +const excludes = [ |
| 7 | + "mod.ts", |
| 8 | + "*_test.ts", |
| 9 | + "*_bench.ts", |
| 10 | + "_*.ts", |
| 11 | +]; |
| 12 | + |
| 13 | +type DenoDocEntry = { |
| 14 | + name: string; |
| 15 | + location: { |
| 16 | + filename: string; |
| 17 | + }; |
| 18 | + declarationKind: string; |
| 19 | + jsDoc: { |
| 20 | + doc: string; |
| 21 | + }; |
| 22 | + kind: string; |
| 23 | +}; |
| 24 | + |
| 25 | +function isDenoDocEntry(x: unknown): x is DenoDocEntry { |
| 26 | + if (x == null || typeof x !== "object") return false; |
| 27 | + if (typeof (x as DenoDocEntry).name !== "string") return false; |
| 28 | + if (typeof (x as DenoDocEntry).location !== "object") return false; |
| 29 | + if (typeof (x as DenoDocEntry).location.filename !== "string") return false; |
| 30 | + if (typeof (x as DenoDocEntry).declarationKind !== "string") return false; |
| 31 | + if (typeof (x as DenoDocEntry).jsDoc !== "object") return false; |
| 32 | + if (typeof (x as DenoDocEntry).jsDoc.doc !== "string") return false; |
| 33 | + if (typeof (x as DenoDocEntry).kind !== "string") return false; |
| 34 | + return true; |
| 35 | +} |
| 36 | + |
| 37 | +async function listDenoDocEntries(path: string): Promise<DenoDocEntry[]> { |
| 38 | + const cmd = new Deno.Command(Deno.execPath(), { |
| 39 | + args: ["doc", "--json", path], |
| 40 | + stdout: "piped", |
| 41 | + stderr: "piped", |
| 42 | + }); |
| 43 | + const { success, stdout, stderr } = await cmd.output(); |
| 44 | + if (!success) { |
| 45 | + throw new Error(decoder.decode(stderr)); |
| 46 | + } |
| 47 | + const json = JSON.parse(decoder.decode(stdout)); |
| 48 | + if (!Array.isArray(json)) { |
| 49 | + throw new Error(`Expected array but got ${JSON.stringify(json)}`); |
| 50 | + } |
| 51 | + return json.filter(isDenoDocEntry); |
| 52 | +} |
| 53 | + |
| 54 | +async function* iterModules(path: string): AsyncIterable<string> { |
| 55 | + const patterns = excludes.map((p) => globToRegExp(p)); |
| 56 | + for await (const entry of Deno.readDir(path)) { |
| 57 | + if (!entry.isFile || !entry.name.endsWith(".ts")) continue; |
| 58 | + if (patterns.some((p) => p.test(entry.name))) continue; |
| 59 | + yield join(path, entry.name); |
| 60 | + } |
| 61 | +} |
| 62 | + |
| 63 | +async function generateModTs( |
| 64 | + namespace: string, |
| 65 | +): Promise<void> { |
| 66 | + const path = fromFileUrl(import.meta.resolve(`../${namespace}/`)); |
| 67 | + const exports = (await Array.fromAsync( |
| 68 | + flatMap(iterModules(path), (x) => listDenoDocEntries(x)), |
| 69 | + )) |
| 70 | + .filter((x) => x.kind === "function") |
| 71 | + .filter((x) => x.declarationKind === "export") |
| 72 | + .filter((x) => x.name.startsWith(namespace)) |
| 73 | + .map((x) => ({ |
| 74 | + path: relative(path, fromFileUrl(x.location.filename)), |
| 75 | + name: x.name, |
| 76 | + doc: x.jsDoc.doc, |
| 77 | + })) |
| 78 | + .toSorted((a, b) => a.name.localeCompare(b.name)); |
| 79 | + const lines = [ |
| 80 | + "// NOTE: This file is generated by gen-mod.ts", |
| 81 | + ...exports.map((x) => { |
| 82 | + return `import { ${x.name} } from "./${x.path}";`; |
| 83 | + }), |
| 84 | + "", |
| 85 | + `export const ${namespace}: {`, |
| 86 | + ...exports.flatMap((x) => { |
| 87 | + return [ |
| 88 | + " /**", |
| 89 | + ...x.doc.split("\n").map((line) => ` * ${line}`.trimEnd()), |
| 90 | + " */", |
| 91 | + ` ${x.name.replace(namespace, "")}: typeof ${x.name};`.trimEnd(), |
| 92 | + ]; |
| 93 | + }), |
| 94 | + "} = {", |
| 95 | + ...exports.flatMap((x) => { |
| 96 | + return [ |
| 97 | + ` ${x.name.replace(namespace, "")}: ${x.name},`.trimEnd(), |
| 98 | + ]; |
| 99 | + }), |
| 100 | + "};", |
| 101 | + ]; |
| 102 | + await Deno.writeTextFile(join(path, "mod.ts"), lines.join("\n") + "\n"); |
| 103 | +} |
| 104 | + |
| 105 | +async function main(): Promise<void> { |
| 106 | + await generateModTs("is"); |
| 107 | + await generateModTs("as"); |
| 108 | +} |
| 109 | + |
| 110 | +if (import.meta.main) { |
| 111 | + main().catch((err) => { |
| 112 | + console.error(err); |
| 113 | + Deno.exit(1); |
| 114 | + }); |
| 115 | +} |
0 commit comments