canon add module <path> creates <stem>/<path>.hpp, .cpp and .test.cpp (--no-test skips the test), records the module in .canon.toml, adds the source to the project's target and re-renders the managed block of CMakeLists.txt. canon add unit-test adds the test later. Both work from any directory inside the project. - toml: a reader for the TOML subset canon writes, with line numbers in errors, so canon stays dependency-free - manifest: strict parsing (unknown keys, wrong types, bad references) - project: finds .canon.toml by walking up from the current directory - plan/executor: update_file refuses to write if the file changed after it was read, and the report skips directories that already exist - tests: shared scenarios drive both the golden and end-to-end tests; goldens now live in tests/golden/<scenario>/ Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
canon
Scaffolds C++ projects that follow P1204R0 (Canonical Project Structure), then keeps adding to them. Written in C++23, no third-party dependencies.
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 exe hello --dir ~/src # create the project somewhere else
cd libhello
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
add commands work from any directory inside the project. Every generated
project builds and passes its tests out of the box:
cmake -S . -B build
cmake --build build
ctest --test-dir build --output-on-failure
Build canon
cmake -S . -B build
cmake --build build
ctest --test-dir build --output-on-failure
cmake --install build --prefix ~/.local # puts canon in ~/.local/bin
How it works
Generators never touch the filesystem. They return a plan: a list of
make_directory, create_file and update_file operations. The executor
checks every operation before writing anything, so a conflict writes nothing and
--dry-run just skips the writing. canon never overwrites existing files, and
it refuses to update a file that changed after it was read.
Each project has a .canon.toml manifest recording what canon declared.
The block between # >>> canon:managed >>> and # <<< canon:managed <<< in
CMakeLists.txt is rendered entirely from that manifest. An add command
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; .canon.tomlis rewritten by canon, so comments you add to it are dropped.
canon reads .canon.toml strictly: unknown keys (usually typos) are errors
with a line number.
| File | Role |
|---|---|
canon/canon.cpp |
main: wires the command line to the generators and executor |
canon/cli.* |
argument parsing |
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/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 |
canon/cmake.* |
renders CMakeLists.txt and replaces its managed block |
canon/plan.* |
operations and plans (pure data) |
canon/executor.* |
checks and applies a plan |
canon/template.* |
{{key}} substitution |
Tests
- Unit tests:
canon/<unit>.test.cpp, next to the code they cover. - Scenarios:
tests/scenarios.cmakeruns command sequences (for examplenew libfollowed by severaladdcommands) that both of the following use. - 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:cmake -DCANON=build/canon -DWORK_DIR=build/tests/golden -DUPDATE=ON -P tests/golden/check.cmake - End to end:
tests/e2e/runs each scenario, then configures, builds and tests the result. Skip it withctest -LE e2e.
Roadmap
- ✅
new lib,new exe, plan/apply,--dry-run, manifest - ✅
add module,add unit-test, reading.canon.toml, managed-block updates add target,add test,add dep,doctor(check a project against P1204R0)- User template overrides
License
MIT, see LICENSE.