Insertions¶
Insertions grew out of validators. Validators came first: in some cases developers weren't ready to let repolish touch a file, but they still wanted its value — so they handed over a function that inspects the file instead ("make sure I did this"). Once that existed, the follow-up was obvious: the same kind of function can write instead of check. "I like my file, I'll maintain it — but here is a space you can fill."
That is the insertion block: a marked region in your file whose body a provider function regenerates on every apply. It's where docs-in-sync-with-code came from — instead of manually copying a class's fields or a CLI's options into a README, a function reads the code directly and writes the section. Add a field, run repolish, the docs follow.
The block¶
An on/off pair reserves the region between them:
config— the tag (optional visual aid for matching pairs).config-options— the function the provider registered for this file.- Anything after the function name is passed to it as arguments.
The body between the markers is always regenerated by the function on each
apply — put nothing there yourself. Use whatever comment syntax the file type
supports (#, //, <!-- -->, …); the tag and colon are optional. The exact
syntax — comment styles, argument quoting, provider-qualified function names —
is on the Grammar page.
Worked example¶
Keeping a README's configuration section in sync with the options model — you own the README, the provider fills one block. Note this example uses the your file flow; a template can ship the same markers too (see Template-shipped blocks).
demo_provider/repolish.py — registers the function for README.md:
from pydantic import BaseModel, Field
from repolish import BaseContext, BaseInputs, Provider
class Options(BaseModel):
"""CLI options for the demo tool."""
line_length: int = Field(88, description='Max line length for formatters')
target_python: str = Field('3.12', description='Python target version')
strict: bool = Field(True, description='Fail on warnings')
class Ctx(BaseContext):
pass
class Demo(Provider[Ctx, BaseInputs]):
def create_context(self):
return Ctx()
def create_file_insertions(self, context):
def config_options(brief: str = '') -> str:
if brief == 'yes':
return '\n'.join(f'- `{name}`' for name in Options.model_fields)
return '\n'.join(
f'- `{name}` — {f.description} (default: `{f.default}`)'
for name, f in Options.model_fields.items()
)
return {'README.md': {'config-options': config_options}}
README.md — you write the block once:
repolish preview does not simulate insertions
(it runs the pre-render phase only), so this sandbox is a scratch project:
repolish.yaml:
Create demo_provider/repolish.py and README.md from the other tabs,
then:
The block is filled from the model — and stays in sync as fields are added:
Template-shipped blocks¶
The same block works inside a provider-rendered file: the template emits the
on/off markers and registers the function for that same path. The markers
are not directive syntax, so they pass through both directive
phases and Jinja rendering untouched, and the insertion phase fills
the body after the file lands on disk.
## Status
<!-- repolish:on:status render-status ready -->
<!-- repolish:off:status -->
One mechanism, two flows: you ship the block in a file you own, or the template ships it in a file the provider renders.
Editing the block¶
The marker line is yours to edit, even in a template-shipped block — change the function or its arguments, and the next apply adopts your marker (matched by tag and occurrence order) while still regenerating the body.
In the worked example, change the marker to config-options yes and re-apply:
<!-- repolish:on:config config-options yes -->
- `line_length`
- `target_python`
- `strict`
<!-- repolish:off:config -->
Adopted markers make the output stable: repolish apply --check reports no
drift afterward.
!!! note "What adoption doesn't do" Only the marker line is adopted. The body is always the function's output — hand-edits inside the block are replaced on the next apply. If you want a region the provider must not touch at all, that's a keep block — which needs a template, since only templates reconcile regions.
Timing¶
Insertions run after files are written to disk and before post-processing
(formatters see the final content) — see the
full pipeline. Insertion content is covered by
check mode: stale blocks fail repolish apply --check like template drift.
For the provider side — function signatures, keyword-only context injection,
disabling or extending insertions via overrides — see the
provider insertions guide.