Templates¶
tidydots includes a template engine based on Go's text/template that lets you generate machine-specific configurations from a single source file. Templates are processed during tidydots restore and support a 3-way merge system that preserves your manual edits across re-renders.
File Naming Convention¶
Template files use the .tmpl suffix. During restore, tidydots generates sibling files:
| File | Description |
|---|---|
config.toml.tmpl | Template source (you write this, committed to git) |
config.toml.tmpl.rendered | Rendered output (generated, gitignored) |
config.toml.tmpl.conflict | Conflict markers from merge (generated, gitignored) |
config.toml | Relative symlink pointing to config.toml.tmpl.rendered |
The symlink target on your system (e.g., ~/.config/alacritty/alacritty.toml) points into your backup directory, where alacritty.toml is itself a relative symlink to alacritty.toml.tmpl.rendered.
Template Context Variables¶
Templates have access to the following context struct:
| Variable | Type | Description | Example |
|---|---|---|---|
.OS | string | Operating system | "linux", "windows" |
.Distro | string | Linux distribution ID | "arch", "ubuntu", "fedora" |
.Hostname | string | Machine hostname | "desktop", "work-laptop" |
.User | string | Current username | "alice" |
.Env | map[string]string | All environment variables | See below |
Accessing Environment Variables¶
Use the index function to read environment variables:
The .Env map contains all process environment variables plus any platform-specific overrides.
Template Functions¶
tidydots uses sprout to provide a rich set of template functions. The following registries are available:
| Registry | Examples |
|---|---|
| std | default, empty, ternary, fail |
| strings | trim, upper, lower, replace, contains, hasPrefix, hasSuffix |
| numeric | add, sub, mul, div, mod, max, min |
| conversion | toString, toInt, toFloat64, toBool |
| maps | dict, get, set, hasKey, keys, values |
| slices | list, first, last, append, has, uniq |
| regexp | regexMatch, regexFind, regexReplaceAll |
For the full function reference, see the sprout documentation.
Template Examples¶
Conditional block based on OS:
Host-specific values:
{{ if eq .Hostname "desktop" }}
monitor_count = 3
dpi = 96
{{ else if eq .Hostname "laptop" }}
monitor_count = 1
dpi = 144
{{ else }}
monitor_count = 1
dpi = 96
{{ end }}
Using sprout functions:
Default values:
How Template Restore Works¶
When tidydots restore encounters a .tmpl file in a backup directory:
- Read the template source file (
config.toml.tmpl) - Render it using the template engine with the current platform context
- Merge the render output with any existing rendered file (see 3-Way Merge below)
- Write the result to
config.toml.tmpl.rendered - Create a relative symlink
config.tomlpointing toconfig.toml.tmpl.rendered - Store the pure render output in the SQLite state database (
.tidydots.db)
Non-template files in the same backup directory get normal symlinks as usual.
3-Way Merge¶
The 3-way merge system preserves manual edits you make to rendered files. It uses three inputs:
| Input | Source | Description |
|---|---|---|
| base | .tidydots.db (SQLite) | The previous pure render output stored in the database |
| theirs | .tmpl.rendered on disk | The current rendered file, which may contain your manual edits |
| ours | New template render | The freshly rendered template output |
Merge Logic¶
The merge follows these fast paths first:
- base == theirs: No user edits were made. Use the new render (
ours). - base == ours: Template did not change. Keep user edits (
theirs). - theirs == ours: Both arrive at the same result. Use the new render (
ours).
If none of the fast paths apply, the merge proceeds line-by-line:
- Only template changed (base line == their line, our line differs): Use the new template line.
- Only user changed (base line == our line, their line differs): Keep the user edit.
- Both changed the same way (their line == our line): Use either (they are identical).
- Both changed differently: This is a conflict.
Conflict Markers¶
When a conflict is detected, the merged output contains markers:
A separate .tmpl.conflict file is also written with the full merged content including conflict markers. The .tmpl.rendered file itself receives the merged content (including any conflict markers), so you can resolve conflicts by editing the rendered file directly.
Resolving Conflicts
Edit the .tmpl.rendered file to resolve conflicts, then remove the conflict markers. Your edits will be preserved on the next render through the 3-way merge. Alternatively, if you want to discard your edits entirely, use --force-render.
Skip Optimization¶
If the template source has not changed (detected via SHA-256 hash comparison against the database), and the rendered file already exists on disk, tidydots skips re-rendering entirely and just ensures the relative symlink is correct.
Force Render¶
The --force-render flag bypasses the 3-way merge and overwrites the rendered file with the new template output, discarding any user edits.
Warning
Using --force-render permanently discards any manual edits to .tmpl.rendered files. There is no undo.
SQLite State Database¶
tidydots stores template render history in a SQLite database at .tidydots.db in the root of your dotfiles repository (next to tidydots.yaml).
The database stores:
| Field | Description |
|---|---|
template_path | Relative path of the .tmpl file |
pure_render | The unmerged template output (used as base in future merges) |
template_hash | SHA-256 hash of the template source (for skip optimization) |
rendered_at | Timestamp of the render |
platform_os | OS at render time |
platform_host | Hostname at render time |
The database uses WAL mode for safe concurrent access and maintains a history of renders per template.
Recommended .gitignore¶
Add these patterns to the .gitignore in your dotfiles repository:
These files are machine-specific and should not be committed to your dotfiles repository.
Path Templating¶
Template expressions are also supported in targets and backup path fields in your tidydots.yaml. Any path containing {{ }} delimiters is rendered through the template engine before ~ and environment variable expansion.
Paths without {{ }} delimiters fall through to standard path expansion, maintaining full backward compatibility.
Path Template Examples¶
Host-specific target directory:
User-specific path:
Distro-specific path:
Real-World Example: Host-Specific Terminal Config¶
Here is a complete example showing how to use templates for a terminal emulator configuration that varies by machine.
tidydots.yaml:
version: 3
applications:
- name: "alacritty"
description: "GPU-accelerated terminal"
when: '{{ ne .OS "windows" }}'
entries:
- name: "alacritty-config"
backup: "./alacritty"
targets:
linux: "~/.config/alacritty"
package:
managers:
pacman: "alacritty"
apt: "alacritty"
brew: "alacritty"
./alacritty/alacritty.toml.tmpl:
[window]
{{ if eq .Hostname "desktop" }}
# Desktop: large monitor, no decorations
dimensions = { columns = 160, lines = 50 }
decorations = "None"
{{ else if eq .Hostname "laptop" }}
# Laptop: smaller screen, keep decorations
dimensions = { columns = 120, lines = 35 }
decorations = "Full"
{{ else }}
# Default
dimensions = { columns = 120, lines = 40 }
decorations = "Full"
{{ end }}
[font]
{{ if eq .Hostname "laptop" }}
size = 14.0
{{ else }}
size = 12.0
{{ end }}
normal = { family = "JetBrains Mono", style = "Regular" }
[env]
TERM = "xterm-256color"
After running tidydots restore on the desktop machine, the backup directory contains:
alacritty/
alacritty.toml.tmpl # Template source (committed)
alacritty.toml.tmpl.rendered # Rendered output (gitignored)
alacritty.toml # Symlink -> alacritty.toml.tmpl.rendered
And ~/.config/alacritty is a symlink to the backup directory, so your terminal reads the rendered configuration seamlessly.