Config 3.0
Raku Programming — Published on .
For those who don’t know, the
Config
module for the Raku
programming language is a generic class to hold… well… configuration data.
It supports
Config::Parser
modules to handle different configuration file formats, such as JSON
, YAML
and TOML
.
Up until now, the module didn’t do much for you other than provide an interface that’s generally the same, so you won’t need to learn differing methods to handle differing configuration file formats. It was my first Raku module, and as such, the code wasn’t the cleanest. I’ve written many new modules since then, and learned about a good number of (hopefully better) practices.
For version 3.0, I specifically wanted to remove effort from using the Config
module on the developer’s end. It should check default locations for
configuration files, so I don’t have to rewrite that code in every other module
all the time. Additionally, configuration using environment variables is quite
popular in the current day and age, especially for Dockerized applications. So,
I set out to make an automated way to read those too.
The Old Way
First, let’s take a look at how it used to work. Generally, I’d create the default configuration structure and values first.
use Config;
my $config = Config.new.read({
foo => "bar",
alpha => {
beta => "gamma",
},
version => 3,
});
And after that, check for potential configuration file locations, and read any that exist.
$config.read($*HOME.add('config/project.toml').absolute);
The .absolute
call was necessary because I wrote the initial Config
version
with the .read
method not supporting IO::Path
objects. A fix for this has
existed for a while, but wasn’t released, so couldn’t be relied on outside of
my general development machines.
If you wanted to add additional environment variable lookups, you’d have to check for those as well, and perhaps also cast them as well, since environment variables are all strings by default.
Version 3.0
So, how does the new version improve this? For starters, the .new
method of
Config
now takes a Hash
as positional argument, in order to create the
structure, and optionally types or default values of your configuration
object.
use Config;
my $config = Config.new({
foo => Str,
alpha => {
beta => "gamma",
},
version => 3,
}, :name<project>);
foo
has been made into the Str
type object, rather than a Str
value.
This was technically allowed in previous Config
versions, but it comes with
actual significance in 3.0.
Using .new
instead of .read
is a minor syntactic change, which saves 1 word
per program. This isn’t quite that big of a deal. However, the optional name
argument will enable the new automagic features. The name you give to .new
is
arbitrary, but will be used to deduce which directories to check, and which
environment variables to read.
Automatic Configuration File Handling
By setting name
to the value project
, Config
will consult the
configuration directories from the XDG Base Directory
Specification.
It uses one of my other modules,
IO::Path::XDG
, for
this, together with
IO::Glob
.
Specifically, it will check my $XDG_CONFIG_DIRS
and $XDG_CONFIG_HOME
(in
that order) for any files that match the globs project.*
or
project/config.*
.
If any files are found to match, they will be read as well, and the
configuration values contained therein, merged into $config
. It will load the
appropriate Config::Parser
implementation based on the file’s extension. I
intend to add a number of these to future Rakudo Star releases, to ensure most
default configuration file formats are supported out of the box.
Automatic Environment Variable Handling
After this step, it will try out some environment variables for configuration
values. Which variables are checked depends on the structure (and name
) of
the Config
object. The entire structure is squashed into a 1-dimensional list
of fields. Each level is replaced by an _
. Additionally, each variable name
is prefixed with the name
. Lastly, all the variable names are uppercased.
For the example Config
given above, this would result in the following
environment variables being checked.
$PROJECT_FOO
$PROJECT_ALPHA_BETA
$PROJECT_VERSION
If any are found, they’re also cast to the appropriate type. Thus,
$PROJECT_FOO
would be cast to a Str
, and so would $PROJECT_ALPHA_BETA
. In
this case that doesn’t do much, since they’re already strings. But
$PROJECT_VERSION
would be cast to an Int
, since it’s default value is also
of the Int
type. This should ensure that your variables are always in the
type you expected them to be originally, no matter the user’s configuration
choices.
Debugging
In addition to these new features, Config
now also makes use of my
Log
module. This module is
made around the idea that logging should be simple if module developers are to
use it, and the way logs are represented is up to the end-user. When running an
application in your local terminal, you may want more human-friendly logs,
whereas in production you may want JSON
formatted logs to make it fit better
into other tools.
You can tune the amount of logging performed using the $RAKU_LOG_LEVEL
environment variable, as per the Log
module’s interface. When set to 7
(for
“debug”), it will print the configuration files that are being merged into your
Config
and which environment veriables are being used as well.
Config
for its configuration must
also support Log
to actually make the new logging work. Luckily, this is
quite easy to set up, and there’s example code for this in Log
’s README.
Too Fancy For Me
It could very well be that you don’t want these features, and you want to stick
to the old ways as much as possible. No tricks, just plain and simple
configuration handling. This can be done by simply ommitting the name
argument to .new
. The new features depend on this name to be set, and won’t
do anything without it.
Alternatively, both the automatic configuration file handling and the
environment variable handling can be turned off individually using :!from-xdg
and :!from-env
arguments respectively.
In Conclusion
The new Config
module should result in cleaner code in modules using it, and
more convenience for the developer. If you find any bugs or have other ideas
for improving the module, feel free to send an email to
https://lists.sr.ht/~tyil/raku-devel
.