Files
bDS/src/renderer/python/generatePythonApiModuleV1.ts

185 lines
4.7 KiB
TypeScript

import { BDS_PYTHON_API_CONTRACT_V1 } from './pythonApiContractV1';
const PYTHON_RESERVED_KEYWORDS = new Set([
'false',
'none',
'true',
'and',
'as',
'assert',
'async',
'await',
'break',
'class',
'continue',
'def',
'del',
'elif',
'else',
'except',
'finally',
'for',
'from',
'global',
'if',
'import',
'in',
'is',
'lambda',
'nonlocal',
'not',
'or',
'pass',
'raise',
'return',
'try',
'while',
'with',
'yield',
'match',
'case',
]);
function toSnakeCase(value: string): string {
return value
.replace(/([a-z0-9])([A-Z])/g, '$1_$2')
.replace(/[^a-zA-Z0-9]+/g, '_')
.replace(/^_+|_+$/g, '')
.toLowerCase();
}
function quotePython(value: string): string {
return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
}
function toPythonIdentifier(value: string): string {
let identifier = toSnakeCase(value);
if (!identifier) {
identifier = '_';
}
if (/^[0-9]/.test(identifier)) {
identifier = `_${identifier}`;
}
if (PYTHON_RESERVED_KEYWORDS.has(identifier)) {
identifier = `${identifier}_`;
}
return identifier;
}
function buildPythonMethod(method: {
method: string;
description: string;
params: Array<{ name: string; required: boolean }>;
}): string {
const [namespace, member] = method.method.split('.');
if (!namespace || !member) {
return '';
}
const pythonMethodName = toPythonIdentifier(member);
const pythonParams = method.params.map((param) => ({
sourceName: param.name,
pythonName: toPythonIdentifier(param.name),
required: param.required,
}));
const signature = pythonParams.length > 0
? `, ${pythonParams.map((param) => (param.required ? param.pythonName : `${param.pythonName}=None`)).join(', ')}`
: '';
const argsDict = method.params.length > 0
? `{ ${method.params.map((param, index) => `"${param.name}": ${pythonParams[index]?.pythonName}`).join(', ')} }`
: '{}';
return [
` async def ${pythonMethodName}(self${signature}):`,
` \"\"\"${quotePython(method.description)}\"\"\"`,
` return await self._transport.call("${method.method}", ${argsDict})`,
'',
].join('\n');
}
function buildPythonNamespaceClass(
namespace: string,
methods: Array<{ method: string; description: string; params: Array<{ name: string; required: boolean }> }>
): string {
const className = `${namespace[0].toUpperCase()}${namespace.slice(1)}Api`;
const methodBlocks = methods.map((method) => buildPythonMethod(method)).join('');
return [
`class ${className}:`,
' def __init__(self, transport):',
' self._transport = transport',
'',
methodBlocks.trimEnd(),
'',
].join('\n');
}
export function generatePythonApiModuleV1(): string {
const namespaceMap = new Map<string, Array<{ method: string; description: string; params: Array<{ name: string; required: boolean }> }>>();
for (const method of BDS_PYTHON_API_CONTRACT_V1.methods) {
const [namespace] = method.method.split('.');
if (!namespace) {
continue;
}
const entries = namespaceMap.get(namespace) ?? [];
entries.push({
method: method.method,
description: method.description,
params: method.params.map((param) => ({
name: param.name,
required: param.required,
})),
});
namespaceMap.set(namespace, entries);
}
const namespaceBlocks = Array.from(namespaceMap.entries())
.sort(([left], [right]) => left.localeCompare(right))
.map(([namespace, methods]) => buildPythonNamespaceClass(namespace, methods))
.join('\n');
const namespaceAssignments = Array.from(namespaceMap.keys())
.sort((left, right) => left.localeCompare(right))
.map((namespace) => ` self.${toPythonIdentifier(namespace)} = ${namespace[0].toUpperCase()}${namespace.slice(1)}Api(transport)`)
.join('\n');
return [
'# Auto-generated by generatePythonApiModuleV1.ts',
`# Contract version: ${BDS_PYTHON_API_CONTRACT_V1.version}`,
'',
'import json',
'',
'class BdsApiError(Exception):',
' pass',
'',
namespaceBlocks.trimEnd(),
'',
'class BdsApi:',
' def __init__(self, transport):',
' self._transport = transport',
namespaceAssignments,
'',
'class _Transport:',
' def __init__(self, call_impl):',
' self._call_impl = call_impl',
'',
' async def call(self, method, args):',
' raw_result = await self._call_impl(method, json.dumps(args))',
' if raw_result is None or raw_result == "":',
' return None',
' return json.loads(raw_result)',
'',
'def install_bds_api(call_impl):',
' _transport = _Transport(call_impl)',
' bds = BdsApi(_transport)',
' return bds',
'',
].join('\n');
}