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
@@ -0,0 +1,265 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import type {
4
+ Array as CDDLArray,
5
+ Comment,
6
+ Group,
7
+ Operator,
8
+ Property,
9
+ PropertyReference,
10
+ Tag,
11
+ Variable
12
+ } from 'cddl'
13
+
14
+ import { transform } from '../src/index.js'
15
+
16
+ const COMMENTS: Comment[] = []
17
+
18
+ function comment (content: string, leading = false): Comment {
19
+ return {
20
+ Type: 'comment',
21
+ Content: content,
22
+ Leading: leading
23
+ }
24
+ }
25
+
26
+ function groupRef (value: string, operator?: Operator): PropertyReference {
27
+ return {
28
+ Type: 'group',
29
+ Value: value,
30
+ Unwrapped: false,
31
+ Operator: operator
32
+ }
33
+ }
34
+
35
+ function rangeRef (): PropertyReference {
36
+ return {
37
+ Type: 'range',
38
+ Value: {
39
+ Min: 0,
40
+ Max: 10,
41
+ Inclusive: true
42
+ },
43
+ Unwrapped: false
44
+ }
45
+ }
46
+
47
+ function tagRef (typePart: string): PropertyReference {
48
+ return {
49
+ Type: 'tag',
50
+ Value: {
51
+ NumericPart: 1,
52
+ TypePart: typePart
53
+ } satisfies Tag,
54
+ Unwrapped: false
55
+ }
56
+ }
57
+
58
+ function literal (value: unknown): PropertyReference {
59
+ return {
60
+ Type: 'literal',
61
+ Value: value as any,
62
+ Unwrapped: false
63
+ } as PropertyReference
64
+ }
65
+
66
+ function property (
67
+ name: string,
68
+ type: Property['Type'],
69
+ overrides: Partial<Property> = {}
70
+ ): Property {
71
+ return {
72
+ HasCut: false,
73
+ Occurrence: { n: 1, m: 1 },
74
+ Name: name,
75
+ Type: type,
76
+ Comments: COMMENTS,
77
+ ...overrides
78
+ }
79
+ }
80
+
81
+ function unnamedProperty (
82
+ type: Property['Type'],
83
+ overrides: Partial<Property> = {}
84
+ ): Property {
85
+ return property('', type, overrides)
86
+ }
87
+
88
+ function group (
89
+ name: string,
90
+ properties: Group['Properties'],
91
+ comments: Comment[] = COMMENTS
92
+ ): Group {
93
+ return {
94
+ Type: 'group',
95
+ Name: name,
96
+ IsChoiceAddition: false,
97
+ Properties: properties,
98
+ Comments: comments
99
+ }
100
+ }
101
+
102
+ function array (
103
+ name: string,
104
+ values: CDDLArray['Values'],
105
+ comments: Comment[] = COMMENTS
106
+ ): CDDLArray {
107
+ return {
108
+ Type: 'array',
109
+ Name: name,
110
+ Values: values,
111
+ Comments: comments
112
+ }
113
+ }
114
+
115
+ function variable (
116
+ name: string,
117
+ propertyType: Variable['PropertyType'],
118
+ comments: Comment[] = COMMENTS
119
+ ): Variable {
120
+ return {
121
+ Type: 'variable',
122
+ Name: name,
123
+ PropertyType: propertyType,
124
+ IsChoiceAddition: false,
125
+ Comments: comments
126
+ }
127
+ }
128
+
129
+ describe('transform edge cases', () => {
130
+ it('should generate pydantic variants for union mixins with shared fields', () => {
131
+ const output = transform([
132
+ group('base', [property('kind', 'tstr')]),
133
+ group('mixin-a', [property('a', 'int')]),
134
+ group('mixin-b', [property('b', 'int')]),
135
+ group('combined', [
136
+ unnamedProperty(groupRef('base')),
137
+ unnamedProperty([groupRef('mixin-a'), groupRef('mixin-b')]),
138
+ property('enabled', 'bool', {
139
+ Occurrence: { n: 0, m: 1 },
140
+ Operator: { Type: 'default', Value: literal(true) },
141
+ Comments: [comment('inline docs')]
142
+ })
143
+ ], [comment('shared union mixin', true)])
144
+ ], { pydantic: true })
145
+
146
+ expect(output).toContain('# shared union mixin')
147
+ expect(output).toContain('class _CombinedFields(BaseModel):')
148
+ expect(output).toContain('enabled: Optional[bool] = Field(default=True) # inline docs')
149
+ expect(output).toContain('class _CombinedVariant0(_CombinedFields, MixinA, Base):')
150
+ expect(output).toContain('class _CombinedVariant1(_CombinedFields, MixinB, Base):')
151
+ expect(output).toContain('Combined = Union[_CombinedVariant0, _CombinedVariant1]')
152
+ })
153
+
154
+ it('should collapse multiple union mixin groups into a single alias', () => {
155
+ const output = transform([
156
+ group('combined', [
157
+ unnamedProperty(groupRef('base')),
158
+ unnamedProperty([groupRef('mixin-a'), groupRef('mixin-b')]),
159
+ unnamedProperty([groupRef('mixin-c'), groupRef('mixin-d')])
160
+ ])
161
+ ])
162
+
163
+ expect(output).toContain('Combined = Union[Base, MixinA, MixinB, MixinC, MixinD]')
164
+ })
165
+
166
+ it('should handle empty arrays, choice arrays and nested arrays', () => {
167
+ const output = transform([
168
+ array('empty-list', [], [comment('empty array', true)]),
169
+ array('choice-list', [[
170
+ unnamedProperty('int'),
171
+ unnamedProperty('tstr')
172
+ ]]),
173
+ array('nested-list', [
174
+ unnamedProperty(array('', [
175
+ unnamedProperty(['int', 'tstr'])
176
+ ]))
177
+ ])
178
+ ])
179
+
180
+ expect(output).toContain('# empty array')
181
+ expect(output).toContain('EmptyList = list[Any]')
182
+ expect(output).toContain('ChoiceList = list[Union[int, str]]')
183
+ expect(output).toContain('NestedList = list[Union[int, str]]')
184
+ })
185
+
186
+ it('should resolve inline groups, refs, tags and ranges into python types', () => {
187
+ const output = transform([
188
+ variable('tuple-type', group('', [
189
+ unnamedProperty('int'),
190
+ unnamedProperty('tstr')
191
+ ]) as any),
192
+ variable('choice-type', group('', [
193
+ [unnamedProperty('int'), unnamedProperty('tstr')],
194
+ unnamedProperty('bool')
195
+ ]) as any),
196
+ variable('dict-type', group('', [
197
+ property('any', ['tstr'])
198
+ ]) as any),
199
+ variable('fallback-dict', group('', [
200
+ property('foo', 'tstr')
201
+ ]) as any),
202
+ variable('group-array-type', {
203
+ Type: 'group_array',
204
+ Value: 'item-value',
205
+ Unwrapped: false
206
+ }),
207
+ variable('mapped-tag-type', tagRef('tstr')),
208
+ variable('custom-tag-type', tagRef('custom-tag')),
209
+ variable('range-type', rangeRef()),
210
+ variable('literal-null', literal(null))
211
+ ])
212
+
213
+ expect(output).toContain('TupleType = Tuple[int, str]')
214
+ expect(output).toContain('ChoiceType = Union[Tuple[int, str], bool]')
215
+ expect(output).toContain('DictType = dict[Any, str]')
216
+ expect(output).toContain('FallbackDict = dict[str, Any]')
217
+ expect(output).toContain('GroupArrayType = list[ItemValue]')
218
+ expect(output).toContain('MappedTagType = str')
219
+ expect(output).toContain('CustomTagType = CustomTag')
220
+ expect(output).toContain('RangeType = int')
221
+ expect(output).toContain('LiteralNull = None')
222
+ })
223
+
224
+ it('should render property defaults from both property and reference operators', () => {
225
+ const output = transform([
226
+ group('defaults', [
227
+ property('count', 'int', {
228
+ Operator: { Type: 'default', Value: literal(7) }
229
+ }),
230
+ property('status', groupRef('status-value', {
231
+ Type: 'default',
232
+ Value: literal('ready')
233
+ }), {
234
+ Comments: [comment('status docs')]
235
+ }),
236
+ property('maybe_enabled', 'bool', {
237
+ Occurrence: { n: 0, m: 1 },
238
+ Operator: { Type: 'default', Value: literal(false) }
239
+ })
240
+ ])
241
+ ], { pydantic: true })
242
+
243
+ expect(output).toContain('count: int = Field(default=7)')
244
+ expect(output).toContain('status: StatusValue = Field(default="ready") # status docs')
245
+ expect(output).toContain('maybe_enabled: Optional[bool] = Field(default=False)')
246
+ })
247
+
248
+ it('should raise clear errors for unsupported transform inputs', () => {
249
+ expect(() => transform([
250
+ variable('unknown-native', 'nope')
251
+ ])).toThrow('Unknown native type: "nope"')
252
+
253
+ expect(() => transform([
254
+ variable('unsupported-literal', literal({ foo: 'bar' }))
255
+ ])).toThrow('Unsupported literal')
256
+
257
+ expect(() => transform([
258
+ variable('bad-group', { Type: 'group', Value: false, Unwrapped: false } as any)
259
+ ])).toThrow('Unknown group type')
260
+
261
+ expect(() => transform([
262
+ variable('bad-reference', { Type: 'mystery', Value: 'oops', Unwrapped: false } as any)
263
+ ])).toThrow('Unknown type')
264
+ })
265
+ })
@@ -0,0 +1,72 @@
1
+ import url from 'node:url'
2
+ import path from 'node:path'
3
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
4
+
5
+ import cli from '../src/cli.js'
6
+
7
+ const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
8
+ const cddlFile = path.join(__dirname, '..', '..', '..', 'examples', 'commons', 'unknown.cddl')
9
+
10
+ vi.mock('../src/constants', () => ({
11
+ pkg: {
12
+ name: 'cddl2py',
13
+ version: '0.1.0',
14
+ author: 'Test Author',
15
+ description: 'Generate Python types from CDDL'
16
+ },
17
+ NATIVE_TYPE_MAP: {
18
+ any: 'Any',
19
+ number: 'Union[int, float]',
20
+ int: 'int',
21
+ uint: 'int',
22
+ nint: 'int',
23
+ float: 'float',
24
+ float16: 'float',
25
+ float32: 'float',
26
+ float64: 'float',
27
+ bool: 'bool',
28
+ bstr: 'bytes',
29
+ bytes: 'bytes',
30
+ tstr: 'str',
31
+ text: 'str',
32
+ str: 'str',
33
+ nil: 'None',
34
+ null: 'None',
35
+ }
36
+ }))
37
+
38
+ describe('any type mapping', () => {
39
+ let exitOrig = process.exit
40
+ let logOrig = console.log
41
+ let errorOrig = console.error
42
+
43
+ beforeEach(() => {
44
+ process.exit = vi.fn() as any
45
+ console.log = vi.fn()
46
+ console.error = vi.fn()
47
+ })
48
+
49
+ afterEach(() => {
50
+ process.exit = exitOrig
51
+ console.log = logOrig
52
+ console.error = errorOrig
53
+ })
54
+
55
+ it('should map CDDL any to Python Any (TypedDict)', async () => {
56
+ await cli([cddlFile])
57
+
58
+ const output = vi.mocked(console.log).mock.calls.flat().join('\n')
59
+ expect(output).toContain('Foo = Any')
60
+ expect(output).toContain('Bar = list[Any]')
61
+ expect(output).toContain('Baz = dict[str, Any]')
62
+ })
63
+
64
+ it('should map CDDL any to Python Any (Pydantic)', async () => {
65
+ await cli([cddlFile, '--pydantic'])
66
+
67
+ const output = vi.mocked(console.log).mock.calls.flat().join('\n')
68
+ expect(output).toContain('Foo = Any')
69
+ expect(output).toContain('Bar = list[Any]')
70
+ expect(output).toContain('Baz = dict[str, Any]')
71
+ })
72
+ })
@@ -0,0 +1,64 @@
1
+ import url from 'node:url'
2
+ import path from 'node:path'
3
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
4
+
5
+ import cli from '../src/cli.js'
6
+
7
+ const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
8
+ const cddlFile = path.join(__dirname, '..', '..', '..', 'examples', 'webdriver', 'local.cddl')
9
+
10
+ vi.mock('../src/constants', () => ({
11
+ pkg: {
12
+ name: 'cddl2py',
13
+ version: '0.1.0',
14
+ author: 'Test Author',
15
+ description: 'Generate Python types from CDDL'
16
+ },
17
+ NATIVE_TYPE_MAP: {
18
+ any: 'Any',
19
+ number: 'Union[int, float]',
20
+ int: 'int',
21
+ uint: 'int',
22
+ nint: 'int',
23
+ float: 'float',
24
+ float16: 'float',
25
+ float32: 'float',
26
+ float64: 'float',
27
+ bool: 'bool',
28
+ bstr: 'bytes',
29
+ bytes: 'bytes',
30
+ tstr: 'str',
31
+ text: 'str',
32
+ str: 'str',
33
+ nil: 'None',
34
+ null: 'None',
35
+ }
36
+ }))
37
+
38
+ describe('webdriver local spec', () => {
39
+ let exitOrig = process.exit
40
+ let logOrig = console.log
41
+ let errorOrig = console.error
42
+
43
+ beforeEach(() => {
44
+ process.exit = vi.fn() as any
45
+ console.log = vi.fn()
46
+ console.error = vi.fn()
47
+ })
48
+
49
+ afterEach(() => {
50
+ process.exit = exitOrig
51
+ console.log = logOrig
52
+ console.error = errorOrig
53
+ })
54
+
55
+ it('should generate Python types for webdriver local spec', async () => {
56
+ await cli([cddlFile])
57
+
58
+ expect(process.exit).not.toHaveBeenCalledWith(1)
59
+ expect(console.error).not.toHaveBeenCalled()
60
+ expect(console.log).toHaveBeenCalled()
61
+ const output = vi.mocked(console.log).mock.calls.flat().join('\n')
62
+ expect(output).toMatchSnapshot()
63
+ })
64
+ })
@@ -0,0 +1,64 @@
1
+ import url from 'node:url'
2
+ import path from 'node:path'
3
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
4
+
5
+ import cli from '../src/cli.js'
6
+
7
+ const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
8
+ const cddlFile = path.join(__dirname, '..', '..', '..', 'examples', 'webdriver', 'remote.cddl')
9
+
10
+ vi.mock('../src/constants', () => ({
11
+ pkg: {
12
+ name: 'cddl2py',
13
+ version: '0.1.0',
14
+ author: 'Test Author',
15
+ description: 'Generate Python types from CDDL'
16
+ },
17
+ NATIVE_TYPE_MAP: {
18
+ any: 'Any',
19
+ number: 'Union[int, float]',
20
+ int: 'int',
21
+ uint: 'int',
22
+ nint: 'int',
23
+ float: 'float',
24
+ float16: 'float',
25
+ float32: 'float',
26
+ float64: 'float',
27
+ bool: 'bool',
28
+ bstr: 'bytes',
29
+ bytes: 'bytes',
30
+ tstr: 'str',
31
+ text: 'str',
32
+ str: 'str',
33
+ nil: 'None',
34
+ null: 'None',
35
+ }
36
+ }))
37
+
38
+ describe('webdriver remote spec', () => {
39
+ let exitOrig = process.exit
40
+ let logOrig = console.log
41
+ let errorOrig = console.error
42
+
43
+ beforeEach(() => {
44
+ process.exit = vi.fn() as any
45
+ console.log = vi.fn()
46
+ console.error = vi.fn()
47
+ })
48
+
49
+ afterEach(() => {
50
+ process.exit = exitOrig
51
+ console.log = logOrig
52
+ console.error = errorOrig
53
+ })
54
+
55
+ it('should generate Python types for remote.cddl', async () => {
56
+ await cli([cddlFile])
57
+
58
+ expect(process.exit).not.toHaveBeenCalledWith(1)
59
+ expect(console.error).not.toHaveBeenCalled()
60
+ expect(console.log).toHaveBeenCalled()
61
+ const output = vi.mocked(console.log).mock.calls.flat().join('\n')
62
+ expect(output).toMatchSnapshot()
63
+ })
64
+ })
package/tsconfig.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "extends": "../../tsconfig",
3
+ "compilerOptions": {
4
+ "outDir": "build",
5
+ "rootDir": "src",
6
+ },
7
+ "types": ["node"],
8
+ "include": [
9
+ "src/**/*",
10
+ ]
11
+ }