cddl2py 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.release-it.ts +11 -0
- package/bin/cddl2py.js +5 -0
- package/build/cli.d.ts +2 -0
- package/build/cli.d.ts.map +1 -0
- package/build/cli.js +34 -0
- package/build/constants.d.ts +5 -0
- package/build/constants.d.ts.map +1 -0
- package/build/constants.js +28 -0
- package/build/index.d.ts +6 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +554 -0
- package/build/utils.d.ts +7 -0
- package/build/utils.d.ts.map +1 -0
- package/build/utils.js +12 -0
- package/package.json +39 -0
- package/src/cli.ts +42 -0
- package/src/constants.ts +32 -0
- package/src/index.ts +658 -0
- package/src/utils.ts +12 -0
- package/tests/__snapshots__/complex_types.test.ts.snap +81 -0
- package/tests/__snapshots__/group_choice.test.ts.snap +127 -0
- package/tests/__snapshots__/literals.test.ts.snap +15 -0
- package/tests/__snapshots__/mixin_union.test.ts.snap +65 -0
- package/tests/__snapshots__/mod.test.ts.snap +145 -0
- package/tests/__snapshots__/named_group_choice.test.ts.snap +37 -0
- package/tests/__snapshots__/transform.test.ts.snap +137 -0
- package/tests/__snapshots__/webdriver_local.test.ts.snap +921 -0
- package/tests/__snapshots__/webdriver_remote.test.ts.snap +1249 -0
- package/tests/complex_types.test.ts +92 -0
- package/tests/group_choice.test.ts +88 -0
- package/tests/literals.test.ts +63 -0
- package/tests/mixin_union.test.ts +80 -0
- package/tests/mod.test.ts +106 -0
- package/tests/named_group_choice.test.ts +82 -0
- package/tests/transform.test.ts +149 -0
- package/tests/transform_edge_cases.test.ts +265 -0
- package/tests/unknown.test.ts +72 -0
- package/tests/webdriver_local.test.ts +64 -0
- package/tests/webdriver_remote.test.ts +64 -0
- package/tsconfig.json +11 -0
package/.release-it.ts
ADDED
package/bin/cddl2py.js
ADDED
package/build/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AASA,wBAA8B,GAAG,CAAE,IAAI,WAAwB,sBAgC9D"}
|
package/build/cli.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import yargs from 'yargs';
|
|
4
|
+
import { parse } from 'cddl';
|
|
5
|
+
import { transform } from './index.js';
|
|
6
|
+
import { pkg } from './constants.js';
|
|
7
|
+
export default async function cli(argv = process.argv.slice(2)) {
|
|
8
|
+
const parser = yargs(argv)
|
|
9
|
+
.usage(`${pkg.name}\n${pkg.description}\n\nUsage:\ncddl2py ./path/to/spec.cddl > ./path/to/types.py`)
|
|
10
|
+
.epilog(`v${pkg.version}\nCopyright ${(new Date()).getFullYear()} ${pkg.author}`)
|
|
11
|
+
.version(pkg.version)
|
|
12
|
+
.option('p', {
|
|
13
|
+
alias: 'pydantic',
|
|
14
|
+
type: 'boolean',
|
|
15
|
+
description: 'Generate Pydantic BaseModel classes instead of TypedDict',
|
|
16
|
+
default: false
|
|
17
|
+
})
|
|
18
|
+
.help('help')
|
|
19
|
+
.alias('h', 'help')
|
|
20
|
+
.alias('v', 'version');
|
|
21
|
+
const args = await parser.argv;
|
|
22
|
+
if (args._.length === 0) {
|
|
23
|
+
parser.showHelp();
|
|
24
|
+
return process.exit(0);
|
|
25
|
+
}
|
|
26
|
+
const absoluteFilePath = path.resolve(process.cwd(), args._[0]);
|
|
27
|
+
const hasAccess = await fs.access(absoluteFilePath).then(() => true, () => false);
|
|
28
|
+
if (!hasAccess) {
|
|
29
|
+
console.error(`Couldn't find or access source CDDL file at "${absoluteFilePath}"`);
|
|
30
|
+
return process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
const ast = parse(absoluteFilePath);
|
|
33
|
+
console.log(transform(ast, { pydantic: args.p }));
|
|
34
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,GAAG,KAAqF,CAAA;AAErG,eAAO,MAAM,sBAAsB,QAA2E,CAAA;AAE9G,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAkBlD,CAAA;AAED,eAAO,MAAM,wBAAwB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAE3D,CAAA"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import url from 'node:url';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
const __dirname = url.fileURLToPath(new URL('.', import.meta.url));
|
|
5
|
+
export const pkg = JSON.parse(await fs.readFile(path.join(__dirname, '..', 'package.json'), 'utf-8'));
|
|
6
|
+
export const GENERATED_FILE_COMMENT = `# Auto-generated by cddl2py v${pkg.version}\n# Do not edit manually.\n`;
|
|
7
|
+
export const NATIVE_TYPE_MAP = {
|
|
8
|
+
any: 'Any',
|
|
9
|
+
number: 'Union[int, float]',
|
|
10
|
+
int: 'int',
|
|
11
|
+
uint: 'int',
|
|
12
|
+
nint: 'int',
|
|
13
|
+
float: 'float',
|
|
14
|
+
float16: 'float',
|
|
15
|
+
float32: 'float',
|
|
16
|
+
float64: 'float',
|
|
17
|
+
bool: 'bool',
|
|
18
|
+
bstr: 'bytes',
|
|
19
|
+
bytes: 'bytes',
|
|
20
|
+
tstr: 'str',
|
|
21
|
+
text: 'str',
|
|
22
|
+
str: 'str',
|
|
23
|
+
nil: 'None',
|
|
24
|
+
null: 'None',
|
|
25
|
+
};
|
|
26
|
+
export const PYDANTIC_NATIVE_TYPE_MAP = {
|
|
27
|
+
...NATIVE_TYPE_MAP,
|
|
28
|
+
};
|
package/build/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAIH,KAAK,UAAU,EAGlB,MAAM,MAAM,CAAA;AAKb,MAAM,WAAW,gBAAgB;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAA;CACrB;AASD,wBAAgB,SAAS,CAAE,WAAW,EAAE,UAAU,EAAE,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,MAAM,CAkBxF"}
|
package/build/index.js
ADDED
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
import { isCDDLArray, isGroup, isNamedGroupReference, isLiteralWithValue, isNativeTypeWithOperator, isUnNamedProperty, isPropertyReference, isRange, isVariable, pascalCase } from 'cddl';
|
|
2
|
+
import { snakeCase } from './utils.js';
|
|
3
|
+
import { pkg, NATIVE_TYPE_MAP } from './constants.js';
|
|
4
|
+
export function transform(assignments, options) {
|
|
5
|
+
const ctx = {
|
|
6
|
+
pydantic: options?.pydantic ?? false,
|
|
7
|
+
typingImports: new Set(),
|
|
8
|
+
typingExtensionsImports: new Set(),
|
|
9
|
+
pydanticImports: new Set(),
|
|
10
|
+
};
|
|
11
|
+
const blocks = [];
|
|
12
|
+
for (const assignment of assignments) {
|
|
13
|
+
const block = generateAssignment(assignment, ctx);
|
|
14
|
+
if (block) {
|
|
15
|
+
blocks.push(block);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return renderOutput(ctx, blocks);
|
|
19
|
+
}
|
|
20
|
+
function renderOutput(ctx, blocks) {
|
|
21
|
+
const lines = [];
|
|
22
|
+
lines.push(`# compiled with https://www.npmjs.com/package/cddl2py v${pkg.version}`);
|
|
23
|
+
lines.push('');
|
|
24
|
+
lines.push('from __future__ import annotations');
|
|
25
|
+
lines.push('');
|
|
26
|
+
if (ctx.typingImports.size > 0) {
|
|
27
|
+
lines.push(`from typing import ${[...ctx.typingImports].sort().join(', ')}`);
|
|
28
|
+
}
|
|
29
|
+
if (ctx.pydantic) {
|
|
30
|
+
if (ctx.pydanticImports.size > 0) {
|
|
31
|
+
lines.push(`from pydantic import ${[...ctx.pydanticImports].sort().join(', ')}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
else if (ctx.typingExtensionsImports.size > 0) {
|
|
35
|
+
lines.push(`from typing_extensions import ${[...ctx.typingExtensionsImports].sort().join(', ')}`);
|
|
36
|
+
}
|
|
37
|
+
if (ctx.typingImports.size > 0 || ctx.pydanticImports.size > 0 || ctx.typingExtensionsImports.size > 0) {
|
|
38
|
+
lines.push('');
|
|
39
|
+
}
|
|
40
|
+
lines.push(blocks.join('\n\n'));
|
|
41
|
+
lines.push('');
|
|
42
|
+
return lines.join('\n');
|
|
43
|
+
}
|
|
44
|
+
function generateAssignment(assignment, ctx) {
|
|
45
|
+
if (isVariable(assignment)) {
|
|
46
|
+
return generateVariable(assignment, ctx);
|
|
47
|
+
}
|
|
48
|
+
if (isGroup(assignment)) {
|
|
49
|
+
return generateGroup(assignment, ctx);
|
|
50
|
+
}
|
|
51
|
+
if (isCDDLArray(assignment)) {
|
|
52
|
+
return generateArrayAssignment(assignment, ctx);
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
// Variable
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
function generateVariable(v, ctx) {
|
|
60
|
+
const name = pascalCase(v.Name);
|
|
61
|
+
const propTypes = Array.isArray(v.PropertyType) ? v.PropertyType : [v.PropertyType];
|
|
62
|
+
const comments = formatLeadingComments(v.Comments);
|
|
63
|
+
if (propTypes.length === 1 && isRange(propTypes[0])) {
|
|
64
|
+
return `${comments}${name} = int`;
|
|
65
|
+
}
|
|
66
|
+
const types = propTypes.map(t => resolveType(t, ctx));
|
|
67
|
+
if (types.length === 1) {
|
|
68
|
+
return `${comments}${name} = ${types[0]}`;
|
|
69
|
+
}
|
|
70
|
+
ctx.typingImports.add('Union');
|
|
71
|
+
return `${comments}${name} = Union[${types.join(', ')}]`;
|
|
72
|
+
}
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
// Group
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
function generateGroup(group, ctx) {
|
|
77
|
+
const name = pascalCase(group.Name);
|
|
78
|
+
const properties = group.Properties;
|
|
79
|
+
const hasChoices = properties.some(p => Array.isArray(p));
|
|
80
|
+
const comments = formatLeadingComments(group.Comments);
|
|
81
|
+
if (hasChoices) {
|
|
82
|
+
return comments + generateGroupWithChoices(name, properties, ctx);
|
|
83
|
+
}
|
|
84
|
+
const props = properties;
|
|
85
|
+
if (props.length === 1) {
|
|
86
|
+
const prop = props[0];
|
|
87
|
+
const propType = Array.isArray(prop.Type) ? prop.Type : [prop.Type];
|
|
88
|
+
if (propType.length === 1 && Object.keys(NATIVE_TYPE_MAP).includes(prop.Name)) {
|
|
89
|
+
const keyType = NATIVE_TYPE_MAP[prop.Name];
|
|
90
|
+
if (keyType === 'Any') {
|
|
91
|
+
ctx.typingImports.add('Any');
|
|
92
|
+
}
|
|
93
|
+
const valType = resolveType(propType[0], ctx);
|
|
94
|
+
return `${comments}${name} = dict[${keyType}, ${valType}]`;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const mixins = props.filter(isUnNamedProperty);
|
|
98
|
+
const ownProps = props.filter(p => !isUnNamedProperty(p));
|
|
99
|
+
const simpleMixinBases = [];
|
|
100
|
+
const unionMixinGroups = [];
|
|
101
|
+
for (const mixin of mixins) {
|
|
102
|
+
if (Array.isArray(mixin.Type) && mixin.Type.length > 1) {
|
|
103
|
+
const unionTypes = mixin.Type.map(t => resolveType(t, ctx));
|
|
104
|
+
unionMixinGroups.push(unionTypes);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
const typeVal = Array.isArray(mixin.Type) ? mixin.Type[0] : mixin.Type;
|
|
108
|
+
if (isNamedGroupReference(typeVal)) {
|
|
109
|
+
simpleMixinBases.push(pascalCase(typeVal.Value));
|
|
110
|
+
}
|
|
111
|
+
else if (isGroup(typeVal) && !isNamedGroupReference(typeVal) && typeVal.Properties) {
|
|
112
|
+
const inlineGroup = typeVal;
|
|
113
|
+
const inlineProps = inlineGroup.Properties;
|
|
114
|
+
const inlineMixinBases = [];
|
|
115
|
+
for (const p of inlineProps) {
|
|
116
|
+
if (isUnNamedProperty(p)) {
|
|
117
|
+
const innerType = Array.isArray(p.Type) ? p.Type[0] : p.Type;
|
|
118
|
+
if (isNamedGroupReference(innerType)) {
|
|
119
|
+
inlineMixinBases.push(pascalCase(innerType.Value));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
simpleMixinBases.push(...inlineMixinBases);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (unionMixinGroups.length > 0) {
|
|
128
|
+
return comments + generateGroupWithUnionMixins(name, simpleMixinBases, unionMixinGroups, ownProps, ctx);
|
|
129
|
+
}
|
|
130
|
+
return comments + generateClass(name, simpleMixinBases, ownProps, ctx);
|
|
131
|
+
}
|
|
132
|
+
function generateGroupWithChoices(name, properties, ctx) {
|
|
133
|
+
const blocks = [];
|
|
134
|
+
const unionTypes = [];
|
|
135
|
+
let variantIndex = 0;
|
|
136
|
+
for (let i = 0; i < properties.length; i++) {
|
|
137
|
+
const prop = properties[i];
|
|
138
|
+
if (Array.isArray(prop)) {
|
|
139
|
+
const choiceOptions = [...prop];
|
|
140
|
+
const nextProp = properties[i + 1];
|
|
141
|
+
if (nextProp && !Array.isArray(nextProp)) {
|
|
142
|
+
choiceOptions.push(nextProp);
|
|
143
|
+
i++;
|
|
144
|
+
}
|
|
145
|
+
for (const option of choiceOptions) {
|
|
146
|
+
const typeVal = Array.isArray(option.Type) ? option.Type[0] : option.Type;
|
|
147
|
+
if (isUnNamedProperty(option)) {
|
|
148
|
+
if (isNamedGroupReference(typeVal)) {
|
|
149
|
+
unionTypes.push(pascalCase(typeVal.Value));
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
unionTypes.push(resolveType(typeVal, ctx));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
const variantName = `_${name}Variant${variantIndex}`;
|
|
157
|
+
variantIndex++;
|
|
158
|
+
blocks.push(generateClass(variantName, [], [option], ctx));
|
|
159
|
+
unionTypes.push(variantName);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
else if (isUnNamedProperty(prop)) {
|
|
164
|
+
const typeVal = Array.isArray(prop.Type) ? prop.Type[0] : prop.Type;
|
|
165
|
+
if (isNamedGroupReference(typeVal)) {
|
|
166
|
+
unionTypes.push(pascalCase(typeVal.Value));
|
|
167
|
+
}
|
|
168
|
+
else if (Array.isArray(prop.Type) && prop.Type.length > 1) {
|
|
169
|
+
for (const t of prop.Type) {
|
|
170
|
+
unionTypes.push(resolveType(t, ctx));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
unionTypes.push(resolveType(typeVal, ctx));
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
const variantName = `_${name}Variant${variantIndex}`;
|
|
179
|
+
variantIndex++;
|
|
180
|
+
blocks.push(generateClass(variantName, [], [prop], ctx));
|
|
181
|
+
unionTypes.push(variantName);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
if (unionTypes.length === 1) {
|
|
185
|
+
blocks.push(`${name} = ${unionTypes[0]}`);
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
ctx.typingImports.add('Union');
|
|
189
|
+
blocks.push(`${name} = Union[${unionTypes.join(', ')}]`);
|
|
190
|
+
}
|
|
191
|
+
return blocks.join('\n\n');
|
|
192
|
+
}
|
|
193
|
+
function generateGroupWithUnionMixins(name, simpleBases, unionGroups, ownProps, ctx) {
|
|
194
|
+
if (ownProps.length === 0 && simpleBases.length === 0) {
|
|
195
|
+
const allTypes = unionGroups.flat();
|
|
196
|
+
if (allTypes.length === 1) {
|
|
197
|
+
return `${name} = ${allTypes[0]}`;
|
|
198
|
+
}
|
|
199
|
+
ctx.typingImports.add('Union');
|
|
200
|
+
return `${name} = Union[${allTypes.join(', ')}]`;
|
|
201
|
+
}
|
|
202
|
+
const blocks = [];
|
|
203
|
+
const variantNames = [];
|
|
204
|
+
if (unionGroups.length === 1) {
|
|
205
|
+
const unionTypes = unionGroups[0];
|
|
206
|
+
if (ownProps.length > 0) {
|
|
207
|
+
const baseName = `_${name}Fields`;
|
|
208
|
+
blocks.push(generateClass(baseName, [], ownProps, ctx));
|
|
209
|
+
for (let i = 0; i < unionTypes.length; i++) {
|
|
210
|
+
const variantName = `_${name}Variant${i}`;
|
|
211
|
+
variantNames.push(variantName);
|
|
212
|
+
const bases = [baseName, unionTypes[i], ...simpleBases];
|
|
213
|
+
blocks.push(generateClass(variantName, bases, [], ctx));
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
for (let i = 0; i < unionTypes.length; i++) {
|
|
218
|
+
const variantName = `_${name}Variant${i}`;
|
|
219
|
+
variantNames.push(variantName);
|
|
220
|
+
const bases = [unionTypes[i], ...simpleBases];
|
|
221
|
+
blocks.push(generateClass(variantName, bases, [], ctx));
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
const allTypes = [...simpleBases, ...unionGroups.flat()];
|
|
227
|
+
ctx.typingImports.add('Union');
|
|
228
|
+
blocks.push(`${name} = Union[${allTypes.join(', ')}]`);
|
|
229
|
+
return blocks.join('\n\n');
|
|
230
|
+
}
|
|
231
|
+
ctx.typingImports.add('Union');
|
|
232
|
+
blocks.push(`${name} = Union[${variantNames.join(', ')}]`);
|
|
233
|
+
return blocks.join('\n\n');
|
|
234
|
+
}
|
|
235
|
+
// ---------------------------------------------------------------------------
|
|
236
|
+
// Array
|
|
237
|
+
// ---------------------------------------------------------------------------
|
|
238
|
+
function generateArrayAssignment(arr, ctx) {
|
|
239
|
+
const name = pascalCase(arr.Name);
|
|
240
|
+
const comments = formatLeadingComments(arr.Comments);
|
|
241
|
+
const values = arr.Values;
|
|
242
|
+
if (values.length === 0) {
|
|
243
|
+
ctx.typingImports.add('Any');
|
|
244
|
+
return `${comments}${name} = list[Any]`;
|
|
245
|
+
}
|
|
246
|
+
const firstVal = values[0];
|
|
247
|
+
if (Array.isArray(firstVal)) {
|
|
248
|
+
const options = firstVal.map(p => {
|
|
249
|
+
const t = Array.isArray(p.Type) ? p.Type[0] : p.Type;
|
|
250
|
+
return resolveType(t, ctx);
|
|
251
|
+
});
|
|
252
|
+
if (options.length === 1) {
|
|
253
|
+
return `${comments}${name} = list[${options[0]}]`;
|
|
254
|
+
}
|
|
255
|
+
ctx.typingImports.add('Union');
|
|
256
|
+
return `${comments}${name} = list[Union[${options.join(', ')}]]`;
|
|
257
|
+
}
|
|
258
|
+
const firstType = firstVal.Type;
|
|
259
|
+
const types = Array.isArray(firstType) ? firstType : [firstType];
|
|
260
|
+
if (types.length === 1 && isCDDLArray(types[0])) {
|
|
261
|
+
const innerArr = types[0];
|
|
262
|
+
const innerVal = innerArr.Values[0];
|
|
263
|
+
const innerTypes = Array.isArray(innerVal.Type) ? innerVal.Type : [innerVal.Type];
|
|
264
|
+
const typeStrs = innerTypes.map(v => resolveType(v, ctx));
|
|
265
|
+
if (typeStrs.length === 1) {
|
|
266
|
+
return `${comments}${name} = list[${typeStrs[0]}]`;
|
|
267
|
+
}
|
|
268
|
+
ctx.typingImports.add('Union');
|
|
269
|
+
return `${comments}${name} = list[Union[${typeStrs.join(', ')}]]`;
|
|
270
|
+
}
|
|
271
|
+
const typeStrs = types.map(t => resolveType(t, ctx));
|
|
272
|
+
if (typeStrs.length === 1) {
|
|
273
|
+
return `${comments}${name} = list[${typeStrs[0]}]`;
|
|
274
|
+
}
|
|
275
|
+
ctx.typingImports.add('Union');
|
|
276
|
+
return `${comments}${name} = list[Union[${typeStrs.join(', ')}]]`;
|
|
277
|
+
}
|
|
278
|
+
// ---------------------------------------------------------------------------
|
|
279
|
+
// Class generation (TypedDict or Pydantic BaseModel)
|
|
280
|
+
// ---------------------------------------------------------------------------
|
|
281
|
+
function generateClass(name, bases, props, ctx) {
|
|
282
|
+
const lines = [];
|
|
283
|
+
let classDecl;
|
|
284
|
+
if (ctx.pydantic) {
|
|
285
|
+
ctx.pydanticImports.add('BaseModel');
|
|
286
|
+
if (bases.length > 0) {
|
|
287
|
+
classDecl = `class ${name}(${bases.join(', ')}):`;
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
classDecl = `class ${name}(BaseModel):`;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
ctx.typingExtensionsImports.add('TypedDict');
|
|
295
|
+
if (bases.length > 0) {
|
|
296
|
+
classDecl = `class ${name}(${bases.join(', ')}):`;
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
classDecl = `class ${name}(TypedDict):`;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
lines.push(classDecl);
|
|
303
|
+
if (props.length === 0) {
|
|
304
|
+
lines.push(' pass');
|
|
305
|
+
return lines.join('\n');
|
|
306
|
+
}
|
|
307
|
+
for (const prop of props) {
|
|
308
|
+
const fieldLine = generateField(prop, ctx);
|
|
309
|
+
if (fieldLine) {
|
|
310
|
+
lines.push(fieldLine);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
if (lines.length === 1) {
|
|
314
|
+
lines.push(' pass');
|
|
315
|
+
}
|
|
316
|
+
return lines.join('\n');
|
|
317
|
+
}
|
|
318
|
+
function generateField(prop, ctx) {
|
|
319
|
+
if (isUnNamedProperty(prop)) {
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
const propName = snakeCase(prop.Name);
|
|
323
|
+
const cddlTypes = Array.isArray(prop.Type) ? prop.Type : [prop.Type];
|
|
324
|
+
const isOptional = prop.Occurrence.n === 0;
|
|
325
|
+
let typeStr;
|
|
326
|
+
const types = cddlTypes.map(t => resolveType(t, ctx));
|
|
327
|
+
if (types.length === 1) {
|
|
328
|
+
typeStr = types[0];
|
|
329
|
+
}
|
|
330
|
+
else {
|
|
331
|
+
ctx.typingImports.add('Union');
|
|
332
|
+
typeStr = `Union[${types.join(', ')}]`;
|
|
333
|
+
}
|
|
334
|
+
const inlineComment = prop.Comments
|
|
335
|
+
.filter(c => !c.Leading)
|
|
336
|
+
.map(c => c.Content.trim())
|
|
337
|
+
.join('; ');
|
|
338
|
+
const commentSuffix = inlineComment ? ` # ${inlineComment}` : '';
|
|
339
|
+
let defaultExpr = '';
|
|
340
|
+
if (prop.Operator && prop.Operator.Type === 'default') {
|
|
341
|
+
defaultExpr = formatDefaultValue(prop.Operator);
|
|
342
|
+
}
|
|
343
|
+
for (const t of cddlTypes) {
|
|
344
|
+
if (isPropertyReference(t) && t.Operator?.Type === 'default') {
|
|
345
|
+
const val = formatDefaultValue(t.Operator);
|
|
346
|
+
if (val) {
|
|
347
|
+
defaultExpr = val;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
if (ctx.pydantic) {
|
|
352
|
+
if (isOptional) {
|
|
353
|
+
ctx.typingImports.add('Optional');
|
|
354
|
+
if (defaultExpr) {
|
|
355
|
+
ctx.pydanticImports.add('Field');
|
|
356
|
+
return ` ${propName}: Optional[${typeStr}] = Field(default=${defaultExpr})${commentSuffix}`;
|
|
357
|
+
}
|
|
358
|
+
return ` ${propName}: Optional[${typeStr}] = None${commentSuffix}`;
|
|
359
|
+
}
|
|
360
|
+
if (defaultExpr) {
|
|
361
|
+
ctx.pydanticImports.add('Field');
|
|
362
|
+
return ` ${propName}: ${typeStr} = Field(default=${defaultExpr})${commentSuffix}`;
|
|
363
|
+
}
|
|
364
|
+
return ` ${propName}: ${typeStr}${commentSuffix}`;
|
|
365
|
+
}
|
|
366
|
+
if (isOptional) {
|
|
367
|
+
ctx.typingExtensionsImports.add('NotRequired');
|
|
368
|
+
return ` ${propName}: NotRequired[${typeStr}]${commentSuffix}`;
|
|
369
|
+
}
|
|
370
|
+
return ` ${propName}: ${typeStr}${commentSuffix}`;
|
|
371
|
+
}
|
|
372
|
+
// ---------------------------------------------------------------------------
|
|
373
|
+
// Type resolution
|
|
374
|
+
// ---------------------------------------------------------------------------
|
|
375
|
+
function resolveType(t, ctx) {
|
|
376
|
+
if (typeof t === 'string') {
|
|
377
|
+
const mapped = NATIVE_TYPE_MAP[t];
|
|
378
|
+
if (mapped) {
|
|
379
|
+
if (mapped === 'Any') {
|
|
380
|
+
ctx.typingImports.add('Any');
|
|
381
|
+
}
|
|
382
|
+
return mapped;
|
|
383
|
+
}
|
|
384
|
+
throw new Error(`Unknown native type: "${t}"`);
|
|
385
|
+
}
|
|
386
|
+
if (t.Type && typeof t.Type === 'string' && NATIVE_TYPE_MAP[t.Type]) {
|
|
387
|
+
const mapped = NATIVE_TYPE_MAP[t.Type];
|
|
388
|
+
if (mapped === 'Any') {
|
|
389
|
+
ctx.typingImports.add('Any');
|
|
390
|
+
}
|
|
391
|
+
return mapped;
|
|
392
|
+
}
|
|
393
|
+
if (isNativeTypeWithOperator(t) && NATIVE_TYPE_MAP[t.Type.Type]) {
|
|
394
|
+
const mapped = NATIVE_TYPE_MAP[t.Type.Type];
|
|
395
|
+
if (mapped === 'Any') {
|
|
396
|
+
ctx.typingImports.add('Any');
|
|
397
|
+
}
|
|
398
|
+
return mapped;
|
|
399
|
+
}
|
|
400
|
+
if (isPropertyReference(t) && t.Value === 'null') {
|
|
401
|
+
return 'None';
|
|
402
|
+
}
|
|
403
|
+
if (isGroup(t)) {
|
|
404
|
+
if (isNamedGroupReference(t)) {
|
|
405
|
+
return pascalCase(t.Value);
|
|
406
|
+
}
|
|
407
|
+
const group = t;
|
|
408
|
+
if (group.Properties) {
|
|
409
|
+
const props = group.Properties;
|
|
410
|
+
if (props.some(p => Array.isArray(p))) {
|
|
411
|
+
const options = [];
|
|
412
|
+
for (const choice of props) {
|
|
413
|
+
const subProps = Array.isArray(choice) ? choice : [choice];
|
|
414
|
+
if (subProps.length === 1 && isUnNamedProperty(subProps[0])) {
|
|
415
|
+
const subType = Array.isArray(subProps[0].Type) ? subProps[0].Type[0] : subProps[0].Type;
|
|
416
|
+
options.push(resolveType(subType, ctx));
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
419
|
+
if (subProps.every(isUnNamedProperty)) {
|
|
420
|
+
const tupleItems = subProps.map(p => {
|
|
421
|
+
const subType = Array.isArray(p.Type) ? p.Type[0] : p.Type;
|
|
422
|
+
return resolveType(subType, ctx);
|
|
423
|
+
});
|
|
424
|
+
ctx.typingImports.add('Tuple');
|
|
425
|
+
options.push(`Tuple[${tupleItems.join(', ')}]`);
|
|
426
|
+
continue;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
if (options.length > 1) {
|
|
430
|
+
ctx.typingImports.add('Union');
|
|
431
|
+
return `Union[${options.join(', ')}]`;
|
|
432
|
+
}
|
|
433
|
+
if (options.length === 1) {
|
|
434
|
+
return options[0];
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
if (props.every(isUnNamedProperty)) {
|
|
438
|
+
const items = props.map(p => {
|
|
439
|
+
const subType = Array.isArray(p.Type) ? p.Type[0] : p.Type;
|
|
440
|
+
return resolveType(subType, ctx);
|
|
441
|
+
});
|
|
442
|
+
if (items.length === 1) {
|
|
443
|
+
return items[0];
|
|
444
|
+
}
|
|
445
|
+
ctx.typingImports.add('Tuple');
|
|
446
|
+
return `Tuple[${items.join(', ')}]`;
|
|
447
|
+
}
|
|
448
|
+
if (props.length === 1 && Object.keys(NATIVE_TYPE_MAP).includes(props[0].Name)) {
|
|
449
|
+
const keyType = NATIVE_TYPE_MAP[props[0].Name];
|
|
450
|
+
if (keyType === 'Any') {
|
|
451
|
+
ctx.typingImports.add('Any');
|
|
452
|
+
}
|
|
453
|
+
const valType = resolveType(props[0].Type[0], ctx);
|
|
454
|
+
return `dict[${keyType}, ${valType}]`;
|
|
455
|
+
}
|
|
456
|
+
ctx.typingImports.add('Any');
|
|
457
|
+
return 'dict[str, Any]';
|
|
458
|
+
}
|
|
459
|
+
throw new Error(`Unknown group type: ${JSON.stringify(t)}`);
|
|
460
|
+
}
|
|
461
|
+
if (isLiteralWithValue(t)) {
|
|
462
|
+
ctx.typingImports.add('Literal');
|
|
463
|
+
if (typeof t.Value === 'string') {
|
|
464
|
+
return `Literal["${t.Value}"]`;
|
|
465
|
+
}
|
|
466
|
+
if (typeof t.Value === 'number') {
|
|
467
|
+
return `Literal[${t.Value}]`;
|
|
468
|
+
}
|
|
469
|
+
if (typeof t.Value === 'boolean') {
|
|
470
|
+
return `Literal[${t.Value ? 'True' : 'False'}]`;
|
|
471
|
+
}
|
|
472
|
+
if (typeof t.Value === 'bigint') {
|
|
473
|
+
return `Literal[${t.Value.toString()}]`;
|
|
474
|
+
}
|
|
475
|
+
if (t.Value === null) {
|
|
476
|
+
return 'None';
|
|
477
|
+
}
|
|
478
|
+
throw new Error(`Unsupported literal: ${JSON.stringify(t)}`);
|
|
479
|
+
}
|
|
480
|
+
if (isCDDLArray(t)) {
|
|
481
|
+
const arrValues = t.Values;
|
|
482
|
+
if (arrValues.length === 0) {
|
|
483
|
+
ctx.typingImports.add('Any');
|
|
484
|
+
return 'list[Any]';
|
|
485
|
+
}
|
|
486
|
+
const firstVal = arrValues[0];
|
|
487
|
+
const innerTypes = Array.isArray(firstVal.Type) ? firstVal.Type : [firstVal.Type];
|
|
488
|
+
const typeStrs = innerTypes.map(v => resolveType(v, ctx));
|
|
489
|
+
if (typeStrs.length === 1) {
|
|
490
|
+
return `list[${typeStrs[0]}]`;
|
|
491
|
+
}
|
|
492
|
+
ctx.typingImports.add('Union');
|
|
493
|
+
return `list[Union[${typeStrs.join(', ')}]]`;
|
|
494
|
+
}
|
|
495
|
+
if (isRange(t)) {
|
|
496
|
+
return 'int';
|
|
497
|
+
}
|
|
498
|
+
if (isPropertyReference(t) && t.Type === 'range') {
|
|
499
|
+
return 'int';
|
|
500
|
+
}
|
|
501
|
+
if (isNativeTypeWithOperator(t) && isNamedGroupReference(t.Type)) {
|
|
502
|
+
return pascalCase(t.Type.Value);
|
|
503
|
+
}
|
|
504
|
+
if (isPropertyReference(t)) {
|
|
505
|
+
const ref = t;
|
|
506
|
+
if (ref.Type === 'group_array' && typeof ref.Value === 'string') {
|
|
507
|
+
return `list[${pascalCase(ref.Value)}]`;
|
|
508
|
+
}
|
|
509
|
+
if (ref.Type === 'tag') {
|
|
510
|
+
const tag = ref.Value;
|
|
511
|
+
const mapped = NATIVE_TYPE_MAP[tag.TypePart];
|
|
512
|
+
if (mapped) {
|
|
513
|
+
if (mapped === 'Any') {
|
|
514
|
+
ctx.typingImports.add('Any');
|
|
515
|
+
}
|
|
516
|
+
return mapped;
|
|
517
|
+
}
|
|
518
|
+
return pascalCase(tag.TypePart);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
throw new Error(`Unknown type: ${JSON.stringify(t)}`);
|
|
522
|
+
}
|
|
523
|
+
// ---------------------------------------------------------------------------
|
|
524
|
+
// Helpers
|
|
525
|
+
// ---------------------------------------------------------------------------
|
|
526
|
+
function formatLeadingComments(comments) {
|
|
527
|
+
const leading = comments.filter(c => c.Leading);
|
|
528
|
+
if (leading.length === 0) {
|
|
529
|
+
return '';
|
|
530
|
+
}
|
|
531
|
+
return leading.map(c => `# ${c.Content}`).join('\n') + '\n';
|
|
532
|
+
}
|
|
533
|
+
function formatDefaultValue(operator) {
|
|
534
|
+
if (operator.Type !== 'default') {
|
|
535
|
+
return '';
|
|
536
|
+
}
|
|
537
|
+
const val = operator.Value;
|
|
538
|
+
if (val === 'null') {
|
|
539
|
+
return 'None';
|
|
540
|
+
}
|
|
541
|
+
const ref = val;
|
|
542
|
+
if (ref.Type === 'literal') {
|
|
543
|
+
if (typeof ref.Value === 'string') {
|
|
544
|
+
return `"${ref.Value}"`;
|
|
545
|
+
}
|
|
546
|
+
if (typeof ref.Value === 'number') {
|
|
547
|
+
return String(ref.Value);
|
|
548
|
+
}
|
|
549
|
+
if (typeof ref.Value === 'boolean') {
|
|
550
|
+
return ref.Value ? 'True' : 'False';
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
return '';
|
|
554
|
+
}
|
package/build/utils.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAM9C"}
|