Skip to content
This repository has been archived by the owner on Sep 14, 2023. It is now read-only.

chore: reworking CLI + import specifiers #345

Merged
merged 9 commits into from
Oct 31, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 66 additions & 10 deletions codegen.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,78 @@
import { run } from "./codegen/mod.ts";
import { codegen } from "./codegen/mod.ts";
import { Config } from "./config/mod.ts";
import { parse } from "./deps/std/flags.ts";
import * as path from "./deps/std/path.ts";
import { unimplemented } from "./deps/std/testing/asserts.ts";
import * as M from "./frame_metadata/mod.ts";
import { proxyClient } from "./rpc/providers/proxy.ts";
import * as U from "./util/mod.ts";

const args = parse(Deno.args, {
string: ["metadata", "output"],
string: ["src", "out", "import"],
boolean: ["help"],
default: {
import: "deno",
},
alias: {
metadata: ["m"],
output: ["out", "o"],
src: ["s"],
out: ["o"],
help: ["h", "?"],
},
});

if (args.help) {
console.log("Usage: codegen --metadata <file> --output <dir>");
Deno.exit(0);
if (args.help) help();
if (!args.src) fail();
if (!args.out) fail();

await codegen({
importSpecifier: args.import === "npm"
? "capi"
: args.import === "deno"
? "https://deno.land/x/capi/mod.ts"
: args.import,
metadata: await getMetadata(args.src),
outDir: args.out,
}).write();

async function getMetadata(src: string): Promise<M.Metadata> {
if (src.startsWith("ws")) {
const client = U.throwIfError(await proxyClient(new Config(() => src, undefined!)));
const metadata = U.throwIfError(await client.call("state_getMetadata", []));
U.throwIfError(await client.close());
if (metadata.error) fail();
return M.fromPrefixedHex(metadata.result);
} else if (path.isAbsolute(src)) {
return await loadMetadata(src);
} else {
try {
return await loadMetadata(path.fromFileUrl(src));
} catch (_e) {
unimplemented();
}
}
}

if (!args.metadata) throw new Error("Must specify metadata file");
if (!args.output) throw new Error("Must specify output dir");
async function loadMetadata(src: string) {
const ext = path.extname(src);
switch (ext) {
case ".scale": {
return M.fromPrefixedHex(await Deno.readTextFile(src));
}
case ".json": {
return unimplemented();
}
tjjfvi marked this conversation as resolved.
Show resolved Hide resolved
default:
fail();
}
}

run(args.metadata, args.output);
// TODO: error message + correct usage suggestion
function fail(): never {
Deno.exit();
harrysolovay marked this conversation as resolved.
Show resolved Hide resolved
}

// TODO: do we handle help differently depending on what flags were specified?
function help(): never {
console.log("Usage: codegen --metadata <file> --output <dir>");
harrysolovay marked this conversation as resolved.
Show resolved Hide resolved
Deno.exit();
}
36 changes: 36 additions & 0 deletions codegen/Files.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { tsFormatter } from "../deps/dprint.ts";
import * as path from "../deps/std/path.ts";
import { S } from "./utils.ts";

export type File = { getContent: () => S };
export class Files extends Map<string, File> {
constructor(readonly outDir: string) {
super();
}

write = async () => {
harrysolovay marked this conversation as resolved.
Show resolved Hide resolved
const errors = [];
try {
await Deno.remove(this.outDir, { recursive: true });
} catch (e) {
if (!(e instanceof Deno.errors.NotFound)) {
throw e;
}
}
await Deno.mkdir(this.outDir, { recursive: true });
for (const [relativePath, file] of this.entries()) {
const outputPath = path.join(this.outDir, relativePath);
const content = S.toString(file.getContent());
try {
const formatted = tsFormatter.formatText("gen.ts", content);
await Deno.writeTextFile(outputPath, formatted);
} catch (e) {
await Deno.writeTextFile(outputPath, content);
errors.push(e);
}
}
if (errors.length) {
throw errors;
}
};
}
14 changes: 8 additions & 6 deletions codegen/codecVisitor.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,30 @@
import * as path from "../deps/std/path.ts";
import * as M from "../frame_metadata/mod.ts";
import { Decl, Files, getCodecPath, getName, getRawCodecPath, importSource, S } from "./utils.ts";
import { Files } from "./Files.ts";
import { CodegenProps } from "./mod.ts";
import { Decl, getCodecPath, getName, getRawCodecPath, S } from "./utils.ts";

export function createCodecVisitor(
tys: M.Ty[],
props: CodegenProps,
decls: Decl[],
typeVisitor: M.TyVisitor<S>,
files: Files,
) {
["import { $, $null, $era } from", S.string(importSource)];
const { tys } = props.metadata;
const namespaceImports = new Set<string>();
const codecs: S[] = [];

files.set("codecs.ts", {
getContent: () => [
"\n",
[
"import { ChainError, BitSequence, Era, $, $era, $null } from",
S.string(importSource),
S.string(path.relative(props.outDir, props.importSpecifier)),
],
[`import type * as t from "./mod.ts"`],
...codecs,
[
"export const _all: $.AnyCodec[] =",
S.array(tys.map((ty) => getName(getRawCodecPath(ty)))),
S.array(props.metadata.tys.map((ty) => getName(getRawCodecPath(ty)))),
],
],
});
Expand Down
68 changes: 19 additions & 49 deletions codegen/mod.ts
Original file line number Diff line number Diff line change
@@ -1,71 +1,41 @@
import { tsFormatter } from "../deps/dprint.ts";
import * as path from "../deps/std/path.ts";
import * as M from "../frame_metadata/mod.ts";
import { createCodecVisitor } from "./codecVisitor.ts";
import { Files } from "./Files.ts";
import { genMetadata } from "./genMetadata.ts";
import { createTypeVisitor } from "./typeVisitor.ts";
import { Decl, Files, importSource, printDecls, S } from "./utils.ts";
import { Decl, printDecls, S } from "./utils.ts";

export async function run(metadataFile: string, outputDir: string) {
const metadata = M.fromPrefixedHex(await Deno.readTextFile(metadataFile));
const output = codegen(metadata);
await writeOutput(outputDir, output);
export interface CodegenProps {
metadata: M.Metadata;
importSpecifier: string;
outDir: string;
harrysolovay marked this conversation as resolved.
Show resolved Hide resolved
}

export async function writeOutput(outputDir: string, output: Files) {
const errors = [];
try {
await Deno.remove(outputDir, { recursive: true });
} catch (e) {
if (!(e instanceof Deno.errors.NotFound)) {
throw e;
}
}
await Deno.mkdir(outputDir, { recursive: true });
for (const [relativePath, file] of output.entries()) {
const outputPath = path.join(outputDir, relativePath);
const content = S.toString(file.getContent());
try {
const formatted = tsFormatter.formatText("gen.ts", content);
await Deno.writeTextFile(outputPath, formatted);
} catch (e) {
await Deno.writeTextFile(outputPath, content);
errors.push(e);
}
}
if (errors.length) {
throw errors;
}
}

export function codegen(metadata: M.Metadata): Files {
export function codegen(props: CodegenProps): Files {
const decls: Decl[] = [];

const { tys } = metadata;

const files: Files = new Map();

const files = new Files(props.outDir);
decls.push({
path: "_",
code: [
"\n",
["import { ChainError, BitSequence, Era, $ } from", S.string(importSource)],
[
"import { ChainError, BitSequence, Era, $ } from",
S.string(path.relative(props.outDir, props.importSpecifier)),
harrysolovay marked this conversation as resolved.
Show resolved Hide resolved
],
[`import * as _codec from "./codecs.ts"`],
[`export { _metadata }`],
],
});

const typeVisitor = createTypeVisitor(tys, decls);
const codecVisitor = createCodecVisitor(tys, decls, typeVisitor, files);

for (const ty of metadata.tys) {
const typeVisitor = createTypeVisitor(props, decls);
const codecVisitor = createCodecVisitor(props, decls, typeVisitor, files);
for (const ty of props.metadata.tys) {
typeVisitor.visit(ty);
codecVisitor.visit(ty);
}

genMetadata(metadata, decls);

files.set("mod.ts", { getContent: () => printDecls(decls) });

genMetadata(props.metadata, decls);
files.set("mod.ts", {
getContent: () => printDecls(decls),
});
return files;
}
4 changes: 3 additions & 1 deletion codegen/typeVisitor.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import * as M from "../frame_metadata/mod.ts";
import { CodegenProps } from "./mod.ts";
import { Decl, getName, getPath, makeDocComment, S } from "./utils.ts";

export function createTypeVisitor(tys: M.Ty[], decls: Decl[]) {
export function createTypeVisitor(props: CodegenProps, decls: Decl[]) {
const { tys } = props.metadata;
return new M.TyVisitor<S>(tys, {
unitStruct(ty) {
return addTypeDecl(ty, "null");
Expand Down
5 changes: 0 additions & 5 deletions codegen/utils.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
import * as M from "../frame_metadata/mod.ts";

export const importSource = new URL("../mod.ts", import.meta.url).toString();

export type File = { getContent: () => S };
export type Files = Map<string, File>;

export type S = string | number | S[];

export namespace S {
Expand Down
5 changes: 2 additions & 3 deletions deno.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
},
"lint": {
"files": {
"exclude": ["**/mod.generated.js", "target"],
"exclude": ["target"],
"include": ["."]
},
"rules": {
Expand Down Expand Up @@ -37,7 +37,6 @@
"test": "deno task run test_ctx.ts deno test -A --no-check=remote -L=info --ignore=target --parallel",
"test:update": "deno task test -- -- --update",
"mdbook:watch": "mdbook watch -o",
"bench": "deno bench -A --no-check=remote --unstable",
"typegen": "deno task run typegen/typegen.ts"
"bench": "deno bench -A --no-check=remote --unstable"
}
}
11 changes: 7 additions & 4 deletions test_util/codegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@ const memo = new Map<TestConfig, Promise<CodegenModule>>();
export function importCodegen(config: TestConfig) {
return U.getOr(memo, config, async () => {
const metadata = U.throwIfError(await C.run(C.metadata(config)));
const outputDir = path.join(
const outDir = path.join(
path.dirname(path.fromFileUrl(import.meta.url)),
"../target/codegen/",
config.runtimeName,
);
const output = gen.codegen(metadata);
await gen.writeOutput(outputDir, output);
return await import(path.toFileUrl(path.join(outputDir, "mod.ts")).toString());
await gen.codegen({
importSpecifier: "./mod.ts",
metadata,
outDir,
}).write();
return await import(path.toFileUrl(path.join(outDir, "mod.ts")).toString());
});
}
1 change: 1 addition & 0 deletions words.txt
Original file line number Diff line number Diff line change
Expand Up @@ -186,3 +186,4 @@ tostring
framesystem
statemigration
xxhash
chainspec