Template syntax
Updated 5 November 2026
A template is an ordinary configuration file. Its first line can be a control record: it starts with # Calculate and describes how to merge the content into the system. It contains three things: parameters (format=…, path=…), conditions (ac_install_merge==on) and substitutions inside values (#-variable-#).
# Calculate format=samba name=smb.conf path=/etc [parameters] [conditions && conditions]
You can wrap the template header over several lines: if the first line starts with # Calculate and ends with a single \, the next line is taken as its continuation. A continuation without # is applied, a continuation line that starts with # leads to Incorrect template, and two \ in a row break parsing.
The control record is not mandatory. Without a header the template is treated as a "raw" file: it overwrites the target completely (format=raw, the consequence being append=replace), and the path is taken from the current position - from the repository root, continuing the directories that come above in the template tree. A practical consequence: if you put a file without a header into a subdirectory, its name ends up in the path of the result, while the action variable must be in the header of the element at the first level - otherwise the subtree will be skipped without any message.
File header parameters
| parameter | value |
|---|---|
| format= | configuration file format: kde, samba, openrc, procmail, xml_xfce, json, patch, diff, kernel, dconf, world, contents, sqlite, backgrounds, plasma and others. Without it, the format is raw. List and differences → |
| comment= | comment character of the targeted file (#, ;, --); the special value xml/XML enables paired <!-- --> |
| append= | the append method: join, before, after, replace, remove, skip, patch, clear |
| path= | the directory where the configuration file will end up |
| name= | name of the configuration file (by default, the template file name) |
| link= | the file the template text is taken from (the template itself is not read); with symbolic, the target of the symlink being created |
| symbolic | create a symlink to the file from link= |
| mirror | merge only if the configuration file exists; if it is given by link= and there is no such file, the destination file is removed |
| force | delete the existing file before writing (enabled automatically with symbolic) |
| protected | do not delete the file when the package the template is bound to is removed |
| chmod= | access rights of the result |
| chown= | owner and group ( |
| autoupdate | after the merge, transfer the changed configuration file into the system without dispatch-conf |
| run= | the shell that runs the result immediately after the template is processed |
| exec= | the same, but the script is put in a queue after all regular templates |
| env= | variables module (install, desktop, update, …); if the module is unavailable, the template is omitted |
| module= | name of the templates module |
| merge= | at the end, configure the listed packages (sys-apps/openrc,sys-boot/grub) |
| postmerge= | the same, but after pkg_postinst() during the package build |
| rebuild= | lower the package version so that it is rebuilt during a system update |
| restart=, start=, stop= | restart/start/stop the comma-separated services |
| multiline, dotall | regular expression modes; read only by the patch format |
| stretch, convert, dconf | parameters of the backgrounds and dconf formats |
An invalid parameter in the header is an error: the utility reports incorrect header parameter: '<name>' and skips the template.
Directory header parameters
A template directory is described by a .calculate_directory file inside it. The set of parameters is different, and noticeably shorter:
append=, path=, name=, link= (copies the directory contents), chmod=, chown=, mirror, subvolume (create it as a btrfs subvolume), autoupdate, env=, module=, merge=, postmerge=, rebuild=, restart=, start=, stop=.
There is no format=, comment=, symbolic, protected, force, run=, exec=, multiline, dotall here - they are file parameters.
For a directory the allowed values are append=join|remove|skip|clear, and link= combines only with append=join or append=replace.
A directory without .calculate_directory is not an error: it is simply transferred into the system as it is. If you do not want that, create a .calculate_directory file in it with a single header line:
.calculate_directory
# Calculate append=skip
Otherwise an extra directory will appear in the system (you can check in advance with an offline merge).
Default values worth knowing
If the header has no append=, it is chosen by the format:
patchand the "executable" formats (diff,dconf,contents,sqlite,backgrounds) →patch; moreover, for them append= must bepatch: any other value the utility will reject with the messageWrong option 'append=<value>' in template <file>;- any other format →
join(the two files are merged together); raw,bin→replace(the file is written in full).
By default, name= is taken from the template file name, path= from the parent directory of the template or is inherited from .calculate_directory.
Variables and functions
A variable value is substituted as #-name-#, one from another module as #-module.name-#:
HOSTNAME="#-os_net_hostname-#" Modes "#-install.os_install_x11_resolution-#"
Functions are written the same way, but with argument brackets - they are mandatory even when there are no arguments: #-cut(1)-#, #-name()-#. A #-…-# construct without brackets is always a variable, not a function: the engine tells them apart by the presence of (). Condition functions are often needed:
pkg(cat/pkg),merge(cat/pkg),mergepkg(cat/pkg)- about an installed or being-configured package.mergepkg()means "this exact package is being configured" and "the package is installed": the version is taken from the /var/db/pkg database. If the list of packages being configured is empty,merge()returns true;exists(path[,root])- whether a file or directory exists; instead of1it returns the type (f,d,l, …), so the condition is usually written asexists(/etc/x)!=;ini(section.key)- reads a variable from the ini.env stack;grep(file, regular expression)- the first match from the file;cut(number[separator[string]])- a fragment of a string; by default the separator is-, and the string is taken from cl_pass_file - the name of the template file being processed (for a directory, its name). Fields are numbered from zero:cut(0,-,a-b-c)→a,cut(1,-,a-b-c)→b,cut(2,-,a-b-c)→c; a number past the end of the string gives an empty value, a negative one - a template error (cut(-1,-,a-b-c)stops the template witherror in template);in(variable,values…),groups(groups…),ls(directory,pattern),env(module.variable),case(type,variable),list(variable,index),sum(),push()/pop()(a stack between templates).
You can create and change function variables inside a template; their scope is the current template, and through push()/pop() a value is passed to another one.
Conditions in the header
Conditions come after the parameters on the same line. Operators: >, <, ==, !=, >=, <=, and for versions also <> (outside the range) and =>, <= (range boundaries, given comma separated).
- several conditions separated by a space are combined with "AND";
- explicit connectors:
&&("AND"),||("OR"), "AND" has priority; - an empty right side (
!=,==) means "the value is not empty" / "the value is empty".
# Calculate cl_ver>=3.5 os_linux_shortname==CLDX||os_linux_shortname==CLD
It reads as "version ≥ 3.5 and (CLDX or CLD)".
The left side of a condition can be a function call without framing: exists(/etc/f)==, ini(x.y)!=off, checkdev(wan0)!=.
Conditional blocks in the body
A block of text is substituted if the condition is true. It starts with #?condition# at the beginning of a line and ends with #name_of_the_first_condition_variable#:
#?os_locale_language==ru&&pkg(media-gfx/cldx-themes)!=# current_theme calculate_ru #os_locale_language#
Inside the block, as everywhere else, #-…-# substitutions are available; variables are calculated before functions, so you can pass a #-variable-# as a function argument.
Changing, adding and removing entries
The method depends on the format, but the basic set of name prefixes is:
key=value- set a value (add it if the key was not there);!key=- remove the key; works in all ini-like formats (samba,kde,openrc,procmail,postfix, …);+key=value- add a value to a shared list (samba,ldap);-key=valueformally means "replace", but there is no action behind it: the code does not set an action for the-prefix, so the behavior comes down to an ordinary write. Inkdethings are worse:+/-at key level are not recognized at all and stay inside the name, so+key=creates a parameter named+key, which will never matchkey. Inkde, remove only with!.
At section level, ! before a group name removes it, - replaces it entirely.
See also
- Configuration file formats - why the behavior of prefixes depends on the format.
- Template variables - env=, calculate.env, ini.env,
cl-variable. - Syntax on the site - an archived version with a longer list of functions.