Skip to content

Keep Blocks

Keep blocks are where the pattern-writing ends. The tail-capture regex kept reappearing everywhere — "everything past this point is mine" — and the realization followed: it would be better to just name the blocks that should not be touched. A keep block is exactly that: visible marker lines declaring a developer-owned region inside a provider-rendered file. The intention is self-documenting, and there is no regex to write.

The template ships a default between the markers for fresh projects. If your file already has the marker pair, what's between them survives every apply — the markers themselves stay in the output (visible contract), only the directive line is stripped.

Syntax

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

<!-- notes-start -->

Default content for new projects.

<!-- notes-end -->
  • start / end are literal marker strings — use whatever comment style fits the file (they are ordinary lines, and they remain in the output).
  • If your project file has no matching marker pair, the template default is kept.
  • Supports the |after-render phase — required when the blocks are generated by Jinja (see end-regex).

!!! warning "Placement" A keep-block directive governs the region(s) that follow it, up to the next keep directive line. Place each directive directly above the block(s) it manages — stacking several directive lines together at the top of the file leaves the earlier regions unmanaged (the first directive's section ends where the next directive begins). Supporting stacked directives is a possible non-breaking change for a later version.

Worked example

repolish/README.md.jinja:

# My Project

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

Default content for new projects.

<!-- custom-end -->

README.md — you already filled the block:

# My Project

<!-- custom-start -->

Our setup notes: run `make bootstrap` first.

<!-- custom-end -->

Save this as scratch.yaml:

template: |
  # My Project

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

  Default content for new projects.

  <!-- custom-end -->

target: |
  # My Project

  <!-- custom-start -->

  Our setup notes: run `make bootstrap` first.

  <!-- custom-end -->

and run:

repolish preview scratch.yaml

Your content survives; the provider can rewrite everything around it:

# My Project

<!-- custom-start -->

Our setup notes: run `make bootstrap` first.

<!-- custom-end -->

Repeated regions

One directive covers many blocks that share the same markers — repolish matches them in encounter order, first block to first block, second to second:

template
## 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 your README already has both marker pairs filled in (say install instructions under Installation, an example under Usage), each block is preserved in its own position — no notes-2 variant needed. Blocks without a matching pair in your file keep the template default.

(To see the pairing, extend the Try-it scratch file above with a second marked section.)

In Python files

Comment style is free-form, so keep blocks work in code too — e.g. a provider-managed registry whose lists only developers can fill. Note the interleaved placement: each directive sits directly above its own region (per the placement rule):

repolish/registry.py.jinja
"""Plugin registry — managed by repolish, lists owned by the project."""

# repolish:keep-block[plugins] start="# -- plugins-start" end="# -- plugins-end"

# -- plugins-start
PLUGINS = []
# -- plugins-end

# repolish:keep-block[envs] start="# -- envs-start" end="# -- envs-end"

# -- envs-start
ALLOWED_ENVIRONMENTS = ["development"]
# -- envs-end

Once developers add entries, both lists survive every apply; the provider can still change imports, docstrings, and methods around them.

end-regex

end-regex closes each kept region dynamically, at the first following line matching a pattern — the piece that made keep blocks usable inside Jinja loops, where a literal end marker is awkward and every repetition is identical:

template
## repolish:keep-block[provider-additional|after-render] start="# additional-paths" end-regex="^provider[0-9]+:$"
{% for idx in providers %}
provider{{ idx }}:
   - static{{ idx }}
   # additional-paths
   - default{{ idx }}
{% endfor %}

Two things do the work here:

  • |after-render — the blocks don't exist until Jinja renders the loop, so the directive waits for the second phase.
  • end-regex="^provider[0-9]+:$" — each region closes at the next loop item (or at the end of the directive's section), so N rendered blocks pair first-to-first with N blocks in your file.

If no match is found before the section ends, the region closes at the section boundary.

Trying end-regex in preview

repolish preview runs the pre-render phase only. To simulate after-render behavior, paste the rendered content into the scratch file as template and drop the |after-render suffix — the reconciliation machinery is identical.

When to use

Keep blocks when a region is yours, not just a value. For single lines or structured key/value sections, regex and multiregex are enough; for the inverse — a provider filling blocks inside a file you own — see Insertions.