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.
Files changed (40) hide show
  1. package/.release-it.ts +11 -0
  2. package/bin/cddl2py.js +5 -0
  3. package/build/cli.d.ts +2 -0
  4. package/build/cli.d.ts.map +1 -0
  5. package/build/cli.js +34 -0
  6. package/build/constants.d.ts +5 -0
  7. package/build/constants.d.ts.map +1 -0
  8. package/build/constants.js +28 -0
  9. package/build/index.d.ts +6 -0
  10. package/build/index.d.ts.map +1 -0
  11. package/build/index.js +554 -0
  12. package/build/utils.d.ts +7 -0
  13. package/build/utils.d.ts.map +1 -0
  14. package/build/utils.js +12 -0
  15. package/package.json +39 -0
  16. package/src/cli.ts +42 -0
  17. package/src/constants.ts +32 -0
  18. package/src/index.ts +658 -0
  19. package/src/utils.ts +12 -0
  20. package/tests/__snapshots__/complex_types.test.ts.snap +81 -0
  21. package/tests/__snapshots__/group_choice.test.ts.snap +127 -0
  22. package/tests/__snapshots__/literals.test.ts.snap +15 -0
  23. package/tests/__snapshots__/mixin_union.test.ts.snap +65 -0
  24. package/tests/__snapshots__/mod.test.ts.snap +145 -0
  25. package/tests/__snapshots__/named_group_choice.test.ts.snap +37 -0
  26. package/tests/__snapshots__/transform.test.ts.snap +137 -0
  27. package/tests/__snapshots__/webdriver_local.test.ts.snap +921 -0
  28. package/tests/__snapshots__/webdriver_remote.test.ts.snap +1249 -0
  29. package/tests/complex_types.test.ts +92 -0
  30. package/tests/group_choice.test.ts +88 -0
  31. package/tests/literals.test.ts +63 -0
  32. package/tests/mixin_union.test.ts +80 -0
  33. package/tests/mod.test.ts +106 -0
  34. package/tests/named_group_choice.test.ts +82 -0
  35. package/tests/transform.test.ts +149 -0
  36. package/tests/transform_edge_cases.test.ts +265 -0
  37. package/tests/unknown.test.ts +72 -0
  38. package/tests/webdriver_local.test.ts +64 -0
  39. package/tests/webdriver_remote.test.ts +64 -0
  40. package/tsconfig.json +11 -0
package/.release-it.ts ADDED
@@ -0,0 +1,11 @@
1
+ import baseConfig from '../../.release-it.base';
2
+ import type { Config } from 'release-it';
3
+
4
+ const config: Config = {
5
+ ...baseConfig('cddl2py'),
6
+ };
7
+
8
+ console.log("Release-it config for cddl2py loaded", config);
9
+
10
+ export default config;
11
+
package/bin/cddl2py.js ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+
3
+ import parse from '../build/cli.js'
4
+
5
+ parse()
package/build/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export default function cli(argv?: string[]): Promise<undefined>;
2
+ //# sourceMappingURL=cli.d.ts.map
@@ -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,5 @@
1
+ export declare const pkg: any;
2
+ export declare const GENERATED_FILE_COMMENT: string;
3
+ export declare const NATIVE_TYPE_MAP: Record<string, string>;
4
+ export declare const PYDANTIC_NATIVE_TYPE_MAP: Record<string, string>;
5
+ //# sourceMappingURL=constants.d.ts.map
@@ -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
+ };
@@ -0,0 +1,6 @@
1
+ import { type Assignment } from 'cddl';
2
+ export interface TransformOptions {
3
+ pydantic?: boolean;
4
+ }
5
+ export declare function transform(assignments: Assignment[], options?: TransformOptions): string;
6
+ //# sourceMappingURL=index.d.ts.map
@@ -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
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Convert a string to snake_case
3
+ * @param name - The string to convert
4
+ * @returns The snake_case string
5
+ */
6
+ export declare function snakeCase(name: string): string;
7
+ //# sourceMappingURL=utils.d.ts.map
@@ -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"}