summaryrefslogtreecommitdiff
path: root/src/_posts/2020-07-15-config-3.0.md
diff options
context:
space:
mode:
Diffstat (limited to 'src/_posts/2020-07-15-config-3.0.md')
-rw-r--r--src/_posts/2020-07-15-config-3.0.md181
1 files changed, 181 insertions, 0 deletions
diff --git a/src/_posts/2020-07-15-config-3.0.md b/src/_posts/2020-07-15-config-3.0.md
new file mode 100644
index 0000000..9fb33c0
--- /dev/null
+++ b/src/_posts/2020-07-15-config-3.0.md
@@ -0,0 +1,181 @@
+---
+title: Config 3.0
+layout: post
+tags: Raku Programming
+social:
+ email: mailto:~tyil/public-inbox@lists.sr.ht&subject=Config 3.0
+ mastodon: https://soc.fglt.nl/notice/9x8QT2TxD2dSlEYse8
+description: >
+ I've made a reasonably sized change to Raku's Config module, resulting in a
+ major version bump. This article details my reasoning behind it, and shows
+ some examples on how I think I solved the issues at hand.
+---
+
+For those who don't know, the
+[`Config`](https://modules.raku.org/dist/Config:cpan:TYIL) module for the Raku
+programming language is a generic class to hold... well... configuration data.
+It supports
+[`Config::Parser`](https://modules.raku.org/search/?q=Config%3A%3AParser)
+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.
+
+{% highlight perl6 %}
+use Config;
+
+my $config = Config.new.read({
+ foo => "bar",
+ alpha => {
+ beta => "gamma",
+ },
+ version => 3,
+});
+{% endhighlight %}
+
+And after that, check for potential configuration file locations, and read any
+that exist.
+
+{% highlight perl6 %}
+$config.read($*HOME.add('config/project.toml').absolute);
+{% endhighlight %}
+
+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.
+
+{% highlight perl6 %}
+use Config;
+
+my $config = Config.new({
+ foo => Str,
+ alpha => {
+ beta => "gamma",
+ },
+ version => 3,
+}, :name<project>);
+{% endhighlight %}
+
+{% admonition_md note %}
+`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.
+{% endadmonition_md %}
+
+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](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html).
+It uses one of my other modules,
+[`IO::Path::XDG`](https://modules.raku.org/dist/IO::Path::XDG:cpan:TYIL), for
+this, together with
+[`IO::Glob`](https://modules.raku.org/dist/IO::Glob:cpan:HANENKAMP).
+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.
+
+{% highlight sh %}
+$PROJECT_FOO
+$PROJECT_ALPHA_BETA
+$PROJECT_VERSION
+{% endhighlight %}
+
+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`](https://modules.raku.org/dist/Log:cpan:TYIL) 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.
+
+{% admonition_md note %}
+A downside is that the application using `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.
+{% endadmonition_md %}
+
+## 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`.