185 lines
4.7 KiB
TypeScript
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');
|
|
} |