Skip to content

Commit c838502

Browse files
committed
docs: refresh portfolio docs
1 parent 321dc8f commit c838502

4 files changed

Lines changed: 27 additions & 2 deletions

File tree

.portfolio/architecture.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,21 @@ Features like Docker, GitHub Actions, and VS Code settings are modular add-ons:
189189
- **Post-creation addition**: Features can be added to existing projects via `quickforge add`
190190
- **Independent templates**: Each feature has its own template set
191191

192+
### 8. Context-Aware Escaping for Generated Code
193+
194+
The Jinja2 environment runs with autoescaping disabled — correct for emitting code and config rather than HTML, but it shifts the responsibility for escaping onto the templates. Since a project's description and author name are free text, a stray quote, backslash, or brace would otherwise produce a broken `pyproject.toml` or unparsable Python file.
195+
196+
I solved this with destination-specific filters in `generator.py` rather than blanket input sanitization:
197+
198+
- `toml_escape` for TOML basic strings
199+
- `py_escape` for Python string and docstring literals
200+
- `fstring_escape` for f-string contexts (also doubles `{`/`}` so braces aren't read as replacement fields)
201+
- `github_slug` to reduce an author name to a URL-safe path segment
202+
203+
This keeps user input verbatim where it's displayed and escapes it precisely where it's embedded, so the generated `pyproject.toml` round-trips the exact description while still parsing as valid TOML.
204+
205+
A related concern is license metadata: PyPI trove classifiers use a controlled vocabulary that does not match SPDX identifiers (SPDX `Apache-2.0` is the classifier `Apache Software License`). `License.classifier` and `License.spdx_id` in `models.py` map each license to the correct value for both fields, and the non-SPDX `Proprietary` case is emitted as a `LicenseRef-` expression so the generated metadata is valid.
206+
192207
## Module Responsibilities
193208

194209
| Module | Responsibility |

.portfolio/qa.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ Project creation is a linear pipeline (`validate -> create dirs -> render templa
4141
### Conditional template rendering
4242
Templates are tagged by archetype and feature flags. `cli.py.j2` is rendered only for `--type cli`; the FastAPI router only for `--type api`; Docker, MkDocs, devcontainer files only when their flags are set. The generated tree contains only files the user actually asked for.
4343

44+
### Context-aware escaping in a code generator
45+
Jinja2 autoescaping is built for HTML; for emitting TOML and Python it's the wrong default, so it's disabled — which means free-text fields (a project description, an author name) could otherwise inject a stray quote or brace and break the generated files. Rather than sanitizing input globally, I escape at the point of use with destination-specific filters in `src/quickforge/generator.py`: `toml_escape` for TOML strings, `py_escape` for Python literals and docstrings, and `fstring_escape` for f-string contexts (which additionally doubles braces so they aren't parsed as replacement fields). The result is that a description containing `"` and `{}` is preserved verbatim in `pyproject.toml` while the file still parses as valid TOML.
46+
4447
## Engineering Decisions
4548

4649
### tomlkit vs tomli for upgrades
@@ -81,6 +84,12 @@ One tool replaces three, with a single config block in `pyproject.toml` and subs
8184
### Why basedpyright instead of mypy?
8285
basedpyright (a fork of pyright) is significantly faster, stricter by default, and produces clearer error messages with better editor integration. For new projects starting fresh, the stricter defaults catch bugs the day they're written.
8386

87+
### What happens if my project description or author name contains quotes or braces?
88+
It's handled. Because the generator emits code and config (not HTML), free-text fields are escaped per destination — TOML strings, Python literals/docstrings, and f-strings each have their own escaping rule — so a description like `He said "hi" {now}` lands intact in `pyproject.toml` and the generated Python still compiles.
89+
90+
### Which licenses generate a real LICENSE file?
91+
All six offered (MIT, Apache-2.0, GPL-3.0-only, BSD-3-Clause, Unlicense, Proprietary) emit a complete `LICENSE` file, and the project's PyPI trove classifier and SPDX expression are set to the correct values for the chosen license — including the `LicenseRef-` form for the non-SPDX Proprietary case.
92+
8493
### Can I customize the generated templates?
8594
Not at the moment — templates are bundled with the package. The intent is that the generated output is opinionated. If you need a different stack, fork the project or modify the generated files after creation.
8695

.portfolio/stack.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ quickforge/
132132
│ ├── generator.py # Project generation logic
133133
│ ├── auditor.py # Project analysis
134134
│ ├── upgrader.py # Migration logic
135-
│ └── templates/ # Jinja2 templates (18 files)
135+
│ └── templates/ # Jinja2 templates (22 files, incl. 6 license texts)
136136
├── tests/
137137
│ ├── conftest.py # pytest fixtures
138138
│ ├── test_cli.py # CLI tests

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ That's it! Your project is ready with all the best practices built in.
4141
- ✅ VS Code settings optimized
4242
- ✅ Type checking enabled from day one
4343
- ✅ Test skeleton with pytest + coverage
44+
- ✅ Full-text `LICENSE` file for any of six licenses (MIT, Apache-2.0, GPL-3.0, BSD-3-Clause, Unlicense, Proprietary)
4445

4546
### 🔄 **Migration Tools**
4647
- Upgrade legacy projects to modern tooling
@@ -166,7 +167,7 @@ quickforge new myproject [OPTIONS]
166167
|--------|-------|-------------|
167168
| `--type` | `-t` | Project type: library, app, cli, api, script |
168169
| `--python` | `-p` | Python version: 3.11, 3.12, 3.13 |
169-
| `--license` | `-l` | License: MIT, Apache-2.0, GPL-3.0-only, BSD-3-Clause |
170+
| `--license` | `-l` | License: MIT, Apache-2.0, GPL-3.0-only, BSD-3-Clause, Unlicense, Proprietary |
170171
| `--author` | `-a` | Author name |
171172
| `--email` | `-e` | Author email |
172173
| `--output` | `-o` | Output directory |

0 commit comments

Comments
 (0)