Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs(website): move typegraphs in separate folder + add ts version #552

Merged
merged 49 commits into from
Jan 24, 2024
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
4b9d6bb
chore(website): replace generic names t.py
michael-0acf4 Jan 12, 2024
4a20dce
fix(website): make sure all typegraphs have different names/filenames
michael-0acf4 Jan 12, 2024
e53ce5a
Merge branch 'main' into move-typegraphs-in-separate-folder
michael-0acf4 Jan 15, 2024
4fedc12
chore(website): move typegraphs and fix all references
michael-0acf4 Jan 15, 2024
f3a29d3
fix(website): deno module files
michael-0acf4 Jan 15, 2024
418db23
feat(dev): init typegraph converter tg-py2ts tool
michael-0acf4 Jan 15, 2024
38dc0ba
feat(tg-py2ts): translate typegraph body
michael-0acf4 Jan 16, 2024
512a32b
chore(website): wip translation
michael-0acf4 Jan 16, 2024
f97755d
Merge branch 'main' into move-typegraphs-in-separate-folder
michael-0acf4 Jan 17, 2024
12d6eeb
fix(website): react import squiggles + replace <h[0-9]> with @theme/H…
michael-0acf4 Jan 17, 2024
8d5145d
chore(website): wip translation
michael-0acf4 Jan 17, 2024
9a0d991
Merge branch 'main' into move-typegraphs-in-separate-folder
michael-0acf4 Jan 18, 2024
5972835
fix(tg-pyt2ts): better implementation
michael-0acf4 Jan 18, 2024
195edd5
refactor(tg-py2ts): cleanups
michael-0acf4 Jan 18, 2024
055950e
fix(examples): update sdk version to 0.3.2
michael-0acf4 Jan 18, 2024
fd156a7
fix(test): force any type on argument
michael-0acf4 Jan 18, 2024
cfa927e
feat(node): add missing static
michael-0acf4 Jan 18, 2024
fa34eed
Merge branch 'fix-template' into move-typegraphs-in-separate-folder
michael-0acf4 Jan 18, 2024
89e2641
chore(website): translate remaining typegraphs
michael-0acf4 Jan 18, 2024
448f38d
feat(node): add rate
michael-0acf4 Jan 18, 2024
a8da507
refactor(node): better syntax
michael-0acf4 Jan 18, 2024
4bc1c67
Merge branch 'fix-template' into move-typegraphs-in-separate-folder
michael-0acf4 Jan 18, 2024
63ec3af
fix(website): uncomment rate declaration
michael-0acf4 Jan 18, 2024
3d9f17b
Merge branch 'main' into move-typegraphs-in-separate-folder
michael-0acf4 Jan 19, 2024
2f54583
feat(website): handle multiple lang
michael-0acf4 Jan 19, 2024
a74bd4c
feat(website): display typescript version
michael-0acf4 Jan 19, 2024
78fb484
fix(website/ci): dist requirement
michael-0acf4 Jan 19, 2024
3cac1bd
fix(ci): missing dist
michael-0acf4 Jan 19, 2024
e6392ca
Revert "fix(website/ci): dist requirement"
michael-0acf4 Jan 19, 2024
53cbcbd
fix(ci): use published version
michael-0acf4 Jan 19, 2024
60de318
fix(website): small fixes
michael-0acf4 Jan 22, 2024
1005cc5
test(website): compare typegraph pairs
michael-0acf4 Jan 22, 2024
0ffb0da
fix(website): add cors
michael-0acf4 Jan 22, 2024
a6fe331
fix(website): env fetch and typos
michael-0acf4 Jan 22, 2024
b3086e8
test(website): wip
michael-0acf4 Jan 22, 2024
0766ae9
test(website): wip
michael-0acf4 Jan 22, 2024
066a798
test(website): wip
michael-0acf4 Jan 22, 2024
5d73a63
test(website): math, iam-provider, graphql, roadmap-policies
michael-0acf4 Jan 22, 2024
8a57361
fix(pre-commit): exclude typegraph folder
michael-0acf4 Jan 22, 2024
742c460
fix(pre-commit): lint errors
michael-0acf4 Jan 22, 2024
6e1797c
test(website): enable skipped tests
michael-0acf4 Jan 22, 2024
e086005
merge branch 'main'
michael-0acf4 Jan 23, 2024
428b966
fix(website/random): index finalization behaves differently on each f…
michael-0acf4 Jan 23, 2024
b924b8b
fix(website): prisma comparison
michael-0acf4 Jan 23, 2024
ffb5f19
test(website): fix remaining typegraphs
michael-0acf4 Jan 23, 2024
ad5a64e
chore(website): move website/typegraphs to examples/
michael-0acf4 Jan 23, 2024
e984f3f
chore(website): fix paths
michael-0acf4 Jan 23, 2024
af6d15c
fix(website/whiz): cleanups
michael-0acf4 Jan 23, 2024
3dbac94
Merge branch 'main' into move-typegraphs-in-separate-folder
michael-0acf4 Jan 23, 2024
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
1 change: 1 addition & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,4 @@ repos:
- ts
- tsx
files: ^website/
exclude: website/typegraphs
2 changes: 2 additions & 0 deletions dev/lock.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ dev:
'( WASM_OPT_VERSION: ").+(")': WASM_OPT_VERSION
website/docusaurus.config.js:
'( tagline: ").+(",)': TAGLINE
website/package.json:
'(\s*"@typegraph\/sdk"\s*:\s*"\^).+(",?)': PUBLISHED_VERSION
examples/**/compose.yml:
'( image: ghcr.io/metatypedev/typegate:v).+()': METATYPE_VERSION
'**/pyproject.toml':
Expand Down
335 changes: 335 additions & 0 deletions dev/tg-py2ts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,335 @@
// Copyright Metatype OÜ, licensed under the Elastic License 2.0.
// SPDX-License-Identifier: Elastic-2.0

/**
* Translate typegraph in python to deno (experimental) for version 0.3.2
* A better implementation would be to
* (1) parse the python source directly and 1-1 map the imports/functions + refer to the ambient node sdk to validate the imports or attempt to autoresolve them.
* (2) emit typescript code directly from the serialized json.
*
* Usage:
* deno run -A --config=typegate/deno.jsonc dev/tg-py2ts.ts --file <file.py> [--force]
*/

import { basename, dirname, join } from "std/path/mod.ts";
import { parseFlags, resolve } from "./deps.ts";
import { camelCase, Cursor, findCursors, nextMatch } from "./utils.ts";

const args = parseFlags(Deno.args, {
string: ["file"],
boolean: ["force", "print"],
});

if (!args.file) {
console.error("No typegraph was given");
Deno.exit(-1);
}

// Concept:
// source => step1 => step2(input step1) => .. => output

type ReplaceStep = { description: string; apply: (text: string) => string };
type Failure = { error: Error | string; stepDescription: string };
type StepResult = { output: string; errors: Array<Failure> };

/**
* Capture args string in function-calls
*/
function captureInsideParenthesis(
text: string,
prefix: string | RegExp,
offset?: number, // inclusive
): Cursor | null {
const res = nextMatch(text, prefix, offset);

if (res == null) {
return null;
}

const searchStart = res.end + 1;
let startPos = null;
let parenthStack = 0;
let lastOpenedParenth = -1;
let expr = "";
for (let i = searchStart; i < text.length; i++) {
const char = text.charAt(i);
const isParenthesis = char == "(" || char == ")";
if (lastOpenedParenth < 0 && !isParenthesis) {
if (/\s/.test(char)) {
continue; // prefix\s*()
} else {
break; // prefix\s*something\s*()
}
}

if (isParenthesis) {
if (char == "(") {
lastOpenedParenth = i;
parenthStack += 1;
} else {
parenthStack -= 1;
}
// first "(" has closed or we reached a premature end
if (parenthStack <= 0) break;
}
if (i > searchStart) {
startPos = startPos == null ? i : startPos;
expr += char;
}
}

// no-op
if (lastOpenedParenth == null) {
return null;
}

if (parenthStack != 0) {
const peekRadius = 10;
throw new Error(
`invalid parenthesis near .. ${
text.substring(
Math.max(0, lastOpenedParenth - peekRadius),
Math.min(text.length, lastOpenedParenth + peekRadius),
).replace(/[\n\r]/g, "\\n")
.trim()
} ..`,
);
}

const start = startPos ?? lastOpenedParenth; // handle 0 length epxr: prefix()
return {
start,
end: start + expr.length,
length: expr.length,
match: expr,
};
}

// Concept:
// source => step1 => step2(input step1) => .. => output
const chain: Array<ReplaceStep> = [
{
description: "imports",
apply(text: string) {
return text.replace(
/from\s+(.+?)\s+import\s+(.+,?)\s/g,
(m: string, pkg, imp) => {
if (typeof pkg != "string") {
throw new Error(
`package expr invalid at "${m}", got ${typeof pkg}`,
);
}
if (typeof imp != "string") {
throw new Error(
`import expr invalid at "${m}", got ${typeof imp}`,
);
}

const pkgSplit = pkg.split(".");
const [start, ...rem] = pkgSplit;
const first = start == "typegraph" ? "" : start;
const relPath = `${pkgSplit.length == 1 ? "/index" : ""}${
[first, ...rem]
.join("/")
}`;
const imports = imp.split(/\s*,\s*/)
.map(camelCase)
.join(", ");
return `import { ${imports} } from "@typegraph/sdk${relPath}.js"\n`;
},
);
},
},
{
description: "Translate inline comments",
apply(text: string) {
return text
.replace(/#(.*)(\s*?)/g, (_, start, end) => `//${start}${end}`);
},
},
{
description: "Translate typegraph body",
apply(text: string) {
// match name near def
const tmp = text.match(/def\s+(\w*)\(.*\)\s*\:/);
if (tmp == null) {
throw new Error("could not extract typegraph name");
}
const [tgDefExpr, tgName] = tmp!;
const defOffset = text.lastIndexOf(tgDefExpr);
const body = text.substring(defOffset + tgDefExpr.length);

// match <expr> in @typegraph(<expr>)
const prefixTg = "@typegraph";
const cursor = text.lastIndexOf(prefixTg) + prefixTg.length;
if (cursor < 0) {
throw new Error(`could not find ${prefixTg}`);
}

const name = tgName.replace(/_+/g, "-");
const nextParenthesis = captureInsideParenthesis(text, prefixTg) ??
{ match: "" };
const config = nextParenthesis.match.replace(
/=/g,
":",
);
const header = text.substring(0, text.lastIndexOf(prefixTg));

return `${header}\ntypegraph({\nname: "${name}",\n${config}}, (g) => \n{${body}\n});`;
},
},
{
description: "Function name on an object: foo.some_func => foo.someFunc",
apply(text: string) {
return text
.replace(
/\.(\w+_)+?(\w+)/g,
camelCase,
);
},
},
{
description: "Expose expression",
apply(text: string) {
const exposed = captureInsideParenthesis(text, "g.expose") ??
{ match: "" };
return text.replace(
exposed.match,
`{${exposed.match.replace(/=/g, ":")}}`,
);
},
},
{
description: "Keyword translation",
apply(text: string) {
const replMap = Object.entries({
"True": "true",
"False": "false",
"None": "null",
});
return replMap
.reduce((prev, [tk, repl]) => prev.replaceAll(tk, repl), text);
},
},
{
description: "Scalar type argument translation",
apply(text: string) {
const objectify = (list: Array<[string, string]>) => {
return `{${list.map(([k, v]) => `${k}: ${v}`).join(", ")}}`;
};
const prefix = /t\.(integer|float|string|boolean|uuid)/g;
const cursors = findCursors(text, prefix)
.map(({ start }) =>
captureInsideParenthesis(text, prefix, start) ?? { match: "" }
);

return cursors.reduce((prev, { match }) => {
if (match == "") return prev;
const basic = ["min", "max", "xmin", "xmax"];
const splits = match.split(",")
.map((arg) => arg.split("="));
const left = [] as Array<[string, string]>;
const right = [] as Array<[string, string]>;
for (const [k, v] of splits) {
const ptr = basic.includes(k.trim()) ? left : right;
ptr.push([camelCase(k.trim()), v]);
}
return prev.replace(
match,
`${objectify(left)}, ${objectify(right)}`,
);
}, text);
},
},
{
description: "Comment name=..",
apply(text: string) {
// struc({}, name=..) => struct({}/*rename("..")*/)
const prefix = "t.struct";
return findCursors(text, prefix)
.map(({ start }) => {
const insideParenth = captureInsideParenthesis(text, prefix, start) ??
{ match: "" };
return insideParenth.match.match(/,?\s*name\s*=\s*("\w+")/);
}).reduce(
(prev, rename) =>
rename === null ? prev : ({
value: prev.value.replace(
rename[0],
`/*rename(${rename[1].trim()})*/`,
),
}),
{ value: text },
).value;
},
},
// {
// description: "Variable assignements",
// apply(text: string) {
// // at this stage g.expose is already translated
// return text.split(/\n/g).map((line) => {
// // assignement test
// if (!/\s*\w+\s*=.+\s/.test(line)) {
// return line;
// }
// const [left, right] = line.split("=");
// if (right && !/config/.test(left)) {
// const indent = left.match(/\s*/)?.[0] ?? "";
// return `${indent}const ${left.trim()} = ${right.trim()}`;
// }
// return line;
// }).join("\n");
// },
// },
// {
// description: "Translate multiline comments",
// apply(text: string) {
// return text;
// },
// },
// {
// description: "Translate multiline string",
// apply(text: string) {
// return text;
// },
// },
];

const path = resolve(args.file);
const source = Deno.readTextFileSync(path);

const result = chain
.reduce((prev: StepResult, step: ReplaceStep) => {
let { output, errors } = prev;
try {
output = step.apply(output);
} catch (error) {
errors = [...errors, {
error,
stepDescription: step.description,
}];
}
return { output, errors };
}, { output: source, errors: [] } as StepResult);

if (!args.force && result.errors.length > 0) {
console.error(
"Failed the following steps:\n",
result
.errors
.map(({ stepDescription, error }) =>
`- ${stepDescription}: "${
typeof error == "string" ? error : error.message
}"`
)
.join("\n"),
);
Deno.exit(-2);
}

if (args.print) {
console.log(result.output);
} else {
const outputFile = basename(path).replace(/\.py$/, ".ts");
Deno.writeTextFileSync(join(dirname(path), outputFile), result.output);
}
2 changes: 1 addition & 1 deletion dev/tree-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

/**
* Usage:
* deno run -A dev/tree-view.ts --confg typegate/deno.json [<options>] <file.py>
* deno run -A dev/tree-view.ts --config=typegate/deno.jsonc [<options>] <file.py>
*
* Options:
* --depth <N> The depth of the tree
Expand Down
Loading
Loading