@typra/emitter 0.2.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/dist/src/cleanup/generated-file.d.ts +6 -0
- package/dist/src/cleanup/generated-file.js +61 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +110 -0
- package/dist/src/decorators.d.ts +56 -0
- package/dist/src/decorators.js +177 -0
- package/dist/src/emitter.d.ts +13 -0
- package/dist/src/emitter.js +137 -0
- package/dist/src/generate.d.ts +86 -0
- package/dist/src/generate.js +104 -0
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.js +5 -0
- package/dist/src/ir/ast.d.ts +235 -0
- package/dist/src/ir/ast.js +589 -0
- package/dist/src/ir/declarations.d.ts +364 -0
- package/dist/src/ir/declarations.js +23 -0
- package/dist/src/ir/expansion.d.ts +140 -0
- package/dist/src/ir/expansion.js +407 -0
- package/dist/src/ir/lower.d.ts +53 -0
- package/dist/src/ir/lower.js +480 -0
- package/dist/src/ir/utilities.d.ts +12 -0
- package/dist/src/ir/utilities.js +39 -0
- package/dist/src/ir/visitor.d.ts +29 -0
- package/dist/src/ir/visitor.js +48 -0
- package/dist/src/languages/csharp/driver.d.ts +5 -0
- package/dist/src/languages/csharp/driver.js +315 -0
- package/dist/src/languages/csharp/emitter.d.ts +33 -0
- package/dist/src/languages/csharp/emitter.js +1140 -0
- package/dist/src/languages/csharp/scaffolding.d.ts +18 -0
- package/dist/src/languages/csharp/scaffolding.js +591 -0
- package/dist/src/languages/csharp/test-emitter.d.ts +43 -0
- package/dist/src/languages/csharp/test-emitter.js +274 -0
- package/dist/src/languages/csharp/visitor.d.ts +14 -0
- package/dist/src/languages/csharp/visitor.js +79 -0
- package/dist/src/languages/go/driver.d.ts +12 -0
- package/dist/src/languages/go/driver.js +128 -0
- package/dist/src/languages/go/emitter.d.ts +33 -0
- package/dist/src/languages/go/emitter.js +879 -0
- package/dist/src/languages/go/scaffolding.d.ts +18 -0
- package/dist/src/languages/go/scaffolding.js +53 -0
- package/dist/src/languages/go/test-emitter.d.ts +20 -0
- package/dist/src/languages/go/test-emitter.js +300 -0
- package/dist/src/languages/go/visitor.d.ts +14 -0
- package/dist/src/languages/go/visitor.js +78 -0
- package/dist/src/languages/markdown/driver.d.ts +19 -0
- package/dist/src/languages/markdown/driver.js +408 -0
- package/dist/src/languages/python/driver.d.ts +14 -0
- package/dist/src/languages/python/driver.js +372 -0
- package/dist/src/languages/python/emitter.d.ts +31 -0
- package/dist/src/languages/python/emitter.js +856 -0
- package/dist/src/languages/python/scaffolding.d.ts +33 -0
- package/dist/src/languages/python/scaffolding.js +279 -0
- package/dist/src/languages/python/test-emitter.d.ts +29 -0
- package/dist/src/languages/python/test-emitter.js +388 -0
- package/dist/src/languages/python/visitor.d.ts +14 -0
- package/dist/src/languages/python/visitor.js +65 -0
- package/dist/src/languages/rust/driver.d.ts +13 -0
- package/dist/src/languages/rust/driver.js +624 -0
- package/dist/src/languages/rust/emitter.d.ts +45 -0
- package/dist/src/languages/rust/emitter.js +1596 -0
- package/dist/src/languages/rust/visitor.d.ts +25 -0
- package/dist/src/languages/rust/visitor.js +153 -0
- package/dist/src/languages/typescript/driver.d.ts +8 -0
- package/dist/src/languages/typescript/driver.js +209 -0
- package/dist/src/languages/typescript/emitter.d.ts +42 -0
- package/dist/src/languages/typescript/emitter.js +904 -0
- package/dist/src/languages/typescript/scaffolding.d.ts +32 -0
- package/dist/src/languages/typescript/scaffolding.js +303 -0
- package/dist/src/languages/typescript/test-emitter.d.ts +23 -0
- package/dist/src/languages/typescript/test-emitter.js +204 -0
- package/dist/src/languages/typescript/visitor.d.ts +14 -0
- package/dist/src/languages/typescript/visitor.js +64 -0
- package/dist/src/lib.d.ts +33 -0
- package/dist/src/lib.js +101 -0
- package/dist/src/testing/index.d.ts +2 -0
- package/dist/src/testing/index.js +8 -0
- package/dist/src/testing/test-context.d.ts +63 -0
- package/dist/src/testing/test-context.js +355 -0
- package/fixtures/shapes/main.tsp +43 -0
- package/fixtures/tspconfig.yaml +13 -0
- package/package.json +76 -0
- package/src/lib/main.tsp +110 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Python scaffolding emitter — static/structural files.
|
|
3
|
+
*
|
|
4
|
+
* Replaces the Nunjucks templates:
|
|
5
|
+
* - `context.py.njk` → emitPythonContext()
|
|
6
|
+
* - `init.py.njk` → emitPythonInit()
|
|
7
|
+
*
|
|
8
|
+
* Also provides:
|
|
9
|
+
* - emitPythonGroupInit() → per-group __init__.py (re-exports group types)
|
|
10
|
+
*
|
|
11
|
+
* These emit files whose content depends only on the type graph
|
|
12
|
+
* shape (not on the Declaration IR used for per-type files).
|
|
13
|
+
*/
|
|
14
|
+
import { TypeNode } from "../../ir/ast.js";
|
|
15
|
+
/**
|
|
16
|
+
* Emit the _context.py file content (LoadContext + SaveContext classes).
|
|
17
|
+
* Replaces context.py.njk template.
|
|
18
|
+
*/
|
|
19
|
+
export declare function emitPythonContext(header: string): string;
|
|
20
|
+
/**
|
|
21
|
+
* Emit the __init__.py file content.
|
|
22
|
+
* Replaces init.py.njk template.
|
|
23
|
+
*
|
|
24
|
+
* When types are organised into group subfolders, the root __init__.py imports
|
|
25
|
+
* from each group sub-package (e.g. `from .connection import Connection`).
|
|
26
|
+
*/
|
|
27
|
+
export declare function emitPythonInit(baseTypes: TypeNode[], types: TypeNode[]): string;
|
|
28
|
+
/**
|
|
29
|
+
* Emit a group-level __init__.py that re-exports all types defined in that group.
|
|
30
|
+
* This file lives at `model/{group}/__init__.py` and lets the root `__init__.py`
|
|
31
|
+
* import from the group package with `from .{group} import TypeA, TypeB, ...`.
|
|
32
|
+
*/
|
|
33
|
+
export declare function emitPythonGroupInit(group: string, groupNodes: TypeNode[]): string;
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Python scaffolding emitter — static/structural files.
|
|
3
|
+
*
|
|
4
|
+
* Replaces the Nunjucks templates:
|
|
5
|
+
* - `context.py.njk` → emitPythonContext()
|
|
6
|
+
* - `init.py.njk` → emitPythonInit()
|
|
7
|
+
*
|
|
8
|
+
* Also provides:
|
|
9
|
+
* - emitPythonGroupInit() → per-group __init__.py (re-exports group types)
|
|
10
|
+
*
|
|
11
|
+
* These emit files whose content depends only on the type graph
|
|
12
|
+
* shape (not on the Declaration IR used for per-type files).
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Emit the _context.py file content (LoadContext + SaveContext classes).
|
|
16
|
+
* Replaces context.py.njk template.
|
|
17
|
+
*/
|
|
18
|
+
export function emitPythonContext(header) {
|
|
19
|
+
const headerLine = header ? `# ${header}\n` : '';
|
|
20
|
+
return `${headerLine}import json
|
|
21
|
+
from dataclasses import dataclass
|
|
22
|
+
from typing import Any, Callable, Optional
|
|
23
|
+
|
|
24
|
+
import yaml
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class LoadContext:
|
|
29
|
+
"""
|
|
30
|
+
Context for customizing the loading process of agent definitions.
|
|
31
|
+
|
|
32
|
+
Provides hooks for pre-processing input data before parsing and
|
|
33
|
+
post-processing output data after instantiation.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
pre_process: Optional[Callable[[dict[str, Any]], dict[str, Any]]] = None
|
|
37
|
+
"""Optional callback to transform input data before parsing."""
|
|
38
|
+
|
|
39
|
+
post_process: Optional[Callable[[Any], Any]] = None
|
|
40
|
+
"""Optional callback to transform the result after instantiation."""
|
|
41
|
+
|
|
42
|
+
def process_input(self, data: dict[str, Any]) -> dict[str, Any]:
|
|
43
|
+
"""
|
|
44
|
+
Apply pre-processing to input data if a pre_process callback is set.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
data: The raw input dictionary to process.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
The processed dictionary, or the original if no callback is set.
|
|
51
|
+
"""
|
|
52
|
+
if self.pre_process is not None:
|
|
53
|
+
return self.pre_process(data)
|
|
54
|
+
return data
|
|
55
|
+
|
|
56
|
+
def process_output(self, result: Any) -> Any:
|
|
57
|
+
"""
|
|
58
|
+
Apply post-processing to the result if a post_process callback is set.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
result: The instantiated object to process.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
The processed result, or the original if no callback is set.
|
|
65
|
+
"""
|
|
66
|
+
if self.post_process is not None:
|
|
67
|
+
return self.post_process(result)
|
|
68
|
+
return result
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@dataclass
|
|
72
|
+
class SaveContext:
|
|
73
|
+
"""
|
|
74
|
+
Context for customizing the serialization process of agent definitions.
|
|
75
|
+
|
|
76
|
+
Provides hooks for pre-processing the object before serialization and
|
|
77
|
+
post-processing the dictionary after serialization.
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
pre_save: Optional[Callable[[Any], Any]] = None
|
|
81
|
+
"""Optional callback to transform the object before serialization."""
|
|
82
|
+
|
|
83
|
+
post_save: Optional[Callable[[dict[str, Any]], dict[str, Any]]] = None
|
|
84
|
+
"""Optional callback to transform the dictionary after serialization."""
|
|
85
|
+
|
|
86
|
+
collection_format: str = "object"
|
|
87
|
+
"""Output format for collections: 'object' (name as key) or 'array' (list of dicts)."""
|
|
88
|
+
|
|
89
|
+
use_shorthand: bool = True
|
|
90
|
+
"""Use shorthand scalar representation when possible (e.g., {"myTool": "function"})."""
|
|
91
|
+
|
|
92
|
+
def process_object(self, obj: Any) -> Any:
|
|
93
|
+
"""
|
|
94
|
+
Apply pre-processing to the object if a pre_save callback is set.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
obj: The object to process before serialization.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
The processed object, or the original if no callback is set.
|
|
101
|
+
"""
|
|
102
|
+
if self.pre_save is not None:
|
|
103
|
+
return self.pre_save(obj)
|
|
104
|
+
return obj
|
|
105
|
+
|
|
106
|
+
def process_dict(self, data: dict[str, Any]) -> dict[str, Any]:
|
|
107
|
+
"""
|
|
108
|
+
Apply post-processing to the dictionary if a post_save callback is set.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
data: The serialized dictionary to process.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
The processed dictionary, or the original if no callback is set.
|
|
115
|
+
"""
|
|
116
|
+
if self.post_save is not None:
|
|
117
|
+
return self.post_save(data)
|
|
118
|
+
return data
|
|
119
|
+
|
|
120
|
+
def to_yaml(self, data: dict[str, Any]) -> str:
|
|
121
|
+
"""
|
|
122
|
+
Convert the dictionary to a YAML string.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
data: The dictionary to convert.
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
The YAML string representation.
|
|
129
|
+
"""
|
|
130
|
+
return yaml.dump(data, default_flow_style=False, sort_keys=False)
|
|
131
|
+
|
|
132
|
+
def to_json(self, data: dict[str, Any], indent: int = 2) -> str:
|
|
133
|
+
"""
|
|
134
|
+
Convert the dictionary to a JSON string.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
data: The dictionary to convert.
|
|
138
|
+
indent: Number of spaces for indentation.
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
The JSON string representation.
|
|
142
|
+
"""
|
|
143
|
+
return json.dumps(data, indent=indent)
|
|
144
|
+
`;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Emit the __init__.py file content.
|
|
148
|
+
* Replaces init.py.njk template.
|
|
149
|
+
*
|
|
150
|
+
* When types are organised into group subfolders, the root __init__.py imports
|
|
151
|
+
* from each group sub-package (e.g. `from .connection import Connection`).
|
|
152
|
+
*/
|
|
153
|
+
export function emitPythonInit(baseTypes, types) {
|
|
154
|
+
const lines = [];
|
|
155
|
+
lines.push('##########################################');
|
|
156
|
+
lines.push('# WARNING: This is an auto-generated file.');
|
|
157
|
+
lines.push('# DO NOT EDIT THIS FILE DIRECTLY');
|
|
158
|
+
lines.push('# ANY EDITS WILL BE LOST');
|
|
159
|
+
lines.push('##########################################');
|
|
160
|
+
lines.push('from ._context import LoadContext, SaveContext');
|
|
161
|
+
// Group root types by their semantic group folder.
|
|
162
|
+
// Types without a group are emitted directly in the root model folder.
|
|
163
|
+
const groupMap = new Map();
|
|
164
|
+
for (const type of baseTypes) {
|
|
165
|
+
const g = type.group || "";
|
|
166
|
+
if (!groupMap.has(g))
|
|
167
|
+
groupMap.set(g, []);
|
|
168
|
+
groupMap.get(g).push(type);
|
|
169
|
+
}
|
|
170
|
+
// Sort groups for deterministic output
|
|
171
|
+
const sortedGroups = Array.from(groupMap.keys()).sort();
|
|
172
|
+
for (const group of sortedGroups) {
|
|
173
|
+
const groupTypes = groupMap.get(group);
|
|
174
|
+
if (!group) {
|
|
175
|
+
// Root-level types — import directly from the file
|
|
176
|
+
for (const type of groupTypes) {
|
|
177
|
+
if (type.childTypes.length > 0) {
|
|
178
|
+
const names = [type.typeName.name, ...type.childTypes.map(c => c.typeName.name)];
|
|
179
|
+
if (!type.isProtocol && type.methods.length > 0) {
|
|
180
|
+
names.push(`${type.typeName.name}Helpers`);
|
|
181
|
+
}
|
|
182
|
+
lines.push('');
|
|
183
|
+
lines.push(`from ._${type.typeName.name} import (`);
|
|
184
|
+
for (const name of names) {
|
|
185
|
+
lines.push(` ${name},`);
|
|
186
|
+
}
|
|
187
|
+
lines.push(')');
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
const names = [type.typeName.name];
|
|
191
|
+
if (!type.isProtocol && type.methods.length > 0) {
|
|
192
|
+
names.push(`${type.typeName.name}Helpers`);
|
|
193
|
+
}
|
|
194
|
+
lines.push('');
|
|
195
|
+
if (names.length === 1) {
|
|
196
|
+
lines.push(`from ._${type.typeName.name} import ${type.typeName.name}`);
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
lines.push(`from ._${type.typeName.name} import (`);
|
|
200
|
+
for (const name of names) {
|
|
201
|
+
lines.push(` ${name},`);
|
|
202
|
+
}
|
|
203
|
+
lines.push(')');
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
// Group subfolder — import from the group's __init__.py (which re-exports all group types)
|
|
210
|
+
lines.push('');
|
|
211
|
+
lines.push(`from .${group} import (`);
|
|
212
|
+
for (const type of groupTypes) {
|
|
213
|
+
const allNames = [type.typeName.name, ...type.childTypes.map(c => c.typeName.name)];
|
|
214
|
+
if (!type.isProtocol && type.methods.length > 0) {
|
|
215
|
+
allNames.push(`${type.typeName.name}Helpers`);
|
|
216
|
+
}
|
|
217
|
+
for (const name of allNames) {
|
|
218
|
+
lines.push(` ${name},`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
lines.push(')');
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
lines.push('');
|
|
225
|
+
lines.push('__all__ = [');
|
|
226
|
+
lines.push(' "LoadContext",');
|
|
227
|
+
lines.push(' "SaveContext",');
|
|
228
|
+
for (const type of types) {
|
|
229
|
+
lines.push(` "${type.typeName.name}",`);
|
|
230
|
+
if (!type.isProtocol && type.methods.length > 0) {
|
|
231
|
+
lines.push(` "${type.typeName.name}Helpers",`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
lines.push(']');
|
|
235
|
+
return lines.join('\n') + '\n';
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Emit a group-level __init__.py that re-exports all types defined in that group.
|
|
239
|
+
* This file lives at `model/{group}/__init__.py` and lets the root `__init__.py`
|
|
240
|
+
* import from the group package with `from .{group} import TypeA, TypeB, ...`.
|
|
241
|
+
*/
|
|
242
|
+
export function emitPythonGroupInit(group, groupNodes) {
|
|
243
|
+
const lines = [];
|
|
244
|
+
lines.push('##########################################');
|
|
245
|
+
lines.push('# WARNING: This is an auto-generated file.');
|
|
246
|
+
lines.push('# DO NOT EDIT THIS FILE DIRECTLY');
|
|
247
|
+
lines.push('# ANY EDITS WILL BE LOST');
|
|
248
|
+
lines.push('##########################################');
|
|
249
|
+
for (const type of groupNodes) {
|
|
250
|
+
const names = [type.typeName.name, ...type.childTypes.map(c => c.typeName.name)];
|
|
251
|
+
if (!type.isProtocol && type.methods.length > 0) {
|
|
252
|
+
names.push(`${type.typeName.name}Helpers`);
|
|
253
|
+
}
|
|
254
|
+
if (names.length === 1) {
|
|
255
|
+
lines.push(`from ._${type.typeName.name} import ${names[0]}`);
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
lines.push(`from ._${type.typeName.name} import (`);
|
|
259
|
+
for (const name of names) {
|
|
260
|
+
lines.push(` ${name},`);
|
|
261
|
+
}
|
|
262
|
+
lines.push(')');
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
lines.push('');
|
|
266
|
+
lines.push('__all__ = [');
|
|
267
|
+
for (const type of groupNodes) {
|
|
268
|
+
const names = [type.typeName.name, ...type.childTypes.map(c => c.typeName.name)];
|
|
269
|
+
if (!type.isProtocol && type.methods.length > 0) {
|
|
270
|
+
names.push(`${type.typeName.name}Helpers`);
|
|
271
|
+
}
|
|
272
|
+
for (const name of names) {
|
|
273
|
+
lines.push(` "${name}",`);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
lines.push(']');
|
|
277
|
+
return lines.join('\n') + '\n';
|
|
278
|
+
}
|
|
279
|
+
//# sourceMappingURL=scaffolding.js.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Python test file emitter — BaseTestContext → pytest source code.
|
|
3
|
+
*
|
|
4
|
+
* Replaces the Nunjucks templates:
|
|
5
|
+
* - `test.py.njk` → emitPythonTest()
|
|
6
|
+
* - `test_context.py.njk` → emitPythonTestContext()
|
|
7
|
+
* - `_macros.njk` → factoryParamTestValue(), renderValidation()
|
|
8
|
+
*
|
|
9
|
+
* The emitter produces pytest functions for:
|
|
10
|
+
* - JSON loading (load_json per example)
|
|
11
|
+
* - YAML loading (load_yaml per example)
|
|
12
|
+
* - Round-trip (load → save → load per example)
|
|
13
|
+
* - Serialization (to_json, to_yaml per example)
|
|
14
|
+
* - Alternate representations (scalar coercions)
|
|
15
|
+
* - Factory methods
|
|
16
|
+
*/
|
|
17
|
+
import { PythonClassContext, BaseTestContext } from "../../ir/ast.js";
|
|
18
|
+
/**
|
|
19
|
+
* Emit the test_context.py file content (tests for LoadContext + SaveContext).
|
|
20
|
+
* Replaces test_context.py.njk template.
|
|
21
|
+
*/
|
|
22
|
+
export declare function emitPythonTestContext(header: string, packageName: string): string;
|
|
23
|
+
/**
|
|
24
|
+
* Emit a pytest test file for a type.
|
|
25
|
+
* Replaces test.py.njk template.
|
|
26
|
+
*/
|
|
27
|
+
export declare function emitPythonTest(ctx: BaseTestContext & {
|
|
28
|
+
classCtx: PythonClassContext;
|
|
29
|
+
}): string;
|