Paste blocks: injecting custom code into generated files
Table of Contents
Summary
Codegen overwrites every generated file on each run. Paste blocks are
the mechanism for injecting hand-written C++ into those files in a way
that survives regeneration. Each injection point in a template is
identified by a UUID marker (<<paste:UUID>>); any Babel block in the
entity org model that carries a matching :implements UUID header
argument contributes its body to that marker. The pattern covers
declarations (.hpp), implementations (.cpp), and extra .cpp
includes.
Detail
How it works
During code generation core.py collects all Babel blocks in the entity
org model that carry an :implements <UUID> header argument. After
rendering the Mustache template it replaces each <<paste:UUID>> marker
with the concatenated bodies of all matching blocks.
The Babel block :name tag is for human navigation only. The :implements
tag is what codegen acts on.
Repository injection points
The following paste UUIDs are defined for the repository profile. Their canonical IDs live in org entity meta model.
| UUID | Where injected | Template |
|---|---|---|
DCA78C69-E508-48D9-9972-A9B8094D91FB |
Inside the repository class, before the closing } |
ores.cpp.repository.repository_header |
F2EB1914-5E94-42CA-9C77-46BDB364BF9E |
Near the end of _repository.cpp, before } of namespace |
ores.cpp.repository.repository_impl |
6141050C-0ED4-4680-B387-7DDDA3A69806 |
Top of _repository.cpp, after the generated includes |
ores.cpp.repository.repository_impl |
Qt detail dialog injection points
The following paste UUIDs are defined for the Qt detail dialog profile,
for entity-specific dialog logic that doesn't fit the generic
is_locked_after_create=/=locked_fields-style declarative flags (e.g. a
derived field computed live from two others, or a field whose
availability depends on another field's value at runtime). Their
canonical IDs live in org entity meta model.
| UUID | Where injected | Template |
|---|---|---|
3F8B6C1D-4E2A-4F9B-8C3D-1A5E7F2B9D4C |
Top of _DetailDialog.cpp, after the generated includes |
ores.cpp.qt.detail_dialog_impl |
F55CB64E-165A-4E15-A50A-5723C0320E97 |
Inside the class, in private:, after the generated declarations |
ores.cpp.qt.detail_dialog_header |
EC97923E-BC8D-4A50-9970-CBC5CBD4B732 |
Near the end of _DetailDialog.cpp, after the generated methods |
ores.cpp.qt.detail_dialog_impl |
B6F2748A-9896-4B9C-BEAC-38436FD1CA79 |
End of setupConnections(), before its closing } |
ores.cpp.qt.detail_dialog_impl |
9A719585-3AB1-47DE-B0B0-99AFDFE7180E |
End of setCreateMode(), after the generated locked_fields loop |
ores.cpp.qt.detail_dialog_impl |
7A4AE8D5-A22D-4912-B4BF-D58215681CD2 |
End of setReadOnly(), after the generated is_locked_after_create loop |
ores.cpp.qt.detail_dialog_impl |
69F22F14-6701-4E73-AFCC-78AC1B1FDDDF |
Inside validateInput()'s return chain, before the closing ; |
ores.cpp.qt.detail_dialog_impl |
4E83FA7A-742A-4102-90AF-D337F6FB1269 |
Inside onSaveClicked(), after validateInput() passes, before the save dispatch |
ores.cpp.qt.detail_dialog_impl |
F8EC96B9-04BA-46A1-A2D8-4E4B91DCB435 |
Inside onSaveClicked()'s async watcher, right after the entity save succeeds |
ores.cpp.qt.detail_dialog_impl |
67D24D2F-2D98-49EB-9A1D-32F1D8BFA76A |
In signals:, after the generated signals |
ores.cpp.qt.mdi_window_header |
18B9ED63-78C2-4259-93AC-CCDFBC88EFBD |
End of setupToolbar(), after the generated History action |
ores.cpp.qt.mdi_window_impl |
66E078FB-D6A6-4DF6-814B-65995D948090 |
In signals:, after the generated signals |
ores.cpp.qt.controller_header |
52E70F3B-5903-4359-91FB-B533054A18AC |
End of showListWindow(), after the generated signal connections |
ores.cpp.qt.controller_impl |
EC19A4EE-363D-4D41-A33A-D5F85A139C80 |
In the dialog's signals:, after the generated signals |
ores.cpp.qt.detail_dialog_header |
DF72DFCF-FD65-4FE5-98BA-CB8168CE0FDF |
End of wireDetailDialogCommon(), after the generated connections |
ores.cpp.qt.controller_impl |
The create-mode and read-only seams run last, after the generated
per-field lock logic — a paste block there can override what the
generated code just did (e.g. replace a setEnabled(false)-based lock
with WidgetUtils::set_combo_locked() for a field that should stay
visually normal, flag icon included, while locked).
The mdi_window/controller header+impl pairs follow the same shape as the repository profile's declaration/implementation pairing above: an entity declares a signal via the header seam, then wires it via the matching impl seam — e.g. a cross-navigation button on one entity's list window that opens a related entity's list window.
Recipe: adding a custom repository method
Given an entity org model, add a heading under ** Repository with a
unique :ID:, then attach three Babel blocks:
#+begin_src org
read_latest_all_tenants
Reads the latest records across all tenants (no tenant_id filter).
std::vector<domain::feed_binding> read_latest_all_tenants(context ctx);
#include <string>
std::vector<domain::feed_binding> feed_binding_repository::read_latest_all_tenants(context ctx) { ... }
#+end_src
After editing the org model run:
./compass.sh codegen entity generate <entity>
The generated .hpp will contain the declaration and the generated
.cpp will contain the implementation — both survive all future regen
runs.
Considerations
- The
impl_includesblock can be omitted when no extra includes are needed; codegen silently skips absent markers. - Multiple paste blocks with the same
:implementsUUID are concatenated in document order. read_latest_all_tenantsis a good candidate for promotion to a generated optional controlled by a flag in the entity org model (e.g.#+has_cross_tenant_read: true), since the pattern is identical across entities. Tracked as a future codegen improvement.
See also
- ores.codegen architecture — module list, data files, generation flow.
- org entity meta model — defines the paste UUIDs and their semantics.
- ores.cpp.repository.repository_header — template that hosts the declaration paste point.
- ores.cpp.repository.repository_impl — template that hosts the impl and includes paste points.