Preprocessors¶
Repolish uses preprocessor directives in template files to define how content should be processed and replaced. These directives allow you to create flexible templates that can preserve local customizations while maintaining consistency.
Block Anchors¶
Block anchors allow you to define sections of content that can be replaced entirely or preserved with defaults.
Syntax¶
Usage¶
Block anchors are replaced with content from the context under the same key name. If no content is provided in the context, the default content between the markers is preserved.
Example:
# Template
## repolish-start[header]
# Default Header
Welcome to our project!
## repolish-end[header]
# Context
header: |
# Custom Header
Welcome to My Awesome Project!
Result:
Regex Replacements¶
Regex replacements allow you to preserve specific values from existing files using regular expressions.
Syntax¶
Usage¶
The regex pattern captures values from the target file, and the default value is used when no match is found.
Example:
# Template
## repolish-regex[version]: __version__ = "(.+?)"
__version__ = "0.0.0"
# Target file contains: __version__ = "1.2.3"
Result:
Multiregex Replacements¶
Multiregex replacements allow you to process entire configuration sections with multiple key-value pairs, extracting all values from a block and replacing template defaults.
Syntax¶
## repolish-multiregex-block[block-name]: block_pattern
## repolish-multiregex[block-name]: item_pattern
key1 = "default1"
key2 = "default2"
key3 = "default3"
Usage¶
The multiregex-block defines the pattern to extract the entire block from the
target file. The multiregex pattern defines how to extract individual
key-value pairs within that block.
Example:
# Template
[tools]
## repolish-multiregex-block[tools]: ^\[tools\](.*?)(?=\n\[|\Z)
## repolish-multiregex[tools]: ^(")?([^"=\s]+)(")?\s*=\s*"([^"]+)"$
uv = "0.0.0"
dprint = "0.0.0"
starship = "0.0.0"
# Target file contains:
[tools]
uv = "0.7.20"
dprint = "0.50.1"
starship = "1.0.0"
Result:
Processing Order¶
Preprocessors are applied in the following order:
- Block anchors - Replace entire sections
- Regex replacements - Replace individual values
- Multiregex replacements - Replace multiple values in blocks
Best Practices¶
- Use descriptive anchor names that clearly indicate their purpose
- Test your regex patterns thoroughly to ensure they match the expected content
- For complex configurations, consider using multiregex for entire sections
- Keep default values in templates to ensure files work without context
- Use the debugger to validate your preprocessor patterns
Practical examples¶
Dockerfile (block anchor)¶
Template (templates/my-template/repolish/Dockerfile):
FROM python:3.11-slim
## repolish-start[install]
# install system deps
RUN apt-get update && apt-get install -y build-essential libssl-dev
## repolish-end[install]
COPY pyproject.toml .
RUN pip install --no-cache-dir .
Local project Dockerfile (developer has custom install needs):
FROM python:3.11-slim
## repolish-start[install]
# custom build deps for project X
RUN apt-get update && apt-get install -y locales libpq-dev
## repolish-end[install]
COPY pyproject.toml .
RUN pip install --no-cache-dir .
When Repolish preprocesses the template, the install block from the local
project file is preserved in the staged template. The generated output keeps the
local custom RUN command while the rest of the Dockerfile comes from the
template.
pyproject.toml (regex anchor + block anchor)¶
Template (templates/my-template/repolish/pyproject.toml):
[tool.poetry]
name = "{{ cookiecutter.package_name }}"
version = "0.1.0"
## repolish-regex[keep]: ^version\s*=\s*".*"
description = "A short description"
## repolish-start[extra-deps]
# optional extra deps (preserved when present)
## repolish-end[extra-deps]
Local pyproject.toml (developer bumped version and added extras):
[tool.poetry]
name = "myproj"
version = "0.2.0"
description = "Local project description"
## repolish-start[extra-deps]
requests = "^2.30"
## repolish-end[extra-deps]
The repolish-regex[keep] anchor ensures the local version = "0.2.0" line is
preserved instead of being replaced by the template's 0.1.0. The extra-deps
block is preserved whole-cloth, letting projects keep local dependency
additions.
Tips
- Use meaningful anchor names (
install,readme,extra-deps) so reviewers immediately understand what the preserved section contains. - Regex anchors are applied line-by-line; prefer simple, easy-to-read patterns to avoid surprises.
- Anchors are processed before template rendering, so template substitutions still work around preserved sections.
Regex capture groups¶
Two important behaviors control what is extracted and inserted:
Capture group preference: If your regex includes a capturing group (parentheses), Repolish prefers the first capture group (group 1) as the block to insert into the template. If there are no capture groups, Repolish falls back to the entire match (group 0).
Safeguard trimming: As a conservative safeguard Repolish trims the captured block to a contiguous region based on indentation, so that incidental following sections are not accidentally pulled in. The canonical way to express intent is an explicit capture group — authors should prefer to capture exactly what they mean.
Example¶
Template excerpt:
cat1:
- line1
- line2
## repolish-regex[cat1-filter]: (^\s*# cat1-filter-additional-paths.*\n(?:\s+.*\n)*)
# cat1-filter-additional-paths
cat2:
- from-template
## repolish-regex[cat2-filter]: (^\s*# cat2-filter-additional-paths.*\n(?:\s+.*\n)*)
# cat2-filter-additional-paths
Local file excerpt:
Result after preprocessing:
cat1:
- line1
- line2
# cat1-filter-additional-paths
- extra
cat2:
- from-template
# cat2-filter-additional-paths
When your regex is too greedy, tighten it or add explicit parentheses around the intended capture so Repolish can reliably hydrate the template.
Anchor scope and uniqueness¶
Anchors can come from three places and are merged in this order:
- Provider templates: any
## repolish-start[...]/## repolish-regex[...]markers present inside provider template files. - Provider code: a provider's
create_anchors()callable can return an anchors mapping (key → replacement text) used during preprocessing. - Config-level anchors: the
anchorsmapping inrepolish.yamlapplies last and overrides earlier values.
Anchor keys are global identifiers — they must be unique across the entire merged template set. If two template files from different providers use the same anchor key, the later provider's value wins, which can produce surprising results.
Example conflict¶
Two providers accidentally use the same key init:
templates/a/Dockerfilecontains## repolish-start[init]…## repolish-end[init]templates/b/README.mdalso contains## repolish-start[init]…## repolish-end[init]
The init block from whichever provider is processed last will replace the
other. For predictable behavior, scope anchor keys to the file or provider: e.g.
docker-install or readme-intro.
Best practice: prefix anchor keys with the file or provider name when the content is file-scoped. This avoids accidental collisions when multiple providers contribute templates with similarly-named sections.