wizard-codegen 0.1.5__py3-none-any.whl
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.
- cli/__init__.py +28 -0
- cli/main.py +155 -0
- core/__init__.py +22 -0
- core/config.py +123 -0
- core/context_builder.py +321 -0
- core/filter.py +65 -0
- core/renderer.py +77 -0
- core/writer.py +70 -0
- hooks/__init__.py +6 -0
- hooks/hooks.py +28 -0
- proto/__init__.py +19 -0
- proto/discover.py +70 -0
- proto/fds_loader.py +51 -0
- proto/proto_source.py +119 -0
- proto/protoc_runner.py +55 -0
- utils/__init__.py +17 -0
- utils/name.py +69 -0
- wizard_codegen-0.1.5.dist-info/METADATA +1068 -0
- wizard_codegen-0.1.5.dist-info/RECORD +21 -0
- wizard_codegen-0.1.5.dist-info/WHEEL +4 -0
- wizard_codegen-0.1.5.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,1068 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: wizard-codegen
|
|
3
|
+
Version: 0.1.5
|
|
4
|
+
Summary: A powerful, template-driven code generation tool for Protocol Buffers
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Requires-Dist: click==8.3.1
|
|
7
|
+
Requires-Dist: jinja2==3.1.6
|
|
8
|
+
Requires-Dist: protobuf==6.33.2
|
|
9
|
+
Requires-Dist: pydantic-core==2.41.5
|
|
10
|
+
Requires-Dist: pydantic==2.12.5
|
|
11
|
+
Requires-Dist: pyyaml==6.0.3
|
|
12
|
+
Requires-Dist: rich==14.2.0
|
|
13
|
+
Requires-Dist: typer==0.20.1
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# ๐ง Wizard Codegen
|
|
17
|
+
|
|
18
|
+
[](https://dl.circleci.com/status-badge/redirect/gh/ConsultingMD/wizard-codegen-experiment/tree/main)
|
|
19
|
+
[](https://codecov.io/gh/ConsultingMD/wizard-codegen-experiment)
|
|
20
|
+
[](https://www.python.org/downloads/)
|
|
21
|
+
|
|
22
|
+
> **A powerful, template-driven code generation tool for Protocol Buffers**
|
|
23
|
+
|
|
24
|
+
Wizard Codegen transforms your `.proto` definitions into typed code for multiple programming languages using customizable Jinja2 templates. Whether you're building React components, Swift structs, Kotlin data classes, or Go handlers โ this tool has you covered.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## ๐ Table of Contents
|
|
29
|
+
|
|
30
|
+
- [Features](#-features)
|
|
31
|
+
- [Quick Start](#-quick-start)
|
|
32
|
+
- [Installation](#-installation)
|
|
33
|
+
- [CLI Usage](#-cli-usage)
|
|
34
|
+
- [Configuration](#-configuration)
|
|
35
|
+
- [Proto Configuration](#proto-configuration)
|
|
36
|
+
- [Targets Configuration](#targets-configuration)
|
|
37
|
+
- [Hooks Configuration](#hooks-configuration)
|
|
38
|
+
- [Template Authoring](#-template-authoring)
|
|
39
|
+
- [Template Context](#template-context)
|
|
40
|
+
- [Name Transformations](#name-transformations)
|
|
41
|
+
- [Built-in Filters](#built-in-filters)
|
|
42
|
+
- [Custom Hooks](#-custom-hooks)
|
|
43
|
+
- [Architecture](#-architecture)
|
|
44
|
+
- [Developer Guide](#-developer-guide)
|
|
45
|
+
- [Project Structure](#project-structure)
|
|
46
|
+
- [Running Tests](#running-tests)
|
|
47
|
+
- [Adding New Target Languages](#adding-new-target-languages)
|
|
48
|
+
- [Contributing](#contributing)
|
|
49
|
+
- [Examples](#-examples)
|
|
50
|
+
- [Troubleshooting](#-troubleshooting)
|
|
51
|
+
- [License](#-license)
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## โจ Features
|
|
56
|
+
|
|
57
|
+
| Feature | Description |
|
|
58
|
+
|---------|-------------|
|
|
59
|
+
| **Multi-Language Support** | Generate code for TypeScript, Swift, Kotlin, Go, and more |
|
|
60
|
+
| **Jinja2 Templates** | Full power of Jinja2 templating with custom filters |
|
|
61
|
+
| **Flexible Filtering** | Target specific messages, enums, or services with `where` clauses |
|
|
62
|
+
| **Git Proto Sources** | Fetch proto definitions directly from Git repositories |
|
|
63
|
+
| **Multiple Write Modes** | `overwrite`, `append`, or `write-once` file generation |
|
|
64
|
+
| **Custom Hooks** | Extend with your own Jinja filters and helpers |
|
|
65
|
+
| **Name Transformations** | Auto-convert between `snake_case`, `kebab-case`, `PascalCase`, `camelCase`, `MACROCASE`, `MACRO_SNAKE_CASE` |
|
|
66
|
+
| **Dry Run Mode** | Preview changes without writing files |
|
|
67
|
+
| **Verbose Output** | Debug with detailed proto and context inspection |
|
|
68
|
+
| **Nested Type Support** | Full support for nested messages and enums |
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## ๐ Quick Start
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
# 1. Clone and setup
|
|
76
|
+
git clone <your-repo-url>
|
|
77
|
+
cd wizard-codegen
|
|
78
|
+
make deps
|
|
79
|
+
|
|
80
|
+
# 2. Create your configuration
|
|
81
|
+
cat > wizard/codegen.yaml << 'EOF'
|
|
82
|
+
proto:
|
|
83
|
+
cache_dir: ".cache/protos"
|
|
84
|
+
root: "path/to/your/protos"
|
|
85
|
+
files:
|
|
86
|
+
- "**/*.proto"
|
|
87
|
+
|
|
88
|
+
targets:
|
|
89
|
+
typescript:
|
|
90
|
+
templates: "wizard/templates/ts"
|
|
91
|
+
out: "src/generated/ts"
|
|
92
|
+
render:
|
|
93
|
+
- template: "form.j2"
|
|
94
|
+
for_each: "message"
|
|
95
|
+
where:
|
|
96
|
+
all:
|
|
97
|
+
- name: "*Form"
|
|
98
|
+
output: "components/{{ item.name.kebab_case }}/index.tsx"
|
|
99
|
+
EOF
|
|
100
|
+
|
|
101
|
+
# 3. Generate code
|
|
102
|
+
uv run wizard-codegen generate
|
|
103
|
+
|
|
104
|
+
# Or with verbose output
|
|
105
|
+
uv run wizard-codegen --verbose generate
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## ๐ฆ Installation
|
|
111
|
+
|
|
112
|
+
### Prerequisites
|
|
113
|
+
|
|
114
|
+
- **Python 3.10+**
|
|
115
|
+
- **[uv](https://docs.astral.sh/uv/)** โ Fast Python package manager (installed via `ih-setup upgrade`)
|
|
116
|
+
- **protoc** (Protocol Buffer Compiler) โ [Installation Guide](https://grpc.io/docs/protoc-installation/)
|
|
117
|
+
- **Git** (for fetching proto sources from repositories)
|
|
118
|
+
|
|
119
|
+
### Install via uv tool (Recommended)
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
# Install protobuf compiler
|
|
123
|
+
brew install protobuf
|
|
124
|
+
|
|
125
|
+
# Install wizard-codegen from public PyPI
|
|
126
|
+
uv tool install wizard-codegen
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
#### Upgrade
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
uv tool upgrade wizard-codegen
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### From Source
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
# Clone the repository
|
|
139
|
+
git clone <repo-url>
|
|
140
|
+
cd wizard-codegen
|
|
141
|
+
|
|
142
|
+
# Install protobuf compiler
|
|
143
|
+
brew install protobuf
|
|
144
|
+
|
|
145
|
+
# Install dependencies
|
|
146
|
+
make deps
|
|
147
|
+
|
|
148
|
+
# Install the cli
|
|
149
|
+
make install
|
|
150
|
+
|
|
151
|
+
# Run the CLI
|
|
152
|
+
wizard-codegen --version
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
#### Upgrade From Source
|
|
156
|
+
```bash
|
|
157
|
+
make upgrade
|
|
158
|
+
````
|
|
159
|
+
|
|
160
|
+
### Dependencies
|
|
161
|
+
|
|
162
|
+
Dependencies are managed in `pyproject.toml` and locked in `uv.lock`:
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
typer==0.20.1 # CLI framework
|
|
166
|
+
pydantic==2.12.5 # Configuration validation
|
|
167
|
+
jinja2==3.1.6 # Template engine
|
|
168
|
+
rich==14.2.0 # Beautiful terminal output
|
|
169
|
+
protobuf==6.33.2 # Proto descriptor handling
|
|
170
|
+
pyyaml==6.0.3 # YAML configuration
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## ๐ฅ๏ธ CLI Usage
|
|
176
|
+
|
|
177
|
+
The CLI is built with [Typer](https://typer.tiangolo.com/) and provides rich, colorful output.
|
|
178
|
+
|
|
179
|
+
### Commands
|
|
180
|
+
|
|
181
|
+
#### `generate` โ Generate code from protos
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
# Basic generation
|
|
185
|
+
wizard-codegen generate
|
|
186
|
+
|
|
187
|
+
# With custom config file
|
|
188
|
+
wizard-codegen --config path/to/codegen.yaml generate
|
|
189
|
+
|
|
190
|
+
# Dry run (preview without writing)
|
|
191
|
+
wizard-codegen --dry-run generate
|
|
192
|
+
|
|
193
|
+
# Verbose mode (detailed output)
|
|
194
|
+
wizard-codegen --verbose generate
|
|
195
|
+
|
|
196
|
+
# Combine flags
|
|
197
|
+
wizard-codegen --verbose --dry-run --config custom.yaml generate
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
#### `list-protos` โ List discovered proto files
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
wizard-codegen list-protos
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Output:
|
|
207
|
+
```
|
|
208
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
209
|
+
โ Available proto schemas โ
|
|
210
|
+
โโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
|
|
211
|
+
โ Name โ Path โ
|
|
212
|
+
โโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
|
|
213
|
+
โ user โ user/user.proto โ
|
|
214
|
+
โ user_form โ user/user_form.proto โ
|
|
215
|
+
โ order โ order/order.proto โ
|
|
216
|
+
โโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
#### `validate` โ Validate templates and configuration
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
wizard-codegen validate
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Validates:
|
|
226
|
+
- Configuration file syntax
|
|
227
|
+
- Proto file discovery
|
|
228
|
+
- Template syntax and variable resolution
|
|
229
|
+
- Descriptor set generation
|
|
230
|
+
|
|
231
|
+
### Global Options
|
|
232
|
+
|
|
233
|
+
| Option | Short | Description |
|
|
234
|
+
|--------|-------|-------------|
|
|
235
|
+
| `--version` | `-V` | Show version and exit |
|
|
236
|
+
| `--config` | `-c` | Path to codegen YAML configuration (default: `wizard/codegen.yaml`) |
|
|
237
|
+
| `--verbose` | `-v` | Enable detailed logging and context printing |
|
|
238
|
+
| `--dry-run` | | Preview actions without writing files |
|
|
239
|
+
| `--local` | `-l` | Use local `proto.root` instead of git source |
|
|
240
|
+
| `--help` | | Show help message |
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## โ๏ธ Configuration
|
|
245
|
+
|
|
246
|
+
Configuration is defined in YAML format. The default location is `wizard/codegen.yaml`.
|
|
247
|
+
|
|
248
|
+
### Complete Example
|
|
249
|
+
|
|
250
|
+
```yaml
|
|
251
|
+
proto:
|
|
252
|
+
# Git source configuration (used by default)
|
|
253
|
+
source:
|
|
254
|
+
git: "git@github.com:YourOrg/proto-definitions.git"
|
|
255
|
+
ref: "latest-tag" # Default: auto-resolves to latest semver tag
|
|
256
|
+
# fds: "build/descriptor.pb" # Optional: use pre-built descriptor
|
|
257
|
+
include_info: true # Include source info in descriptors
|
|
258
|
+
|
|
259
|
+
# Local path (used with --local flag for development)
|
|
260
|
+
root: "../local-protos"
|
|
261
|
+
|
|
262
|
+
# Cache directory for git clones
|
|
263
|
+
cache_dir: ".cache/proto-common"
|
|
264
|
+
|
|
265
|
+
# Include paths (relative to proto root)
|
|
266
|
+
includes:
|
|
267
|
+
- "{proto_root}/shared"
|
|
268
|
+
- "{proto_root}/service/wizard"
|
|
269
|
+
|
|
270
|
+
# File patterns to discover
|
|
271
|
+
files:
|
|
272
|
+
- "pages/**/*.proto"
|
|
273
|
+
- "models/*.proto"
|
|
274
|
+
|
|
275
|
+
targets:
|
|
276
|
+
typescript:
|
|
277
|
+
templates: "wizard/templates/ts"
|
|
278
|
+
out: "src/generated/ts"
|
|
279
|
+
render:
|
|
280
|
+
# Generate form components for *Form messages
|
|
281
|
+
- template: "form.j2"
|
|
282
|
+
for_each: "message"
|
|
283
|
+
mode: "write-once"
|
|
284
|
+
where:
|
|
285
|
+
all:
|
|
286
|
+
- name: "*Form"
|
|
287
|
+
not:
|
|
288
|
+
- file: "*/shared/design_system/*"
|
|
289
|
+
output: "components/{{ item.name.kebab_case }}-form/index.tsx"
|
|
290
|
+
|
|
291
|
+
# Generate context providers for *Context messages
|
|
292
|
+
- template: "context.j2"
|
|
293
|
+
for_each: "message"
|
|
294
|
+
where:
|
|
295
|
+
any:
|
|
296
|
+
- name: "*Context"
|
|
297
|
+
not:
|
|
298
|
+
- file: "*/shared/design_system/*"
|
|
299
|
+
output: "contexts/{{ item.name.kebab_case }}/index.tsx"
|
|
300
|
+
|
|
301
|
+
swift:
|
|
302
|
+
templates: "wizard/templates/swift"
|
|
303
|
+
out: "ios/Generated"
|
|
304
|
+
render:
|
|
305
|
+
- template: "struct.j2"
|
|
306
|
+
for_each: "message"
|
|
307
|
+
output: "{{ item.name.pascal_case }}.swift"
|
|
308
|
+
|
|
309
|
+
kotlin:
|
|
310
|
+
templates: "wizard/templates/kotlin"
|
|
311
|
+
out: "android/generated"
|
|
312
|
+
render:
|
|
313
|
+
- template: "data_class.j2"
|
|
314
|
+
for_each: "message"
|
|
315
|
+
output: "{{ item.name.pascal_case }}.kt"
|
|
316
|
+
|
|
317
|
+
hooks:
|
|
318
|
+
root: "wizard"
|
|
319
|
+
module: "hook_sample" # Optional: custom filters module
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### Proto Configuration
|
|
323
|
+
|
|
324
|
+
| Key | Type | Description |
|
|
325
|
+
|-----|------|-------------|
|
|
326
|
+
| `root` | `string` | Local path to proto files (used with `--local` flag) |
|
|
327
|
+
| `source.git` | `string` | Git repository URL for proto source |
|
|
328
|
+
| `source.ref` | `string` | Git ref: tag, branch, commit SHA, or `latest-tag` (default: `latest-tag`) |
|
|
329
|
+
| `source.fds` | `string` | Path to pre-built FileDescriptorSet (optional) |
|
|
330
|
+
| `source.include_info` | `bool` | Include source info in protoc output (default: `true`) |
|
|
331
|
+
| `cache_dir` | `string` | Directory for caching git clones |
|
|
332
|
+
| `includes` | `list[string]` | Include paths for proto imports |
|
|
333
|
+
| `files` | `list[string]` | Glob patterns for proto file discovery |
|
|
334
|
+
|
|
335
|
+
#### Special Ref: `latest-tag`
|
|
336
|
+
|
|
337
|
+
Use `ref: "latest-tag"` to automatically checkout the latest semver tag from the repository:
|
|
338
|
+
|
|
339
|
+
```yaml
|
|
340
|
+
proto:
|
|
341
|
+
source:
|
|
342
|
+
git: "git@github.com:YourOrg/proto-definitions.git"
|
|
343
|
+
ref: "latest-tag" # Automatically resolves to latest semver tag (e.g., v2.1.0)
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
This feature:
|
|
347
|
+
- Lists all tags in the repository
|
|
348
|
+
- Filters for valid semver tags (e.g., `v1.2.3`, `1.0.0`, `v2.0.0-beta`)
|
|
349
|
+
- Prefers stable releases over prereleases (e.g., `v1.0.0` > `v1.0.0-beta`)
|
|
350
|
+
- Checks out the highest version according to semver ordering
|
|
351
|
+
|
|
352
|
+
#### Proto Source Resolution
|
|
353
|
+
|
|
354
|
+
You can configure **both** a local path (`proto.root`) and a git source (`proto.source`) simultaneously. This allows developers to easily switch between tagged releases and local development:
|
|
355
|
+
|
|
356
|
+
**Default behavior (no flags):**
|
|
357
|
+
1. `proto.source.git` โ clone/fetch from git repository
|
|
358
|
+
2. Falls back to `proto.root` โ if no git source is configured
|
|
359
|
+
3. Error โ if neither is available
|
|
360
|
+
|
|
361
|
+
**With `--local` flag:**
|
|
362
|
+
1. `proto.root` โ use local filesystem path
|
|
363
|
+
2. Error โ if `proto.root` is not configured or doesn't exist
|
|
364
|
+
|
|
365
|
+
This design enables a common workflow:
|
|
366
|
+
- **CI/Production**: Uses git source with `latest-tag` (the default ref)
|
|
367
|
+
- **Local Development**: Use `--local` flag to test against local proto changes
|
|
368
|
+
|
|
369
|
+
```bash
|
|
370
|
+
# Use git source (default - fetches latest tag)
|
|
371
|
+
wizard-codegen generate
|
|
372
|
+
|
|
373
|
+
# Use local protos for development
|
|
374
|
+
wizard-codegen --local generate
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### Targets Configuration
|
|
378
|
+
|
|
379
|
+
Each target represents a language/output configuration.
|
|
380
|
+
|
|
381
|
+
| Key | Type | Description |
|
|
382
|
+
|-----|------|-------------|
|
|
383
|
+
| `templates` | `string` | Path to Jinja2 template directory |
|
|
384
|
+
| `out` | `string` | Output directory for generated files |
|
|
385
|
+
| `render` | `list` | List of render rules |
|
|
386
|
+
|
|
387
|
+
#### Render Rules
|
|
388
|
+
|
|
389
|
+
| Key | Type | Description |
|
|
390
|
+
|-----|------|-------------|
|
|
391
|
+
| `template` | `string` | Template filename (relative to templates dir) |
|
|
392
|
+
| `output` | `string` | Output path pattern (Jinja2 template string) |
|
|
393
|
+
| `for_each` | `enum` | Iteration mode: `file`, `message`, `enum`, `service` |
|
|
394
|
+
| `mode` | `enum` | Write mode: `overwrite`, `append`, `write-once` |
|
|
395
|
+
| `where` | `object` | Filter predicates (optional) |
|
|
396
|
+
|
|
397
|
+
#### Write Modes
|
|
398
|
+
|
|
399
|
+
| Mode | Description |
|
|
400
|
+
|------|-------------|
|
|
401
|
+
| `overwrite` | Always replace existing files (default) |
|
|
402
|
+
| `append` | Add content to end of existing files |
|
|
403
|
+
| `write-once` | Only create if file doesn't exist |
|
|
404
|
+
|
|
405
|
+
#### Where Clauses (Filtering)
|
|
406
|
+
|
|
407
|
+
Filter which items to generate for:
|
|
408
|
+
|
|
409
|
+
```yaml
|
|
410
|
+
where:
|
|
411
|
+
all: # AND conditions (all must match)
|
|
412
|
+
- name: "*Form"
|
|
413
|
+
- package: "wizard.*"
|
|
414
|
+
any: # OR conditions (at least one must match)
|
|
415
|
+
- name: "*Context"
|
|
416
|
+
- name: "*Provider"
|
|
417
|
+
not: # Exclude matching items
|
|
418
|
+
- file: "*/design_system/*"
|
|
419
|
+
- name: "*Internal"
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
##### Predicate Fields
|
|
423
|
+
|
|
424
|
+
| Field | Description | Example |
|
|
425
|
+
|-------|-------------|---------|
|
|
426
|
+
| `name` | Message/enum/service name | `"*Form"`, `"User*"`, `"Order"` |
|
|
427
|
+
| `package` | Proto package name | `"wizard.*"`, `"com.example.*"` |
|
|
428
|
+
| `file` | Proto file path | `"*/shared/*"`, `"user/*.proto"` |
|
|
429
|
+
| `full_name` | Fully qualified name | `".wizard.UserForm"` |
|
|
430
|
+
| `option.equals` | Match proto options | `{ key: "deprecated", value: true }` |
|
|
431
|
+
|
|
432
|
+
Patterns support:
|
|
433
|
+
- **Glob patterns**: `*`, `?`, `[abc]`
|
|
434
|
+
- **Regex patterns**: Any valid Python regex
|
|
435
|
+
|
|
436
|
+
### Hooks Configuration
|
|
437
|
+
|
|
438
|
+
| Key | Type | Description |
|
|
439
|
+
|-----|------|-------------|
|
|
440
|
+
| `root` | `string` | Root directory for hooks module (default: `"wizard"`) |
|
|
441
|
+
| `module` | `string` | Python module name with `register()` function |
|
|
442
|
+
|
|
443
|
+
---
|
|
444
|
+
|
|
445
|
+
## ๐ Template Authoring
|
|
446
|
+
|
|
447
|
+
Templates use [Jinja2](https://jinja.palletsprojects.com/) syntax with custom extensions.
|
|
448
|
+
|
|
449
|
+
### Template Context
|
|
450
|
+
|
|
451
|
+
When a template is rendered, it receives a rich context:
|
|
452
|
+
|
|
453
|
+
```python
|
|
454
|
+
{
|
|
455
|
+
"proto_root": "/path/to/protos",
|
|
456
|
+
|
|
457
|
+
# Current item (when using for_each)
|
|
458
|
+
"item": {
|
|
459
|
+
"name": Name("UserForm"), # Name object with case transformations
|
|
460
|
+
"full_name": ".wizard.UserForm",
|
|
461
|
+
"file": "user/user_form.proto",
|
|
462
|
+
"package": "wizard",
|
|
463
|
+
"fields": [...], # List of field objects
|
|
464
|
+
"nested_messages": [...], # Nested message definitions
|
|
465
|
+
"nested_enums": [...], # Nested enum definitions
|
|
466
|
+
},
|
|
467
|
+
|
|
468
|
+
# All files (for cross-referencing)
|
|
469
|
+
"files": [...],
|
|
470
|
+
|
|
471
|
+
# Indexes for quick lookup
|
|
472
|
+
"message": { ".package.MessageName": {...}, ... },
|
|
473
|
+
"enum": { ".package.EnumName": {...}, ... },
|
|
474
|
+
"service": { ".package.ServiceName": {...}, ... },
|
|
475
|
+
"types": { ".package.TypeName": {...}, ... },
|
|
476
|
+
}
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
#### File Object
|
|
480
|
+
|
|
481
|
+
```python
|
|
482
|
+
{
|
|
483
|
+
"name": Name("user_form.proto"),
|
|
484
|
+
"basename": "user_form",
|
|
485
|
+
"package": "wizard",
|
|
486
|
+
"package_path": "wizard",
|
|
487
|
+
"imports": ["common/timestamp.proto"],
|
|
488
|
+
"messages": [...],
|
|
489
|
+
"enums": [...],
|
|
490
|
+
"services": [...],
|
|
491
|
+
"options": <FileOptions>,
|
|
492
|
+
}
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
#### Message Object
|
|
496
|
+
|
|
497
|
+
```python
|
|
498
|
+
{
|
|
499
|
+
"name": Name("UserForm"),
|
|
500
|
+
"full_name": ".wizard.UserForm",
|
|
501
|
+
"file": "user/user_form.proto",
|
|
502
|
+
"package": "wizard",
|
|
503
|
+
"fields": [
|
|
504
|
+
{
|
|
505
|
+
"name": Name("user_name"),
|
|
506
|
+
"number": 1,
|
|
507
|
+
"label": 1, # 1=optional, 2=required, 3=repeated
|
|
508
|
+
"type": 9, # Proto type number (9=string)
|
|
509
|
+
"type_name": "", # For message/enum refs: ".package.Type"
|
|
510
|
+
"json_name": "userName",
|
|
511
|
+
"field": <FieldDescriptor>,
|
|
512
|
+
},
|
|
513
|
+
...
|
|
514
|
+
],
|
|
515
|
+
"nested_messages": [...],
|
|
516
|
+
"nested_enums": [...],
|
|
517
|
+
}
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
#### Enum Object
|
|
521
|
+
|
|
522
|
+
```python
|
|
523
|
+
{
|
|
524
|
+
"name": Name("Status"),
|
|
525
|
+
"full_name": ".wizard.Status",
|
|
526
|
+
"file": "common/status.proto",
|
|
527
|
+
"package": "wizard",
|
|
528
|
+
"enum_values": [
|
|
529
|
+
{"name": "STATUS_UNKNOWN", "number": 0},
|
|
530
|
+
{"name": "STATUS_ACTIVE", "number": 1},
|
|
531
|
+
...
|
|
532
|
+
],
|
|
533
|
+
}
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
#### Service Object
|
|
537
|
+
|
|
538
|
+
```python
|
|
539
|
+
{
|
|
540
|
+
"name": Name("UserService"),
|
|
541
|
+
"file": "user/user_service.proto",
|
|
542
|
+
"package": "wizard",
|
|
543
|
+
"methods": [
|
|
544
|
+
{
|
|
545
|
+
"name": Name("GetUser"),
|
|
546
|
+
"input_type": ".wizard.GetUserRequest",
|
|
547
|
+
"output_type": ".wizard.User",
|
|
548
|
+
"client_streaming": False,
|
|
549
|
+
"server_streaming": False,
|
|
550
|
+
"options": <MethodOptions>,
|
|
551
|
+
},
|
|
552
|
+
...
|
|
553
|
+
],
|
|
554
|
+
}
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
### Name Transformations
|
|
558
|
+
|
|
559
|
+
Every name in the context is wrapped in a `Name` object that provides automatic case transformations:
|
|
560
|
+
|
|
561
|
+
```jinja
|
|
562
|
+
{{ item.name.raw }} โ UserFormRequest
|
|
563
|
+
{{ item.name.snake_case }} โ user_form_request
|
|
564
|
+
{{ item.name.kebab_case }} โ user-form-request
|
|
565
|
+
{{ item.name.pascal_case }} โ UserFormRequest
|
|
566
|
+
{{ item.name.camel_case }} โ userFormRequest
|
|
567
|
+
{{ item.name.macro_case }} โ USERFORMREQUEST
|
|
568
|
+
{{ item.name.macro_snake_case }} โ USER_FORM_REQUEST
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
Use in output paths:
|
|
572
|
+
```yaml
|
|
573
|
+
output: "{{ item.name.kebab_case }}/index.tsx"
|
|
574
|
+
# Generates: user-form-request/index.tsx
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
Use in templates:
|
|
578
|
+
```jinja
|
|
579
|
+
export interface {{ item.name.pascal_case }} {
|
|
580
|
+
{% for field in item.fields %}
|
|
581
|
+
{{ field.name.camel_case }}: {{ field | ts_type }};
|
|
582
|
+
{% endfor %}
|
|
583
|
+
}
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
### Built-in Filters
|
|
587
|
+
|
|
588
|
+
The following filters are available in all templates:
|
|
589
|
+
|
|
590
|
+
#### Common Filters
|
|
591
|
+
|
|
592
|
+
| Filter | Description | Example |
|
|
593
|
+
|--------|-------------|---------|
|
|
594
|
+
| `replace` | String replacement | `{{ name \| replace("_", "-") }}` |
|
|
595
|
+
|
|
596
|
+
#### TypeScript Filters (via hooks)
|
|
597
|
+
|
|
598
|
+
| Filter | Description | Example |
|
|
599
|
+
|--------|-------------|---------|
|
|
600
|
+
| `ts_type` | Proto to TS type | `{{ field \| ts_type }}` โ `string`, `number`, `User` |
|
|
601
|
+
| `ts_type_optional` | TS type with undefined | `{{ field \| ts_type_optional }}` โ `string \| undefined` |
|
|
602
|
+
| `is_ts_optional` | Check if optional | `{% if field \| is_ts_optional %}?{% endif %}` |
|
|
603
|
+
| `is_repeated` | Check if array | `{% if field \| is_repeated %}[]{% endif %}` |
|
|
604
|
+
|
|
605
|
+
#### Swift Filters (via hooks)
|
|
606
|
+
|
|
607
|
+
| Filter | Description | Example |
|
|
608
|
+
|--------|-------------|---------|
|
|
609
|
+
| `swift_type` | Proto to Swift type | `{{ field \| swift_type }}` โ `String`, `Int32`, `User` |
|
|
610
|
+
| `swift_default` | Swift default value | `{{ field \| swift_default }}` โ `""`, `0`, `[]` |
|
|
611
|
+
|
|
612
|
+
#### Kotlin Filters (via hooks)
|
|
613
|
+
|
|
614
|
+
| Filter | Description | Example |
|
|
615
|
+
|--------|-------------|---------|
|
|
616
|
+
| `kotlin_type` | Proto to Kotlin type | `{{ field \| kotlin_type }}` โ `String`, `Int`, `User` |
|
|
617
|
+
| `kotlin_default` | Kotlin default value | `{{ field \| kotlin_default }}` โ `""`, `0`, `emptyList()` |
|
|
618
|
+
|
|
619
|
+
#### Go Filters (via hooks)
|
|
620
|
+
|
|
621
|
+
| Filter | Description | Example |
|
|
622
|
+
|--------|-------------|---------|
|
|
623
|
+
| `go_type` | Proto to Go type | `{{ field \| go_type }}` โ `string`, `int32`, `*User` |
|
|
624
|
+
| `go_zero` | Go zero value | `{{ field \| go_zero }}` โ `""`, `0`, `nil` |
|
|
625
|
+
| `go_json_tag` | Go JSON struct tag | `{{ field \| go_json_tag }}` โ `` `json:"userName,omitempty"` `` |
|
|
626
|
+
|
|
627
|
+
### Template Example
|
|
628
|
+
|
|
629
|
+
```jinja
|
|
630
|
+
// {{ item.name.pascal_case }}.swift
|
|
631
|
+
// Generated from: {{ item.file }}
|
|
632
|
+
// Package: {{ item.package }}
|
|
633
|
+
|
|
634
|
+
import Foundation
|
|
635
|
+
|
|
636
|
+
/// {{ item.name.pascal_case }} - Auto-generated from protobuf.
|
|
637
|
+
public struct {{ item.name.pascal_case }}: Codable, Equatable, Sendable {
|
|
638
|
+
{% for field in item.fields %}
|
|
639
|
+
/// {{ field.name.raw }} - Proto type: {{ field.type }}
|
|
640
|
+
public var {{ field.name.camel_case }}: {{ field | swift_type }}
|
|
641
|
+
{% endfor %}
|
|
642
|
+
|
|
643
|
+
public init(
|
|
644
|
+
{% for field in item.fields %}
|
|
645
|
+
{{ field.name.camel_case }}: {{ field | swift_type }} = {{ field | swift_default }}{% if not loop.last %},{% endif %}
|
|
646
|
+
{% endfor %}
|
|
647
|
+
) {
|
|
648
|
+
{% for field in item.fields %}
|
|
649
|
+
self.{{ field.name.camel_case }} = {{ field.name.camel_case }}
|
|
650
|
+
{% endfor %}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
---
|
|
656
|
+
|
|
657
|
+
## ๐ Custom Hooks
|
|
658
|
+
|
|
659
|
+
Extend wizard-codegen with custom Jinja2 filters and helpers.
|
|
660
|
+
|
|
661
|
+
### Creating a Hooks Module
|
|
662
|
+
|
|
663
|
+
1. Create a Python file in your hooks root (default: `wizard/`):
|
|
664
|
+
|
|
665
|
+
```python
|
|
666
|
+
# wizard/my_hooks.py
|
|
667
|
+
|
|
668
|
+
from jinja2 import Environment
|
|
669
|
+
from core import CodegenConfig
|
|
670
|
+
|
|
671
|
+
def custom_filter(value):
|
|
672
|
+
"""Your custom filter logic."""
|
|
673
|
+
return str(value).upper()
|
|
674
|
+
|
|
675
|
+
def format_field_doc(field):
|
|
676
|
+
"""Generate documentation for a field."""
|
|
677
|
+
return f"@param {field['name'].camel_case} - {field.get('json_name', '')}"
|
|
678
|
+
|
|
679
|
+
def register(env: Environment, *, target: str, config: CodegenConfig) -> None:
|
|
680
|
+
"""
|
|
681
|
+
Register custom filters and globals.
|
|
682
|
+
|
|
683
|
+
Args:
|
|
684
|
+
env: Jinja2 environment to extend
|
|
685
|
+
target: Current target name (e.g., "typescript", "swift")
|
|
686
|
+
config: Full codegen configuration
|
|
687
|
+
"""
|
|
688
|
+
# Register filters
|
|
689
|
+
env.filters["uppercase"] = custom_filter
|
|
690
|
+
env.filters["field_doc"] = format_field_doc
|
|
691
|
+
|
|
692
|
+
# Register globals (optional)
|
|
693
|
+
env.globals["TARGET"] = target
|
|
694
|
+
|
|
695
|
+
# Target-specific filters
|
|
696
|
+
if target == "typescript":
|
|
697
|
+
env.filters["ts_custom"] = lambda x: f"TS_{x}"
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
2. Reference it in your configuration:
|
|
701
|
+
|
|
702
|
+
```yaml
|
|
703
|
+
hooks:
|
|
704
|
+
root: "wizard"
|
|
705
|
+
module: "my_hooks" # Loads wizard/my_hooks.py
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
3. Use in templates:
|
|
709
|
+
|
|
710
|
+
```jinja
|
|
711
|
+
{{ item.name.raw | uppercase }}
|
|
712
|
+
{{ field | field_doc }}
|
|
713
|
+
Current target: {{ TARGET }}
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
### Type Mapping Example
|
|
717
|
+
|
|
718
|
+
Here's a complete example of type mapping hooks:
|
|
719
|
+
|
|
720
|
+
```python
|
|
721
|
+
# wizard/type_mappings.py
|
|
722
|
+
|
|
723
|
+
from jinja2 import Environment
|
|
724
|
+
|
|
725
|
+
# Proto type constants
|
|
726
|
+
TYPE_STRING = 9
|
|
727
|
+
TYPE_BOOL = 8
|
|
728
|
+
TYPE_INT32 = 5
|
|
729
|
+
TYPE_INT64 = 3
|
|
730
|
+
|
|
731
|
+
TS_TYPES = {
|
|
732
|
+
TYPE_STRING: "string",
|
|
733
|
+
TYPE_BOOL: "boolean",
|
|
734
|
+
TYPE_INT32: "number",
|
|
735
|
+
TYPE_INT64: "bigint",
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
SWIFT_TYPES = {
|
|
739
|
+
TYPE_STRING: "String",
|
|
740
|
+
TYPE_BOOL: "Bool",
|
|
741
|
+
TYPE_INT32: "Int32",
|
|
742
|
+
TYPE_INT64: "Int64",
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
def ts_type(field):
|
|
746
|
+
if field.get("type_name"):
|
|
747
|
+
return field["type_name"].split(".")[-1]
|
|
748
|
+
return TS_TYPES.get(field.get("type"), "unknown")
|
|
749
|
+
|
|
750
|
+
def swift_type(field):
|
|
751
|
+
if field.get("type_name"):
|
|
752
|
+
return field["type_name"].split(".")[-1]
|
|
753
|
+
return SWIFT_TYPES.get(field.get("type"), "Any")
|
|
754
|
+
|
|
755
|
+
def register(env: Environment, *, target: str, config) -> None:
|
|
756
|
+
if target == "typescript":
|
|
757
|
+
env.filters["lang_type"] = ts_type
|
|
758
|
+
elif target == "swift":
|
|
759
|
+
env.filters["lang_type"] = swift_type
|
|
760
|
+
```
|
|
761
|
+
|
|
762
|
+
---
|
|
763
|
+
|
|
764
|
+
## ๐๏ธ Architecture
|
|
765
|
+
|
|
766
|
+
```
|
|
767
|
+
wizard-codegen/
|
|
768
|
+
โโโ cli/ # CLI package
|
|
769
|
+
โ โโโ __init__.py # Re-exports for backwards compatibility
|
|
770
|
+
โ โโโ main.py # CLI entry point (Typer app)
|
|
771
|
+
โโโ core/ # Core business logic
|
|
772
|
+
โ โโโ config.py # Configuration models (Pydantic)
|
|
773
|
+
โ โโโ context_builder.py # Proto โ Jinja context transformation
|
|
774
|
+
โ โโโ filter.py # Where clause filtering
|
|
775
|
+
โ โโโ renderer.py # Jinja2 template rendering
|
|
776
|
+
โ โโโ writer.py # File writing with modes
|
|
777
|
+
โโโ proto/ # Protocol Buffer handling
|
|
778
|
+
โ โโโ discover.py # Proto file discovery
|
|
779
|
+
โ โโโ fds_loader.py # Descriptor set loading
|
|
780
|
+
โ โโโ proto_source.py # Git checkout handling
|
|
781
|
+
โ โโโ protoc_runner.py # protoc execution
|
|
782
|
+
โโโ hooks/ # Plugin system
|
|
783
|
+
โ โโโ hooks.py # Hook loading and protocol
|
|
784
|
+
โโโ utils/ # Utilities
|
|
785
|
+
โ โโโ name.py # Name transformations
|
|
786
|
+
โโโ wizard/ # Example configuration
|
|
787
|
+
โโโ codegen.yaml # Sample config
|
|
788
|
+
โโโ hook_sample.py # Sample hooks
|
|
789
|
+
โโโ templates/ # Sample templates
|
|
790
|
+
```
|
|
791
|
+
|
|
792
|
+
### Pipeline Flow
|
|
793
|
+
|
|
794
|
+
```
|
|
795
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
796
|
+
โ CLI (cli/main.py) โ
|
|
797
|
+
โ parse args, load config โ
|
|
798
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
799
|
+
โ
|
|
800
|
+
โผ
|
|
801
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
802
|
+
โ Proto Resolution (proto/) โ
|
|
803
|
+
โ discover files โ resolve git source โ build descriptor set โ
|
|
804
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
805
|
+
โ
|
|
806
|
+
โผ
|
|
807
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
808
|
+
โ Context Building (core/context_builder.py) โ
|
|
809
|
+
โ FileDescriptorSet โ Jinja-friendly dictionaries โ
|
|
810
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
811
|
+
โ
|
|
812
|
+
โผ
|
|
813
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
814
|
+
โ Rendering (core/renderer.py) โ
|
|
815
|
+
โ for each target โ for each rule โ filter items โ render template โ
|
|
816
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
817
|
+
โ
|
|
818
|
+
โผ
|
|
819
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
820
|
+
โ Writing (core/writer.py) โ
|
|
821
|
+
โ apply write mode โ hash comparison โ write files โ
|
|
822
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
823
|
+
```
|
|
824
|
+
|
|
825
|
+
---
|
|
826
|
+
|
|
827
|
+
## ๐ฉโ๐ป Developer Guide
|
|
828
|
+
|
|
829
|
+
### Project Structure
|
|
830
|
+
|
|
831
|
+
```
|
|
832
|
+
wizard-codegen/
|
|
833
|
+
โโโ cli/ # CLI package (main entry point)
|
|
834
|
+
โโโ core/ # Core modules
|
|
835
|
+
โโโ proto/ # Proto handling
|
|
836
|
+
โโโ hooks/ # Plugin system
|
|
837
|
+
โโโ utils/ # Utilities
|
|
838
|
+
โโโ wizard/ # Example config and templates
|
|
839
|
+
โโโ tests/ # Test suite
|
|
840
|
+
โ โโโ fixtures/ # Test fixtures
|
|
841
|
+
โ โ โโโ protos/ # Sample proto files
|
|
842
|
+
โ โ โโโ templates/ # Test templates
|
|
843
|
+
โ โ โโโ hooks/ # Test hooks
|
|
844
|
+
โ โ โโโ expected_outputs/ # Golden files
|
|
845
|
+
โ โโโ test_*.py # Unit tests
|
|
846
|
+
โ โโโ test_e2e.py # End-to-end tests
|
|
847
|
+
โโโ Makefile # Build automation
|
|
848
|
+
โโโ pyproject.toml # Project config & dependencies
|
|
849
|
+
โโโ uv.lock # Locked dependencies
|
|
850
|
+
```
|
|
851
|
+
|
|
852
|
+
### Running Tests
|
|
853
|
+
|
|
854
|
+
```bash
|
|
855
|
+
# Run all tests
|
|
856
|
+
make test
|
|
857
|
+
|
|
858
|
+
# Run with coverage
|
|
859
|
+
make coverage
|
|
860
|
+
|
|
861
|
+
# Run specific test file
|
|
862
|
+
make test PYTEST_ARGS=tests/test_context_builder.py
|
|
863
|
+
|
|
864
|
+
# Run specific test
|
|
865
|
+
make test PYTEST_ARGS="tests/test_e2e.py::TestFullPipelineGeneration -v"
|
|
866
|
+
```
|
|
867
|
+
|
|
868
|
+
### Code Coverage
|
|
869
|
+
|
|
870
|
+
[](https://codecov.io/gh/ConsultingMD/wizard-codegen-experiment)
|
|
871
|
+
|
|
872
|
+
### Test Categories
|
|
873
|
+
|
|
874
|
+
| Category | Description | Files |
|
|
875
|
+
|----------|-------------|-------|
|
|
876
|
+
| Unit Tests | Test individual functions | `test_config.py`, `test_filter.py`, `test_name.py` |
|
|
877
|
+
| Integration Tests | Test module interactions | `test_context_builder.py`, `test_renderer.py` |
|
|
878
|
+
| E2E Tests | Full pipeline tests | `test_e2e.py` |
|
|
879
|
+
| Fixture Tests | Golden file comparisons | `test_fixtures.py` |
|
|
880
|
+
|
|
881
|
+
### Adding New Target Languages
|
|
882
|
+
|
|
883
|
+
1. **Create templates** in `wizard/templates/<language>/`:
|
|
884
|
+
|
|
885
|
+
```jinja
|
|
886
|
+
{# wizard/templates/rust/struct.j2 #}
|
|
887
|
+
// {{ item.name.pascal_case }}.rs
|
|
888
|
+
|
|
889
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
890
|
+
pub struct {{ item.name.pascal_case }} {
|
|
891
|
+
{% for field in item.fields %}
|
|
892
|
+
pub {{ field.name.snake_case }}: {{ field | rust_type }},
|
|
893
|
+
{% endfor %}
|
|
894
|
+
}
|
|
895
|
+
```
|
|
896
|
+
|
|
897
|
+
2. **Add type mapping hooks** (optional):
|
|
898
|
+
|
|
899
|
+
```python
|
|
900
|
+
# wizard/rust_hooks.py
|
|
901
|
+
|
|
902
|
+
RUST_TYPES = {
|
|
903
|
+
9: "String",
|
|
904
|
+
8: "bool",
|
|
905
|
+
5: "i32",
|
|
906
|
+
3: "i64",
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
def rust_type(field):
|
|
910
|
+
if field.get("type_name"):
|
|
911
|
+
return field["type_name"].split(".")[-1]
|
|
912
|
+
return RUST_TYPES.get(field.get("type"), "Unknown")
|
|
913
|
+
|
|
914
|
+
def register(env, *, target, config):
|
|
915
|
+
if target == "rust":
|
|
916
|
+
env.filters["rust_type"] = rust_type
|
|
917
|
+
```
|
|
918
|
+
|
|
919
|
+
3. **Add target configuration**:
|
|
920
|
+
|
|
921
|
+
```yaml
|
|
922
|
+
targets:
|
|
923
|
+
rust:
|
|
924
|
+
templates: "wizard/templates/rust"
|
|
925
|
+
out: "src/generated"
|
|
926
|
+
render:
|
|
927
|
+
- template: "struct.j2"
|
|
928
|
+
for_each: "message"
|
|
929
|
+
output: "{{ item.name.snake_case }}.rs"
|
|
930
|
+
```
|
|
931
|
+
|
|
932
|
+
### Contributing
|
|
933
|
+
|
|
934
|
+
1. **Fork** the repository
|
|
935
|
+
2. **Create** a feature branch: `git checkout -b feature/my-feature`
|
|
936
|
+
3. **Write tests** for new functionality
|
|
937
|
+
4. **Run** the test suite: `make test`
|
|
938
|
+
5. **Submit** a pull request
|
|
939
|
+
|
|
940
|
+
#### Code Style
|
|
941
|
+
|
|
942
|
+
- Python 3.10+ type hints
|
|
943
|
+
- Black formatting (88 char line length)
|
|
944
|
+
- Docstrings for public functions
|
|
945
|
+
- Comprehensive tests for new features
|
|
946
|
+
|
|
947
|
+
---
|
|
948
|
+
|
|
949
|
+
## ๐ Examples
|
|
950
|
+
|
|
951
|
+
### TypeScript React Form Component
|
|
952
|
+
|
|
953
|
+
```jinja
|
|
954
|
+
{# templates/ts/form.j2 #}
|
|
955
|
+
import React, { useState } from 'react';
|
|
956
|
+
|
|
957
|
+
export interface {{ item.name.pascal_case }}Data {
|
|
958
|
+
{% for field in item.fields %}
|
|
959
|
+
{{ field.name.camel_case }}{% if field | is_ts_optional %}?{% endif %}: {{ field | ts_type }};
|
|
960
|
+
{% endfor %}
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
export const {{ item.name.pascal_case }}: React.FC = () => {
|
|
964
|
+
{% for field in item.fields %}
|
|
965
|
+
const [{{ field.name.camel_case }}, set{{ field.name.pascal_case }}] = useState<{{ field | ts_type }}>();
|
|
966
|
+
{% endfor %}
|
|
967
|
+
|
|
968
|
+
return (
|
|
969
|
+
<form>
|
|
970
|
+
{% for field in item.fields %}
|
|
971
|
+
<input
|
|
972
|
+
name="{{ field.name.snake_case }}"
|
|
973
|
+
value={ {{ field.name.camel_case }} ?? ''}
|
|
974
|
+
onChange={(e) => set{{ field.name.pascal_case }}(e.target.value)}
|
|
975
|
+
/>
|
|
976
|
+
{% endfor %}
|
|
977
|
+
</form>
|
|
978
|
+
);
|
|
979
|
+
};
|
|
980
|
+
```
|
|
981
|
+
|
|
982
|
+
### Kotlin Data Class
|
|
983
|
+
|
|
984
|
+
```jinja
|
|
985
|
+
{# templates/kotlin/data_class.j2 #}
|
|
986
|
+
package {{ item.package }}
|
|
987
|
+
|
|
988
|
+
import kotlinx.serialization.Serializable
|
|
989
|
+
|
|
990
|
+
@Serializable
|
|
991
|
+
data class {{ item.name.pascal_case }}(
|
|
992
|
+
{% for field in item.fields %}
|
|
993
|
+
val {{ field.name.camel_case }}: {{ field | kotlin_type }} = {{ field | kotlin_default }}{% if not loop.last %},{% endif %}
|
|
994
|
+
{% endfor %}
|
|
995
|
+
)
|
|
996
|
+
```
|
|
997
|
+
|
|
998
|
+
### Go Struct with JSON Tags
|
|
999
|
+
|
|
1000
|
+
```jinja
|
|
1001
|
+
{# templates/go/struct.j2 #}
|
|
1002
|
+
package {{ item.package | replace(".", "_") }}
|
|
1003
|
+
|
|
1004
|
+
// {{ item.name.pascal_case }} - Generated from {{ item.file }}
|
|
1005
|
+
type {{ item.name.pascal_case }} struct {
|
|
1006
|
+
{% for field in item.fields %}
|
|
1007
|
+
{{ field.name.pascal_case }} {{ field | go_type }} {{ field | go_json_tag }}
|
|
1008
|
+
{% endfor %}
|
|
1009
|
+
}
|
|
1010
|
+
```
|
|
1011
|
+
|
|
1012
|
+
---
|
|
1013
|
+
|
|
1014
|
+
## ๐ง Troubleshooting
|
|
1015
|
+
|
|
1016
|
+
### Common Issues
|
|
1017
|
+
|
|
1018
|
+
#### "protoc is not available in PATH"
|
|
1019
|
+
|
|
1020
|
+
```bash
|
|
1021
|
+
# macOS
|
|
1022
|
+
brew install protobuf
|
|
1023
|
+
|
|
1024
|
+
# Ubuntu/Debian
|
|
1025
|
+
apt install -y protobuf-compiler
|
|
1026
|
+
|
|
1027
|
+
# Verify installation
|
|
1028
|
+
protoc --version
|
|
1029
|
+
```
|
|
1030
|
+
|
|
1031
|
+
#### "Config error: proto.root not found"
|
|
1032
|
+
|
|
1033
|
+
Either specify a local `proto.root` or configure `proto.source.git`:
|
|
1034
|
+
|
|
1035
|
+
```yaml
|
|
1036
|
+
proto:
|
|
1037
|
+
root: "../my-protos" # Local path
|
|
1038
|
+
# OR
|
|
1039
|
+
source:
|
|
1040
|
+
git: "git@github.com:Org/protos.git"
|
|
1041
|
+
ref: "main"
|
|
1042
|
+
```
|
|
1043
|
+
|
|
1044
|
+
#### "Failed to import hooks module"
|
|
1045
|
+
|
|
1046
|
+
Ensure your hooks module:
|
|
1047
|
+
1. Is in the correct directory (`hooks.root` config)
|
|
1048
|
+
2. Has a `register(env, *, target, config)` function
|
|
1049
|
+
3. Has no import errors
|
|
1050
|
+
|
|
1051
|
+
```bash
|
|
1052
|
+
# Test manually
|
|
1053
|
+
uv run python -c "import wizard.my_hooks"
|
|
1054
|
+
```
|
|
1055
|
+
|
|
1056
|
+
#### "Template variable undefined"
|
|
1057
|
+
|
|
1058
|
+
- Use `--verbose` to inspect the context
|
|
1059
|
+
- Check your `for_each` setting matches the expected context
|
|
1060
|
+
- Verify field names in the proto file
|
|
1061
|
+
|
|
1062
|
+
<div align="center">
|
|
1063
|
+
|
|
1064
|
+
**Made with ๐ง magic**
|
|
1065
|
+
|
|
1066
|
+
[Report Bug](../../issues) ยท [Request Feature](../../issues)
|
|
1067
|
+
|
|
1068
|
+
</div>
|