cddl2py 0.0.1 → 0.1.1
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/LICENSE +21 -0
- package/README.md +136 -0
- package/bin/cddl2py.js +0 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +246 -30
- package/package.json +12 -8
- package/.release-it.ts +0 -11
- package/src/cli.ts +0 -42
- package/src/constants.ts +0 -32
- package/src/index.ts +0 -658
- package/src/utils.ts +0 -12
- package/tests/__snapshots__/complex_types.test.ts.snap +0 -81
- package/tests/__snapshots__/group_choice.test.ts.snap +0 -127
- package/tests/__snapshots__/literals.test.ts.snap +0 -15
- package/tests/__snapshots__/mixin_union.test.ts.snap +0 -65
- package/tests/__snapshots__/mod.test.ts.snap +0 -145
- package/tests/__snapshots__/named_group_choice.test.ts.snap +0 -37
- package/tests/__snapshots__/transform.test.ts.snap +0 -137
- package/tests/__snapshots__/webdriver_local.test.ts.snap +0 -921
- package/tests/__snapshots__/webdriver_remote.test.ts.snap +0 -1249
- package/tests/complex_types.test.ts +0 -92
- package/tests/group_choice.test.ts +0 -88
- package/tests/literals.test.ts +0 -63
- package/tests/mixin_union.test.ts +0 -80
- package/tests/mod.test.ts +0 -106
- package/tests/named_group_choice.test.ts +0 -82
- package/tests/transform.test.ts +0 -149
- package/tests/transform_edge_cases.test.ts +0 -265
- package/tests/unknown.test.ts +0 -72
- package/tests/webdriver_local.test.ts +0 -64
- package/tests/webdriver_remote.test.ts +0 -64
- package/tsconfig.json +0 -11
package/src/index.ts
DELETED
|
@@ -1,658 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
isCDDLArray, isGroup, isNamedGroupReference, isLiteralWithValue,
|
|
3
|
-
isNativeTypeWithOperator, isUnNamedProperty, isPropertyReference,
|
|
4
|
-
isRange, isVariable, pascalCase,
|
|
5
|
-
type Assignment, type PropertyType, type PropertyReference,
|
|
6
|
-
type Property, type Array as CDDLArray, type Operator, type Group,
|
|
7
|
-
type Variable, type Comment, type Tag
|
|
8
|
-
} from 'cddl'
|
|
9
|
-
|
|
10
|
-
import { snakeCase } from './utils.js'
|
|
11
|
-
import { pkg, NATIVE_TYPE_MAP } from './constants.js'
|
|
12
|
-
|
|
13
|
-
export interface TransformOptions {
|
|
14
|
-
pydantic?: boolean
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
interface Context {
|
|
18
|
-
pydantic: boolean
|
|
19
|
-
typingImports: Set<string>
|
|
20
|
-
typingExtensionsImports: Set<string>
|
|
21
|
-
pydanticImports: Set<string>
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function transform (assignments: Assignment[], options?: TransformOptions): string {
|
|
25
|
-
const ctx: Context = {
|
|
26
|
-
pydantic: options?.pydantic ?? false,
|
|
27
|
-
typingImports: new Set(),
|
|
28
|
-
typingExtensionsImports: new Set(),
|
|
29
|
-
pydanticImports: new Set(),
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const blocks: string[] = []
|
|
33
|
-
|
|
34
|
-
for (const assignment of assignments) {
|
|
35
|
-
const block = generateAssignment(assignment, ctx)
|
|
36
|
-
if (block) {
|
|
37
|
-
blocks.push(block)
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return renderOutput(ctx, blocks)
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function renderOutput (ctx: Context, blocks: string[]): string {
|
|
45
|
-
const lines: string[] = []
|
|
46
|
-
|
|
47
|
-
lines.push(`# compiled with https://www.npmjs.com/package/cddl2py v${pkg.version}`)
|
|
48
|
-
lines.push('')
|
|
49
|
-
lines.push('from __future__ import annotations')
|
|
50
|
-
lines.push('')
|
|
51
|
-
|
|
52
|
-
if (ctx.typingImports.size > 0) {
|
|
53
|
-
lines.push(`from typing import ${[...ctx.typingImports].sort().join(', ')}`)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (ctx.pydantic) {
|
|
57
|
-
if (ctx.pydanticImports.size > 0) {
|
|
58
|
-
lines.push(`from pydantic import ${[...ctx.pydanticImports].sort().join(', ')}`)
|
|
59
|
-
}
|
|
60
|
-
} else if (ctx.typingExtensionsImports.size > 0) {
|
|
61
|
-
lines.push(`from typing_extensions import ${[...ctx.typingExtensionsImports].sort().join(', ')}`)
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (ctx.typingImports.size > 0 || ctx.pydanticImports.size > 0 || ctx.typingExtensionsImports.size > 0) {
|
|
65
|
-
lines.push('')
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
lines.push(blocks.join('\n\n'))
|
|
69
|
-
lines.push('')
|
|
70
|
-
|
|
71
|
-
return lines.join('\n')
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function generateAssignment (assignment: Assignment, ctx: Context): string | null {
|
|
75
|
-
if (isVariable(assignment)) {
|
|
76
|
-
return generateVariable(assignment, ctx)
|
|
77
|
-
}
|
|
78
|
-
if (isGroup(assignment)) {
|
|
79
|
-
return generateGroup(assignment as Group, ctx)
|
|
80
|
-
}
|
|
81
|
-
if (isCDDLArray(assignment)) {
|
|
82
|
-
return generateArrayAssignment(assignment as CDDLArray, ctx)
|
|
83
|
-
}
|
|
84
|
-
return null
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// ---------------------------------------------------------------------------
|
|
88
|
-
// Variable
|
|
89
|
-
// ---------------------------------------------------------------------------
|
|
90
|
-
|
|
91
|
-
function generateVariable (v: Variable, ctx: Context): string {
|
|
92
|
-
const name = pascalCase(v.Name)
|
|
93
|
-
const propTypes = Array.isArray(v.PropertyType) ? v.PropertyType : [v.PropertyType]
|
|
94
|
-
const comments = formatLeadingComments(v.Comments)
|
|
95
|
-
|
|
96
|
-
if (propTypes.length === 1 && isRange(propTypes[0])) {
|
|
97
|
-
return `${comments}${name} = int`
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const types = propTypes.map(t => resolveType(t, ctx))
|
|
101
|
-
|
|
102
|
-
if (types.length === 1) {
|
|
103
|
-
return `${comments}${name} = ${types[0]}`
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
ctx.typingImports.add('Union')
|
|
107
|
-
return `${comments}${name} = Union[${types.join(', ')}]`
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// ---------------------------------------------------------------------------
|
|
111
|
-
// Group
|
|
112
|
-
// ---------------------------------------------------------------------------
|
|
113
|
-
|
|
114
|
-
function generateGroup (group: Group, ctx: Context): string {
|
|
115
|
-
const name = pascalCase(group.Name)
|
|
116
|
-
const properties = group.Properties
|
|
117
|
-
const hasChoices = properties.some(p => Array.isArray(p))
|
|
118
|
-
const comments = formatLeadingComments(group.Comments)
|
|
119
|
-
|
|
120
|
-
if (hasChoices) {
|
|
121
|
-
return comments + generateGroupWithChoices(name, properties, ctx)
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const props = properties as Property[]
|
|
125
|
-
|
|
126
|
-
if (props.length === 1) {
|
|
127
|
-
const prop = props[0]
|
|
128
|
-
const propType = Array.isArray(prop.Type) ? prop.Type : [prop.Type]
|
|
129
|
-
if (propType.length === 1 && Object.keys(NATIVE_TYPE_MAP).includes(prop.Name)) {
|
|
130
|
-
const keyType = NATIVE_TYPE_MAP[prop.Name]
|
|
131
|
-
if (keyType === 'Any') {
|
|
132
|
-
ctx.typingImports.add('Any')
|
|
133
|
-
}
|
|
134
|
-
const valType = resolveType(propType[0], ctx)
|
|
135
|
-
return `${comments}${name} = dict[${keyType}, ${valType}]`
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const mixins = props.filter(isUnNamedProperty)
|
|
140
|
-
const ownProps = props.filter(p => !isUnNamedProperty(p))
|
|
141
|
-
|
|
142
|
-
const simpleMixinBases: string[] = []
|
|
143
|
-
const unionMixinGroups: string[][] = []
|
|
144
|
-
|
|
145
|
-
for (const mixin of mixins) {
|
|
146
|
-
if (Array.isArray(mixin.Type) && mixin.Type.length > 1) {
|
|
147
|
-
const unionTypes = mixin.Type.map(t => resolveType(t, ctx))
|
|
148
|
-
unionMixinGroups.push(unionTypes)
|
|
149
|
-
} else {
|
|
150
|
-
const typeVal = Array.isArray(mixin.Type) ? mixin.Type[0] : mixin.Type
|
|
151
|
-
if (isNamedGroupReference(typeVal)) {
|
|
152
|
-
simpleMixinBases.push(pascalCase(typeVal.Value))
|
|
153
|
-
} else if (isGroup(typeVal) && !isNamedGroupReference(typeVal) && (typeVal as Group).Properties) {
|
|
154
|
-
const inlineGroup = typeVal as Group
|
|
155
|
-
const inlineProps = inlineGroup.Properties as Property[]
|
|
156
|
-
const inlineMixinBases: string[] = []
|
|
157
|
-
for (const p of inlineProps) {
|
|
158
|
-
if (isUnNamedProperty(p)) {
|
|
159
|
-
const innerType = Array.isArray(p.Type) ? p.Type[0] : p.Type
|
|
160
|
-
if (isNamedGroupReference(innerType)) {
|
|
161
|
-
inlineMixinBases.push(pascalCase(innerType.Value))
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
simpleMixinBases.push(...inlineMixinBases)
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
if (unionMixinGroups.length > 0) {
|
|
171
|
-
return comments + generateGroupWithUnionMixins(name, simpleMixinBases, unionMixinGroups, ownProps, ctx)
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
return comments + generateClass(name, simpleMixinBases, ownProps, ctx)
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function generateGroupWithChoices (name: string, properties: (Property | Property[])[], ctx: Context): string {
|
|
178
|
-
const blocks: string[] = []
|
|
179
|
-
const unionTypes: string[] = []
|
|
180
|
-
let variantIndex = 0
|
|
181
|
-
|
|
182
|
-
for (let i = 0; i < properties.length; i++) {
|
|
183
|
-
const prop = properties[i]
|
|
184
|
-
|
|
185
|
-
if (Array.isArray(prop)) {
|
|
186
|
-
const choiceOptions = [...prop]
|
|
187
|
-
const nextProp = properties[i + 1]
|
|
188
|
-
if (nextProp && !Array.isArray(nextProp)) {
|
|
189
|
-
choiceOptions.push(nextProp)
|
|
190
|
-
i++
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
for (const option of choiceOptions) {
|
|
194
|
-
const typeVal = Array.isArray(option.Type) ? option.Type[0] : option.Type
|
|
195
|
-
|
|
196
|
-
if (isUnNamedProperty(option)) {
|
|
197
|
-
if (isNamedGroupReference(typeVal)) {
|
|
198
|
-
unionTypes.push(pascalCase(typeVal.Value as string))
|
|
199
|
-
} else {
|
|
200
|
-
unionTypes.push(resolveType(typeVal, ctx))
|
|
201
|
-
}
|
|
202
|
-
} else {
|
|
203
|
-
const variantName = `_${name}Variant${variantIndex}`
|
|
204
|
-
variantIndex++
|
|
205
|
-
blocks.push(generateClass(variantName, [], [option], ctx))
|
|
206
|
-
unionTypes.push(variantName)
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
} else if (isUnNamedProperty(prop)) {
|
|
210
|
-
const typeVal = Array.isArray(prop.Type) ? prop.Type[0] : prop.Type
|
|
211
|
-
if (isNamedGroupReference(typeVal)) {
|
|
212
|
-
unionTypes.push(pascalCase(typeVal.Value as string))
|
|
213
|
-
} else if (Array.isArray(prop.Type) && prop.Type.length > 1) {
|
|
214
|
-
for (const t of prop.Type) {
|
|
215
|
-
unionTypes.push(resolveType(t, ctx))
|
|
216
|
-
}
|
|
217
|
-
} else {
|
|
218
|
-
unionTypes.push(resolveType(typeVal, ctx))
|
|
219
|
-
}
|
|
220
|
-
} else {
|
|
221
|
-
const variantName = `_${name}Variant${variantIndex}`
|
|
222
|
-
variantIndex++
|
|
223
|
-
blocks.push(generateClass(variantName, [], [prop], ctx))
|
|
224
|
-
unionTypes.push(variantName)
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
if (unionTypes.length === 1) {
|
|
229
|
-
blocks.push(`${name} = ${unionTypes[0]}`)
|
|
230
|
-
} else {
|
|
231
|
-
ctx.typingImports.add('Union')
|
|
232
|
-
blocks.push(`${name} = Union[${unionTypes.join(', ')}]`)
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
return blocks.join('\n\n')
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
function generateGroupWithUnionMixins (
|
|
239
|
-
name: string,
|
|
240
|
-
simpleBases: string[],
|
|
241
|
-
unionGroups: string[][],
|
|
242
|
-
ownProps: Property[],
|
|
243
|
-
ctx: Context
|
|
244
|
-
): string {
|
|
245
|
-
if (ownProps.length === 0 && simpleBases.length === 0) {
|
|
246
|
-
const allTypes = unionGroups.flat()
|
|
247
|
-
if (allTypes.length === 1) {
|
|
248
|
-
return `${name} = ${allTypes[0]}`
|
|
249
|
-
}
|
|
250
|
-
ctx.typingImports.add('Union')
|
|
251
|
-
return `${name} = Union[${allTypes.join(', ')}]`
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
const blocks: string[] = []
|
|
255
|
-
const variantNames: string[] = []
|
|
256
|
-
|
|
257
|
-
if (unionGroups.length === 1) {
|
|
258
|
-
const unionTypes = unionGroups[0]
|
|
259
|
-
|
|
260
|
-
if (ownProps.length > 0) {
|
|
261
|
-
const baseName = `_${name}Fields`
|
|
262
|
-
blocks.push(generateClass(baseName, [], ownProps, ctx))
|
|
263
|
-
|
|
264
|
-
for (let i = 0; i < unionTypes.length; i++) {
|
|
265
|
-
const variantName = `_${name}Variant${i}`
|
|
266
|
-
variantNames.push(variantName)
|
|
267
|
-
const bases = [baseName, unionTypes[i], ...simpleBases]
|
|
268
|
-
blocks.push(generateClass(variantName, bases, [], ctx))
|
|
269
|
-
}
|
|
270
|
-
} else {
|
|
271
|
-
for (let i = 0; i < unionTypes.length; i++) {
|
|
272
|
-
const variantName = `_${name}Variant${i}`
|
|
273
|
-
variantNames.push(variantName)
|
|
274
|
-
const bases = [unionTypes[i], ...simpleBases]
|
|
275
|
-
blocks.push(generateClass(variantName, bases, [], ctx))
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
} else {
|
|
279
|
-
const allTypes = [...simpleBases, ...unionGroups.flat()]
|
|
280
|
-
ctx.typingImports.add('Union')
|
|
281
|
-
blocks.push(`${name} = Union[${allTypes.join(', ')}]`)
|
|
282
|
-
return blocks.join('\n\n')
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
ctx.typingImports.add('Union')
|
|
286
|
-
blocks.push(`${name} = Union[${variantNames.join(', ')}]`)
|
|
287
|
-
|
|
288
|
-
return blocks.join('\n\n')
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// ---------------------------------------------------------------------------
|
|
292
|
-
// Array
|
|
293
|
-
// ---------------------------------------------------------------------------
|
|
294
|
-
|
|
295
|
-
function generateArrayAssignment (arr: CDDLArray, ctx: Context): string {
|
|
296
|
-
const name = pascalCase(arr.Name)
|
|
297
|
-
const comments = formatLeadingComments(arr.Comments)
|
|
298
|
-
|
|
299
|
-
const values = arr.Values
|
|
300
|
-
if (values.length === 0) {
|
|
301
|
-
ctx.typingImports.add('Any')
|
|
302
|
-
return `${comments}${name} = list[Any]`
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
const firstVal = values[0]
|
|
306
|
-
|
|
307
|
-
if (Array.isArray(firstVal)) {
|
|
308
|
-
const options = firstVal.map(p => {
|
|
309
|
-
const t = Array.isArray(p.Type) ? p.Type[0] : p.Type
|
|
310
|
-
return resolveType(t, ctx)
|
|
311
|
-
})
|
|
312
|
-
if (options.length === 1) {
|
|
313
|
-
return `${comments}${name} = list[${options[0]}]`
|
|
314
|
-
}
|
|
315
|
-
ctx.typingImports.add('Union')
|
|
316
|
-
return `${comments}${name} = list[Union[${options.join(', ')}]]`
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
const firstType = firstVal.Type
|
|
320
|
-
const types = Array.isArray(firstType) ? firstType : [firstType]
|
|
321
|
-
|
|
322
|
-
if (types.length === 1 && isCDDLArray(types[0])) {
|
|
323
|
-
const innerArr = types[0] as CDDLArray
|
|
324
|
-
const innerVal = innerArr.Values[0] as Property
|
|
325
|
-
const innerTypes = Array.isArray(innerVal.Type) ? innerVal.Type : [innerVal.Type]
|
|
326
|
-
const typeStrs = innerTypes.map(v => resolveType(v, ctx))
|
|
327
|
-
|
|
328
|
-
if (typeStrs.length === 1) {
|
|
329
|
-
return `${comments}${name} = list[${typeStrs[0]}]`
|
|
330
|
-
}
|
|
331
|
-
ctx.typingImports.add('Union')
|
|
332
|
-
return `${comments}${name} = list[Union[${typeStrs.join(', ')}]]`
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
const typeStrs = types.map(t => resolveType(t, ctx))
|
|
336
|
-
|
|
337
|
-
if (typeStrs.length === 1) {
|
|
338
|
-
return `${comments}${name} = list[${typeStrs[0]}]`
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
ctx.typingImports.add('Union')
|
|
342
|
-
return `${comments}${name} = list[Union[${typeStrs.join(', ')}]]`
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
// ---------------------------------------------------------------------------
|
|
346
|
-
// Class generation (TypedDict or Pydantic BaseModel)
|
|
347
|
-
// ---------------------------------------------------------------------------
|
|
348
|
-
|
|
349
|
-
function generateClass (name: string, bases: string[], props: Property[], ctx: Context): string {
|
|
350
|
-
const lines: string[] = []
|
|
351
|
-
|
|
352
|
-
let classDecl: string
|
|
353
|
-
if (ctx.pydantic) {
|
|
354
|
-
ctx.pydanticImports.add('BaseModel')
|
|
355
|
-
if (bases.length > 0) {
|
|
356
|
-
classDecl = `class ${name}(${bases.join(', ')}):`
|
|
357
|
-
} else {
|
|
358
|
-
classDecl = `class ${name}(BaseModel):`
|
|
359
|
-
}
|
|
360
|
-
} else {
|
|
361
|
-
ctx.typingExtensionsImports.add('TypedDict')
|
|
362
|
-
if (bases.length > 0) {
|
|
363
|
-
classDecl = `class ${name}(${bases.join(', ')}):`
|
|
364
|
-
} else {
|
|
365
|
-
classDecl = `class ${name}(TypedDict):`
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
lines.push(classDecl)
|
|
370
|
-
|
|
371
|
-
if (props.length === 0) {
|
|
372
|
-
lines.push(' pass')
|
|
373
|
-
return lines.join('\n')
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
for (const prop of props) {
|
|
377
|
-
const fieldLine = generateField(prop, ctx)
|
|
378
|
-
if (fieldLine) {
|
|
379
|
-
lines.push(fieldLine)
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
if (lines.length === 1) {
|
|
384
|
-
lines.push(' pass')
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
return lines.join('\n')
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
function generateField (prop: Property, ctx: Context): string | null {
|
|
391
|
-
if (isUnNamedProperty(prop)) {
|
|
392
|
-
return null
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
const propName = snakeCase(prop.Name)
|
|
396
|
-
const cddlTypes: PropertyType[] = Array.isArray(prop.Type) ? prop.Type : [prop.Type]
|
|
397
|
-
const isOptional = prop.Occurrence.n === 0
|
|
398
|
-
|
|
399
|
-
let typeStr: string
|
|
400
|
-
const types = cddlTypes.map(t => resolveType(t, ctx))
|
|
401
|
-
|
|
402
|
-
if (types.length === 1) {
|
|
403
|
-
typeStr = types[0]
|
|
404
|
-
} else {
|
|
405
|
-
ctx.typingImports.add('Union')
|
|
406
|
-
typeStr = `Union[${types.join(', ')}]`
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
const inlineComment = prop.Comments
|
|
410
|
-
.filter(c => !c.Leading)
|
|
411
|
-
.map(c => c.Content.trim())
|
|
412
|
-
.join('; ')
|
|
413
|
-
const commentSuffix = inlineComment ? ` # ${inlineComment}` : ''
|
|
414
|
-
|
|
415
|
-
let defaultExpr = ''
|
|
416
|
-
if (prop.Operator && prop.Operator.Type === 'default') {
|
|
417
|
-
defaultExpr = formatDefaultValue(prop.Operator)
|
|
418
|
-
}
|
|
419
|
-
for (const t of cddlTypes) {
|
|
420
|
-
if (isPropertyReference(t) && (t as PropertyReference).Operator?.Type === 'default') {
|
|
421
|
-
const val = formatDefaultValue((t as PropertyReference).Operator!)
|
|
422
|
-
if (val) {
|
|
423
|
-
defaultExpr = val
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
if (ctx.pydantic) {
|
|
429
|
-
if (isOptional) {
|
|
430
|
-
ctx.typingImports.add('Optional')
|
|
431
|
-
if (defaultExpr) {
|
|
432
|
-
ctx.pydanticImports.add('Field')
|
|
433
|
-
return ` ${propName}: Optional[${typeStr}] = Field(default=${defaultExpr})${commentSuffix}`
|
|
434
|
-
}
|
|
435
|
-
return ` ${propName}: Optional[${typeStr}] = None${commentSuffix}`
|
|
436
|
-
}
|
|
437
|
-
if (defaultExpr) {
|
|
438
|
-
ctx.pydanticImports.add('Field')
|
|
439
|
-
return ` ${propName}: ${typeStr} = Field(default=${defaultExpr})${commentSuffix}`
|
|
440
|
-
}
|
|
441
|
-
return ` ${propName}: ${typeStr}${commentSuffix}`
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
if (isOptional) {
|
|
445
|
-
ctx.typingExtensionsImports.add('NotRequired')
|
|
446
|
-
return ` ${propName}: NotRequired[${typeStr}]${commentSuffix}`
|
|
447
|
-
}
|
|
448
|
-
return ` ${propName}: ${typeStr}${commentSuffix}`
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
// ---------------------------------------------------------------------------
|
|
452
|
-
// Type resolution
|
|
453
|
-
// ---------------------------------------------------------------------------
|
|
454
|
-
|
|
455
|
-
function resolveType (t: PropertyType, ctx: Context): string {
|
|
456
|
-
if (typeof t === 'string') {
|
|
457
|
-
const mapped = NATIVE_TYPE_MAP[t]
|
|
458
|
-
if (mapped) {
|
|
459
|
-
if (mapped === 'Any') {
|
|
460
|
-
ctx.typingImports.add('Any')
|
|
461
|
-
}
|
|
462
|
-
return mapped
|
|
463
|
-
}
|
|
464
|
-
throw new Error(`Unknown native type: "${t}"`)
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
if ((t as any).Type && typeof (t as any).Type === 'string' && NATIVE_TYPE_MAP[(t as any).Type]) {
|
|
468
|
-
const mapped = NATIVE_TYPE_MAP[(t as any).Type]
|
|
469
|
-
if (mapped === 'Any') {
|
|
470
|
-
ctx.typingImports.add('Any')
|
|
471
|
-
}
|
|
472
|
-
return mapped
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
if (isNativeTypeWithOperator(t) && NATIVE_TYPE_MAP[(t.Type as any).Type]) {
|
|
476
|
-
const mapped = NATIVE_TYPE_MAP[(t.Type as any).Type]
|
|
477
|
-
if (mapped === 'Any') {
|
|
478
|
-
ctx.typingImports.add('Any')
|
|
479
|
-
}
|
|
480
|
-
return mapped
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
if (isPropertyReference(t) && (t as PropertyReference).Value === 'null') {
|
|
484
|
-
return 'None'
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
if (isGroup(t)) {
|
|
488
|
-
if (isNamedGroupReference(t)) {
|
|
489
|
-
return pascalCase((t as unknown as PropertyReference).Value as string)
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
const group = t as unknown as Group
|
|
493
|
-
if (group.Properties) {
|
|
494
|
-
const props = group.Properties
|
|
495
|
-
|
|
496
|
-
if (props.some(p => Array.isArray(p))) {
|
|
497
|
-
const options: string[] = []
|
|
498
|
-
for (const choice of props) {
|
|
499
|
-
const subProps = Array.isArray(choice) ? choice : [choice]
|
|
500
|
-
if (subProps.length === 1 && isUnNamedProperty(subProps[0])) {
|
|
501
|
-
const subType = Array.isArray(subProps[0].Type) ? subProps[0].Type[0] : subProps[0].Type
|
|
502
|
-
options.push(resolveType(subType as PropertyType, ctx))
|
|
503
|
-
continue
|
|
504
|
-
}
|
|
505
|
-
if (subProps.every(isUnNamedProperty)) {
|
|
506
|
-
const tupleItems = subProps.map(p => {
|
|
507
|
-
const subType = Array.isArray(p.Type) ? p.Type[0] : p.Type
|
|
508
|
-
return resolveType(subType as PropertyType, ctx)
|
|
509
|
-
})
|
|
510
|
-
ctx.typingImports.add('Tuple')
|
|
511
|
-
options.push(`Tuple[${tupleItems.join(', ')}]`)
|
|
512
|
-
continue
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
if (options.length > 1) {
|
|
516
|
-
ctx.typingImports.add('Union')
|
|
517
|
-
return `Union[${options.join(', ')}]`
|
|
518
|
-
}
|
|
519
|
-
if (options.length === 1) {
|
|
520
|
-
return options[0]
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
if ((props as Property[]).every(isUnNamedProperty)) {
|
|
525
|
-
const items = (props as Property[]).map(p => {
|
|
526
|
-
const subType = Array.isArray(p.Type) ? p.Type[0] : p.Type
|
|
527
|
-
return resolveType(subType as PropertyType, ctx)
|
|
528
|
-
})
|
|
529
|
-
if (items.length === 1) {
|
|
530
|
-
return items[0]
|
|
531
|
-
}
|
|
532
|
-
ctx.typingImports.add('Tuple')
|
|
533
|
-
return `Tuple[${items.join(', ')}]`
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
if (props.length === 1 && Object.keys(NATIVE_TYPE_MAP).includes((props[0] as Property).Name)) {
|
|
537
|
-
const keyType = NATIVE_TYPE_MAP[(props[0] as Property).Name]
|
|
538
|
-
if (keyType === 'Any') {
|
|
539
|
-
ctx.typingImports.add('Any')
|
|
540
|
-
}
|
|
541
|
-
const valType = resolveType(((props[0] as Property).Type as PropertyType[])[0], ctx)
|
|
542
|
-
return `dict[${keyType}, ${valType}]`
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
ctx.typingImports.add('Any')
|
|
546
|
-
return 'dict[str, Any]'
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
throw new Error(`Unknown group type: ${JSON.stringify(t)}`)
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
if (isLiteralWithValue(t)) {
|
|
553
|
-
ctx.typingImports.add('Literal')
|
|
554
|
-
if (typeof t.Value === 'string') {
|
|
555
|
-
return `Literal["${t.Value}"]`
|
|
556
|
-
}
|
|
557
|
-
if (typeof t.Value === 'number') {
|
|
558
|
-
return `Literal[${t.Value}]`
|
|
559
|
-
}
|
|
560
|
-
if (typeof t.Value === 'boolean') {
|
|
561
|
-
return `Literal[${t.Value ? 'True' : 'False'}]`
|
|
562
|
-
}
|
|
563
|
-
if (typeof t.Value === 'bigint') {
|
|
564
|
-
return `Literal[${t.Value.toString()}]`
|
|
565
|
-
}
|
|
566
|
-
if (t.Value === null) {
|
|
567
|
-
return 'None'
|
|
568
|
-
}
|
|
569
|
-
throw new Error(`Unsupported literal: ${JSON.stringify(t)}`)
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
if (isCDDLArray(t)) {
|
|
573
|
-
const arrValues = (t as unknown as CDDLArray).Values
|
|
574
|
-
if (arrValues.length === 0) {
|
|
575
|
-
ctx.typingImports.add('Any')
|
|
576
|
-
return 'list[Any]'
|
|
577
|
-
}
|
|
578
|
-
const firstVal = arrValues[0] as Property
|
|
579
|
-
const innerTypes = Array.isArray(firstVal.Type) ? firstVal.Type : [firstVal.Type]
|
|
580
|
-
const typeStrs = innerTypes.map(v => resolveType(v, ctx))
|
|
581
|
-
|
|
582
|
-
if (typeStrs.length === 1) {
|
|
583
|
-
return `list[${typeStrs[0]}]`
|
|
584
|
-
}
|
|
585
|
-
ctx.typingImports.add('Union')
|
|
586
|
-
return `list[Union[${typeStrs.join(', ')}]]`
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
if (isRange(t)) {
|
|
590
|
-
return 'int'
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
if (isPropertyReference(t) && (t as PropertyReference).Type === 'range') {
|
|
594
|
-
return 'int'
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
if (isNativeTypeWithOperator(t) && isNamedGroupReference(t.Type)) {
|
|
598
|
-
return pascalCase((t.Type as unknown as PropertyReference).Value as string)
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
if (isPropertyReference(t)) {
|
|
602
|
-
const ref = t as PropertyReference
|
|
603
|
-
if (ref.Type === 'group_array' && typeof ref.Value === 'string') {
|
|
604
|
-
return `list[${pascalCase(ref.Value)}]`
|
|
605
|
-
}
|
|
606
|
-
if (ref.Type === 'tag') {
|
|
607
|
-
const tag = ref.Value as Tag
|
|
608
|
-
const mapped = NATIVE_TYPE_MAP[tag.TypePart]
|
|
609
|
-
if (mapped) {
|
|
610
|
-
if (mapped === 'Any') {
|
|
611
|
-
ctx.typingImports.add('Any')
|
|
612
|
-
}
|
|
613
|
-
return mapped
|
|
614
|
-
}
|
|
615
|
-
return pascalCase(tag.TypePart)
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
throw new Error(`Unknown type: ${JSON.stringify(t)}`)
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
// ---------------------------------------------------------------------------
|
|
623
|
-
// Helpers
|
|
624
|
-
// ---------------------------------------------------------------------------
|
|
625
|
-
|
|
626
|
-
function formatLeadingComments (comments: Comment[]): string {
|
|
627
|
-
const leading = comments.filter(c => c.Leading)
|
|
628
|
-
if (leading.length === 0) {
|
|
629
|
-
return ''
|
|
630
|
-
}
|
|
631
|
-
return leading.map(c => `# ${c.Content}`).join('\n') + '\n'
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
function formatDefaultValue (operator: Operator): string {
|
|
635
|
-
if (operator.Type !== 'default') {
|
|
636
|
-
return ''
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
const val = operator.Value
|
|
640
|
-
if (val === 'null') {
|
|
641
|
-
return 'None'
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
const ref = val as PropertyReference
|
|
645
|
-
if (ref.Type === 'literal') {
|
|
646
|
-
if (typeof ref.Value === 'string') {
|
|
647
|
-
return `"${ref.Value}"`
|
|
648
|
-
}
|
|
649
|
-
if (typeof ref.Value === 'number') {
|
|
650
|
-
return String(ref.Value)
|
|
651
|
-
}
|
|
652
|
-
if (typeof ref.Value === 'boolean') {
|
|
653
|
-
return ref.Value ? 'True' : 'False'
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
return ''
|
|
658
|
-
}
|
package/src/utils.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Convert a string to snake_case
|
|
3
|
-
* @param name - The string to convert
|
|
4
|
-
* @returns The snake_case string
|
|
5
|
-
*/
|
|
6
|
-
export function snakeCase(name: string): string {
|
|
7
|
-
return name
|
|
8
|
-
.replace(/([a-z0-9])([A-Z])/g, '$1_$2')
|
|
9
|
-
.replace(/([A-Z])([A-Z][a-z])/g, '$1_$2')
|
|
10
|
-
.replace(/[-.\s]+/g, '_')
|
|
11
|
-
.toLowerCase()
|
|
12
|
-
}
|