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 (user: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:

  • patch and the "executable" formats (diff, dconf, contents, sqlite, backgrounds) → patch; moreover, for them append= must be patch: any other value the utility will reject with the message Wrong 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 of 1 it returns the type (f, d, l, …), so the condition is usually written as exists(/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 with error 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=value formally 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. In kde things 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 match key. In kde, remove only with !.

At section level, ! before a group name removes it, - replaces it entirely.

See also