Skip to content
Tildev0.6.1

Configuration sections

This page shows how a plugin reads its own table of config.toml, and how a protocol plugin reads the settings of an account.

A section is a class with a public parameterless constructor and a property for each key, with its default value. The keys are the names of the properties in snake_case: MaxNames is max_names. I built this one against IConfigSection:

public sealed class HelloConfig : IConfigSection
{
public string Greeting { get; set; } = "Hello";
public int MaxNames { get; set; } = 3;
public void Validate()
{
if (MaxNames is < 1 or > 10)
{
throw new InvalidOperationException("max_names must be between 1 and 10.");
}
}
}

IConfigSection is optional. With it, Validate runs after the section is read and throws InvalidOperationException for a value that is missing or wrong. The message names the key, never the value, because the value may come from a command.

Ask IConfigService for the section by the name of its table, and put it in the container so the services of the plugin can ask for it. From PgpPlugin.cs:

var pgp = await config.AddSectionAsync<PgpConfig>("pgp", cancellationToken);
container.RegisterInstance(pgp);

The user then writes the table in config.toml:

[hello]
greeting = "Hi"
max_names = 5
  • It expands every string of that section first: ${NAME} from the environment and #{command} from what a command prints. A command in another section does not run.
  • A missing section, or a missing file, gives the defaults of the class. The file is never written.
  • Each name can be asked for once. A second plugin that asks for the same table gets The configuration section [<name>] is already in use.
  • Tilde reads accounts, logging, jobs, keys and ui itself, before any plugin. The plugins of the program read cache, pgp and scripting.

The OpenPGP plugin and the cache are off until enabled = true: RegisterAsync reads the section, and registers nothing more when it is off. A plugin that runs a program or writes files can do the same.

tcli and tt stop at the first error, with the section, the key and the line. tcli check goes on instead: each error is one line of its report, the section that is wrong keeps its defaults, and the other sections are still read. There, a key that the class does not have is an error too (is not a key of this section.), and so is a table that no plugin asked for.

When you add a section class, add it to the list Sections() of ConfigKeyScannerTests.cs. The test fails when a key that the serializer writes is called unknown by the check.

A protocol plugin does not have a section of its own. Its factory gets the part of the account it handles as a ProtocolSettings:

Property From config.toml
Protocol protocol, which picked the factory
Credentials username and password, or null when there is no username
Values the settings table, every value as a string

The settings table may hold strings, numbers and booleans. Tilde turns each one into its text with the invariant culture, so port = 993 is "993". Tilde does not check the keys of settings: the plugin reads the ones it knows. The mbox plugin reads one key, path. From MboxSettings.cs:

public static MboxSettings From(ProtocolSettings protocol)
{
ArgumentNullException.ThrowIfNull(protocol);
if (!protocol.Values.TryGetValue("path", out var path) || string.IsNullOrWhiteSpace(path))
{
throw new InvalidOperationException("The mbox account needs a 'path' setting.");
}
return new MboxSettings(System.IO.Path.TrimEndingDirectorySeparator(ExpandHome(path)));
}

And the account that uses it:

[[accounts.mail]]
id = "archive"
name = "Archive"
address = "ada@example.org"
[accounts.mail.store]
protocol = "mbox"
[accounts.mail.store.settings]
path = "~/Mail/archive"
[accounts.mail.transport]
protocol = "smtp"
username = "ada@example.org"
password = "#{pass show mail/work}"
[accounts.mail.transport.settings]
host = "smtp.example.org"

The strings of settings are expanded like the rest of accounts, so password = "#{pass show mail/work}" reaches the plugin as the password itself. Never write a value of Credentials or Values to the log.

Next: Commands, Lua modules and events.