Skip to content

Commit 269eabc

Browse files
committed
Initial commit
0 parents  commit 269eabc

16 files changed

+4319
-0
lines changed

.gitignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/.idea
2+
3+
4+
# built files
5+
6+
/py_ast_json_to_ts.js
7+
/python_nodes.js
8+
# Project exclude paths
9+
/node_modules/

package-lock.json

Lines changed: 2061 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"name": "python-typeddict-to-typscript-interface",
3+
"version": "1.0.0",
4+
"description": "Convert python TypedDict's and type hints to typescript interface",
5+
"scripts": {
6+
"test": "mocha -r ts-node/register tests/**/*.test.ts",
7+
"build": "tsc py_ast_json_to_ts.ts",
8+
"coverage": "nyc -r lcov -e .ts -x \"*.test.ts\" npm run test"
9+
},
10+
"bin": {
11+
"ptdtts": "py_ast_json_to_ts.js"
12+
},
13+
"keywords": [],
14+
"author": "",
15+
"license": "ISC",
16+
"dependencies": {
17+
"argparse": "^2.0.1",
18+
"temp": "^0.9.4",
19+
"typescript": "^4.1.2"
20+
},
21+
"devDependencies": {
22+
"@tsconfig/node12": "^1.0.7",
23+
"@types/argparse": "^2.0.4",
24+
"@types/chai": "^4.2.14",
25+
"@types/mocha": "^8.2.0",
26+
"@types/node": "^14.14.12",
27+
"@types/temp": "^0.8.34",
28+
"chai": "^4.2.0",
29+
"mocha": "^8.2.1",
30+
"nyc": "^15.1.0",
31+
"ts-node": "^9.1.1"
32+
}
33+
}

py_ast_json_to_ts.ts

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
#!/usr/bin/env node
2+
3+
import * as fs from 'fs'
4+
import {PathLike} from 'fs'
5+
import * as py from "./python_nodes";
6+
7+
import {ArgumentParser} from 'argparse'
8+
import * as temp from 'temp'
9+
import {spawn} from 'child_process'
10+
import * as path from "path"
11+
import ts = require("typescript");
12+
13+
14+
const {version} = require('./package.json');
15+
16+
17+
/**
18+
* Note we only care about module level Python classes, that is, for example, inner nested classes will be ignored
19+
* @param source a xx_ast.json converted from xx.py by py_to_ast_json.py
20+
*/
21+
export function extractModuleLevelTypedDicts(source: PathLike): py.ClassDef[] {
22+
const buffer = fs.readFileSync(source)
23+
// correct me if I'm wrong: the source has to be a module
24+
const _moduleObj = JSON.parse(buffer.toString())
25+
const module = py.Node.Create(_moduleObj)
26+
27+
if (! py.Node.IsType(module, py.Module)){
28+
console.error("Failed to parse python module")
29+
process.exit(1)
30+
}
31+
32+
33+
34+
const ret: py.ClassDef[] = []
35+
36+
for (const child of (<py.Module>module).body) {
37+
if (py.Node.IsType(child, py.ClassDef)) {
38+
if (child.isSubclassOfTypedDict) ret.push(<py.ClassDef>child)
39+
}
40+
}
41+
return ret
42+
43+
}
44+
45+
46+
/**
47+
* @param source_ast_json The source json file of Python ast
48+
*/
49+
export function convertTypedDicts(source_ast_json: PathLike): ts.InterfaceDeclaration[] {
50+
return extractModuleLevelTypedDicts(source_ast_json).map(typedDict => typedDict.transform())
51+
}
52+
53+
54+
/**
55+
* @param source_ast_json The source json file of Python ast
56+
* @param target The target typescript file. If omitted, will log to stdout
57+
*/
58+
export function convert(source_ast_json: PathLike, target?: PathLike) {
59+
const interfaceDeclarations = extractModuleLevelTypedDicts(source_ast_json).map(typedDict => typedDict.transform())
60+
61+
62+
let result = ""
63+
64+
for (const interfaceDeclaration of interfaceDeclarations) {
65+
66+
const resultFile = ts.createSourceFile("someFileName.ts", "", ts.ScriptTarget.Latest, /*setParentNodes*/ false, ts.ScriptKind.TS);
67+
const printer = ts.createPrinter({newLine: ts.NewLineKind.LineFeed});
68+
69+
result += printer.printNode(ts.EmitHint.Unspecified, interfaceDeclaration, resultFile) + "\n"
70+
71+
}
72+
73+
if (target) {
74+
fs.writeFileSync(target, result)
75+
} else {
76+
console.log(result)
77+
}
78+
79+
80+
console.log("Conversion Successful!")
81+
82+
83+
}
84+
85+
if (require.main === module) {
86+
87+
88+
const parser = new ArgumentParser({
89+
description: "Convert Python TypedDict to Typescript Interface"
90+
});
91+
92+
parser.add_argument('-v', '--version', {action: 'version', version});
93+
parser.add_argument('input_file', {help: 'Python file that has TypedDict classes in it'});
94+
parser.add_argument('output_file', {help: 'Output typescript file'});
95+
parser.add_argument('--python_interpreter', '-p', {help: 'Path to python interpreter', default: "python3"});
96+
97+
98+
const args = parser.parse_args()
99+
100+
console.log("Thanks for using, this is in an experimental stage. Feature requests/ pull requests are welcomed")
101+
102+
// Automatically track and cleanup files at exit
103+
temp.track()
104+
105+
// Process the data (note: error handling omitted)
106+
temp.open('py_ast', function (err, info) {
107+
if (!err) {
108+
const dumpPythonASTProc = spawn(args.python_interpreter, [path.resolve(__dirname, "py_to_ast_json.py"),args.input_file,info.path], {shell:true})
109+
dumpPythonASTProc.stdout.on('data', data=>console.log(data.toString()))
110+
dumpPythonASTProc.stderr.on('data', data=>console.error(data.toString()))
111+
dumpPythonASTProc.on('close', code=>{
112+
convert(info.path, args.output_file)
113+
return process.exit(code)
114+
})
115+
116+
117+
fs.close(info.fd, function (err) {
118+
});
119+
}
120+
});
121+
122+
}
123+
124+
125+
// const resultFile = ts.createSourceFile("someFileName.ts", "", ts.ScriptTarget.Latest, /*setParentNodes*/ false, ts.ScriptKind.TS);
126+
// const printer = ts.createPrinter({newLine: ts.NewLineKind.LineFeed});
127+
//
128+
// const result = printer.printNode(ts.EmitHint.Unspecified, makeFactorialFunction(), resultFile);
129+
//
130+
// console.log(result);
131+
132+
133+

0 commit comments

Comments
 (0)