Skip to content

Preprocessors

Before Jinja2 runs, repolish applies a preprocessing pass to every staged template. This pass handles four kinds of directives - all of which are stripped from the final output so your project files stay clean.

The most common directives are regex and multiregex: they live inside the template file itself and read values directly from your current project file. This makes templates self-contained - no separate config or provider code is needed to preserve local state. Block anchors and keep directives are simpler alternatives for cases where the provider (not the project file) decides what a section contains or which zone should be preserved.

Regex directives

A regex directive captures a value from your existing project file and injects it back into the template. This is how individual lines survive provider updates - versions you have already bumped, author fields, local config entries.

## repolish-regex[version]: ^__version__\s*=\s*"(.+?)"$
__version__ = "0.0.0"

Repolish runs the pattern against your current file. If a match is found, the captured group replaces the corresponding line in the template. If no match is found, the default template line is used unchanged.

The directive line itself is always removed from the output.

Capture group behavior

If your pattern includes a capturing group (parentheses), repolish uses the first capture group as the replacement value. With no capturing group the entire match is used. Prefer explicit groups when you want only part of the match:

## repolish-regex[version]: ^__version__\s*=\s*"(.+?)"$
__version__ = "0.0.0"
# captures just the version string, e.g. 1.2.3

As a conservative safeguard, repolish also trims the captured text to a contiguous region based on indentation. This prevents a greedy pattern from accidentally pulling in the following section. When a pattern is too broad, tighten it or add explicit parentheses to delimit exactly what should be kept.

Multiregex directives

For structured blocks (a [tools] section in a TOML file, a requirements list, etc.) multiregex directives let you merge additions from the provider while keeping versions you have already pinned locally.

[tools]
## repolish-multiregex-block[tools]: ^\[tools\](.*?)(?=\n\[|\Z)
## repolish-multiregex[tools]: ^(")?([^"=\s]+)(")?\s*=\s*"([^"]+)"$
uv = "0.0.0"
dprint = "0.0.0"

The block pattern locates the relevant section; the line pattern extracts key-value pairs. Your existing versions are preserved for matching keys; new provider keys are appended.

Block anchors

A block anchor marks a section in the template whose content is supplied by the provider's create_anchors() method (which can generate content dynamically from context, such as assembling install extras from a list) or by an anchors: mapping in repolish.yaml (project-level overrides win). All marker lines are stripped - the final project file is clean.

The tradeoff compared to regex directives: to customise the injected content you set it in repolish.yaml, because editing the file directly won't stick - the next apply overwrites it with whatever the provider computes.

.PHONY: install
install:
## repolish-start[install-extras]
    pip install -e ".[dev]"
## repolish-end[install-extras]

The provider supplies the replacement:

def create_anchors(self, context: Ctx) -> dict[str, str]:
    extras = ','.join(['dev', *context.extra_groups])
    return {'install-extras': f'\tpip install -e ".[{extras}]"'}

After preprocessing, the marker lines are gone and the injected content is in place - exactly what Jinja2 will render.

The marker comment style is flexible. Any prefix before repolish-start[name] is accepted, so you can use the comment syntax that fits the file type:

# repolish-start[block]   ← Python / TOML / YAML
// repolish-start[block]   JavaScript / CSS
<!-- repolish-start[block] -->    HTML / Markdown
/* repolish-start[block] */       CSS / C

If no replacement is provided for a key, the default content between the markers is kept (the markers themselves are still stripped).

Keep directives

Keep directives preserve developer-owned content inside provider-managed files without forcing you to handwrite multiline regex patterns. Use them when you want to keep a visible region in place if the project file already has one, while still shipping a sensible template default for fresh projects.

Keep a bounded region

Use repolish-keep-block when the developer-owned content sits between two explicit markers.

## repolish-keep-block[readme-custom-block]: start="<!-- start -->" end="<!-- end -->"

<!-- start -->

Default block content

<!-- end -->

If the current project file already has a matching marker pair, repolish keeps that content. Otherwise the template default remains.

When several sibling keep-block directives use the same start/end markers in one file, repolish matches them in encounter order and restores local blocks in that same order.

One directive is enough — no need to give each block a different name:

## repolish-keep-block[notes]: start="<!-- notes-start -->" end="<!-- notes-end -->"

## Installation

<!-- notes-start -->

_No notes yet._

<!-- notes-end -->

## Usage

<!-- notes-start -->

_No notes yet._

<!-- notes-end -->

If the project file already has both marker pairs with developer content:

## Installation

<!-- notes-start -->

Run `pip install mylib` with Python 3.11+.

<!-- notes-end -->

## Usage

<!-- notes-start -->

Import and call `mylib.run()` after configuring credentials.

<!-- notes-end -->

The output preserves both blocks in place — first block matched to first, second to second, and so on:

## Installation

<!-- notes-start -->

Run `pip install mylib` with Python 3.11+.

<!-- notes-end -->

## Usage

<!-- notes-start -->

Import and call `mylib.run()` after configuring credentials.

<!-- notes-end -->

Keep everything from a marker to EOF

Use repolish-keep-rest when a marker introduces a developer-owned tail.

## repolish-keep-rest[repo-overrides]: marker="## repo-overrides"
## repo-overrides
# Placeholder

Everything from the marker line to EOF is preserved from the project file when present.

Keep the header up to a marker

Use repolish-keep-header when the developer owns the top of the file and the provider owns the section below the marker.

repolish-keep-header must appear at the start of the template file. If placed later in the file, repolish ignores the directive to avoid duplicating content that may already have been emitted before the directive line.

## repolish-keep-header[repo-header]: marker="## managed-start"
Intro text the developer can edit
## managed-start
Provider-managed content below

The header is preserved from the project file, while the provider-managed tail continues to come from the template.

Processing order

  1. Block anchors are applied first (replacement from provider code / config).
  2. Keep directives are applied next (copy developer-owned regions from the current project file when present).
  3. Regex directives are applied next (capture from the current project file).
  4. Multiregex directives are applied last.

All directive lines are stripped before Jinja2 sees the file.

Trying it out

Use repolish preview with a YAML debug file to experiment without touching your project. Create a file called anchor_example.yaml:

template: |
  __version__ = "0.0.0"
  ## repolish-regex[version]: ^__version__\s*=\s*"(.+?)"$

target: |
  __version__ = "1.3.7"

Then run:

repolish preview anchor_example.yaml

Directive naming and uniqueness

Directive names are global identifiers across all templates in a run. Two templates from different providers can each have a ## repolish-start[init] block, but the replacement value for init is a single string - the later provider's value wins and the earlier one is silently discarded.

To avoid this, scope names to the file or provider:

docker-init       ← instead of just "init"
readme-badges     ← instead of "badges"
mylib-version     ← instead of "version"

The same rule applies to regex and multiregex directive names. A regex named version in one template will silently conflict with a version directive in another template that is processed later.

Block anchor replacements come from three places, merged in this order:

  1. Provider code - create_anchors() return value.
  2. Config-level anchors - the anchors: mapping in repolish.yaml (wins over provider code).

Regex and multiregex directives only read from the current project file; they are not affected by repolish.yaml anchors.