Skip to content

Templating and Caching

SuperCuber edited this page May 11, 2023 · 8 revisions

Templating

Dotter uses Handlebars as its rendering engine. I recommend reading through that link to learn the full capabilities of the engine, but the most important feature is that it will substitute {{variable_name}} with the variable's value.

So, if your configuration had the variables name = "Jack" and surname = "Black", rendering the file

Hello, {{name}} {{surname}}!

will result in

Hello, Jack Black!

This is useful for the same reason constants in code are - when you have repeating values many times in the same file, or have the same value repeated in many files, it's useful to have it declared once then templated into everywhere it's used. Then, if you change it in the declaration, it will automatically change everywhere its referenced.

Some examples could be: colors in your colorscheme can be automatically shared between all applications, font sizes can be kept consistent accross applications, etc.

Handlebars is more than simple find-and-replace though - it has many other features, like if blocks.
For example, if age = 8 then:

Jonny can{{#if (lt age 18)}}not{{/if}} drink alcohol

Will render as:

Jonny cannot drink alcohol

An important note - Dotter will detect whether your source file is a template or not.
If it is, it will copy the source to the target. If it is not, it will create a symbolic link. More on that in the Templated vs Non-Templated section.

Helpers

Handlebars supports custom helpers - for example, a helper that will convert a color from hex to rgb named hex2rgb will be used like so: {{hex2rgb background_color}}.
The Rust implementation of Handlebars supports custom helpers written in rhai which is a scripting language.

To define one yourself, add it in the optional [helpers] section in global.toml in the form of script_name = "script_file_location".
The ones I currently use will be in the helpers folder of my dotfiles - you can use them as examples.

Additionally, there's helpers implemented in rust.
The ones that currently exist:

  • math - used like {{math font_size "*" 2}}.
    Executed by meval-rs.
  • include_template - used to include the contents of another file. If the file is a template, it is also rendered.
  • is_executable - used to check whether a program is able to be executed.
    Use with an if statement: {{#if (is_executable "cargo")}}
  • command_success - runs the command and checks if it exited successfully.
    Use with an if statement: {{#if (command_success "test 5 = 5")}}
  • command_output - runs the command and inserts the output into the template.
    Use like so: {{command_output "cargo --help"}}
  • All the helpers from handlebars_misc_helpers

Templated vs Non-Templated

As mentioned previously, Dotter differentiates between template source files and non-template source files.

  • Template files Files which have {{ in them are automatically detected as templates. This can be overridden.
    The file's content in the source location will obviously differ from the desired contents in the target location.
    This means that they have to be copied to the target location while rendering the template.
  • Non-template files - Files which do not use the templating functionality.
    The file's content in the source location will be the same as the target location, which means they can be symbolically linked.
    This has the advantage that by modifying the target location, you're automatically syncing the changes to the source location in your repository.

Caching

Dotter uses a cache to detect differences between the current configuration and the state on disk.
This means it will handle well situations like:

  • Removing files and packages from configuration
  • Accidentally editing the target location of a templated file
  • Changing a templated file to non-templated and vice versa

By default, the cache state is stored in .dotter/cache.toml and .dotter/cache/

Clone this wiki locally