Add canon add target, add test, add dep and doctor

- add target lib|exe <name>: another library or executable in a source
  directory named after it (P1204R0's rule applied per target); a new
  executable links the project's library
- add test <name>: tests/<name>/driver.cpp for a library, or a run of an
  executable, with --arg and --expect
- add dep <dependency>: link a library of this project (no cycles, no
  executables) or an installed package's imported target such as
  fmt::fmt, adding its find_package()
- doctor: read-only check of the files against .canon.toml and the
  P1204R0 layout; errors for missing declared files or broken markers,
  warnings for unbuilt sources, undeclared tests, a hand-edited managed
  block, .h/.cc extensions and include/ or src/ directories
- every add command takes --target; the CLI rejects options a command
  does not take
- manifest: packages, test args, and validation of target names, shared
  source directories and dependencies
- templates moved to canon/templates.hpp
- tests: multi-target and exe-tests scenarios; every scenario ends with
  canon doctor; CANON_CHECK accepts expressions containing commas; the
  CLI test no longer reads a destroyed temporary

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-17 18:38:59 +02:00
parent d38e1466da
commit 651fa4d89b
42 changed files with 2075 additions and 428 deletions
+59 -14
View File
@@ -1,24 +1,43 @@
# canon
Scaffolds C++ projects that follow [P1204R0](https://wg21.link/p1204r0)
(Canonical Project Structure), then keeps adding to them. Written in C++23, no
third-party dependencies.
(Canonical Project Structure), keeps adding to them, and checks them. Written in
C++23, no third-party dependencies.
```bash
canon new lib libhello # library: libhello/hello/hello.{hpp,cpp,test.cpp} + tests/basics
canon new exe hello # executable: hello/hello/hello.cpp
canon new lib libhello # libhello/hello/hello.{hpp,cpp,test.cpp} + tests/basics
canon new exe hello # hello/hello/hello.cpp
canon new exe hello --dir ~/src # create the project somewhere else
cd libhello
# Modules: related .hpp/.cpp/.test.cpp files
canon add module core # hello/core.hpp, core.cpp, core.test.cpp
canon add module details/utility # hello/details/utility.*, namespace hello::details
canon add module parser --no-test # no .test.cpp ...
canon add unit-test parser # ... until you want one
canon add module core --dry-run # any command: show the changes, write nothing
# Targets: more libraries or executables, each in its own source directory
canon add target exe hello-cli # hello-cli/hello-cli.cpp, links libhello
canon add module args --target hello-cli
canon add target lib libextra # extra/extra.{hpp,cpp,test.cpp}
# Functional tests
canon add test edge-cases # tests/edge-cases/driver.cpp, linked with libhello
canon add test greets --target hello-cli --arg Paul --expect "Hello, Paul!"
# Dependencies
canon add dep libextra # libhello links libextra
canon add dep fmt::fmt --target hello-cli # find_package(fmt) + link
canon add dep Boost::filesystem --package Boost # when the package name differs
# Checks
canon doctor # compare the files with .canon.toml and P1204R0
canon add module core --dry-run # any add command: show the changes, write nothing
```
`add` commands work from any directory inside the project. Every generated
`add` and `doctor` work from any directory inside the project. Every generated
project builds and passes its tests out of the box:
```bash
@@ -50,19 +69,44 @@ The block between `# >>> canon:managed >>>` and `# <<< canon:managed <<<` in
updates the manifest and re-renders the whole block, so:
- anything **outside** the block is yours and is never touched;
- anything you edit **inside** the block is replaced on the next `add`;
- anything you edit **inside** the block is replaced on the next `add`
(`canon doctor` warns when the block has been edited);
- `.canon.toml` is rewritten by canon, so comments you add to it are dropped.
canon reads `.canon.toml` strictly: unknown keys (usually typos) are errors
canon reads `.canon.toml` strictly: unknown keys (usually typos), invalid
names, targets sharing a source directory and links to executables are errors
with a line number.
### Targets and P1204R0
P1204R0 describes a project as one library or one executable with a source
directory named after it. `canon add target` applies the same rule to each extra
target: `libextra` lives in `extra/`, `hello-cli` in `hello-cli/`. A new
executable links the project's library automatically; a new library is linked
explicitly with `canon add dep`.
### doctor
`canon doctor` never changes anything. It reports:
- **errors** (exit status 1): files `.canon.toml` declares that are missing, and
a `CMakeLists.txt` without valid managed-block markers;
- **warnings**: `.cpp` files in a source directory that no target builds, unit
or functional tests that are not declared, a hand-edited managed block,
`.h`/`.cc`-style extensions, and `include/` or `src/` directories.
Hidden directories and top-level build output (`build*`, `cmake-build-*`) are
skipped.
| File | Role |
|---|---|
| `canon/canon.cpp` | `main`: wires the command line to the generators and executor |
| `canon/cli.*` | argument parsing |
| `canon/canon.cpp` | `main`: wires the command line to the generators, executor and doctor |
| `canon/cli.*` | argument parsing, including which options each command takes |
| `canon/name.*` | naming rules (`lib` prefix, stem, namespace, keywords) |
| `canon/generate.*` | the manifest and plan for `canon new`; project templates |
| `canon/add.*` | the plans for `canon add module` and `canon add unit-test` |
| `canon/generate.*` | the manifest and plan for `canon new` |
| `canon/add.*` | the plans for every `canon add` command |
| `canon/doctor.*` | the checks behind `canon doctor` |
| `canon/templates.hpp` | the contents of generated files |
| `canon/project.*` | finds and loads an existing project |
| `canon/manifest.*` | the manifest model, written to and read from TOML |
| `canon/toml.*` | the small TOML subset canon reads and writes |
@@ -75,7 +119,8 @@ with a line number.
- **Unit tests**: `canon/<unit>.test.cpp`, next to the code they cover.
- **Scenarios**: `tests/scenarios.cmake` runs command sequences (for example
`new lib` followed by several `add` commands) that both of the following use.
`new lib` followed by several `add` commands, including ones that must fail)
and ends each with `canon doctor`. Both of the following use them.
- **Golden tests**: `tests/golden/<scenario>/` holds the exact expected result of
each scenario. After an intentional template change, regenerate the copies and
review the diff:
@@ -87,7 +132,7 @@ with a line number.
1. ✅ `new lib`, `new exe`, plan/apply, `--dry-run`, manifest
2. ✅ `add module`, `add unit-test`, reading `.canon.toml`, managed-block updates
3. `add target`, `add test`, `add dep`, `doctor` (check a project against P1204R0)
3. ✅ `add target`, `add test`, `add dep`, `doctor`
4. User template overrides
## License