Hugo

Recursive configuration merging

A little over a year ago, I opened this issue on GitHub requesting configuration file merging to be fully recursive. The issue has since gone stale, but because my plans for my theme, Midnight, depend on this feature existing, I’d like to restart discussion around this.

The Problem

To summarize the issue, if a theme/module configuration has the following set:

params:
    foo:
        bar: "default string"
        baz: 12 # default number

And a user has the following override in their site configuration:

params:
    foo:
        baz: 20

The user’s version of foo overwrites the theme’s version, leaving params.foo.bar undefined.

The Consequences

This leaves me as theme/module author with an unfortunate dilemma: either define all default values in a namespaced area, such as params.mymodule.foo.bar, or hard code them into the theme itself everywhere they would be needed. I would rather prefer to use the value at params.foo.bar and know that it is either my default value or the user’s overridden one.

This is particularly important for me as I am trying to set up Midnight with a plugin system based on Hugo modules, with the idea that all necessary plugin configuration is defined by the plugin module under params.plugins.pluginname, which allows me to iterate through all plugins under params.plugins and access the information I need. This leads to two issues, though, both of the same sort as above.

First is that modules are generally expected to namespace their configuration options under params.plugins.pluginname, but if a user overrides an option at params.plugins.pluginname.foo, it wipes out all of the necessary information Midnight needs to properly load the plugin.

Second is that, by this configuration layout and with how configs are currently loaded, Midnight only sees the configuration of the last module loaded, because it has overridden the previous contents of params.plugins and effectively deleted params.plugins.otherplugin.

Thus, I would like to be able to fully recursively merge configurations, even if it is not the default option.

Potential Issues and Responses

A potential issue that @bep brought up in the initial discussion on GitHub is essentially “what if I want to delete a key?”

I personally feel this is a non-issue, because anything given a default value by a module probably expects to either keep that default value or be given a new one; removing the key entirely would break it. If it can be undefined, that’s probably the default and will remain undefined if the user wants it to stay that way.

And if for some reason a module author really wants a key to be able to be deleted, they can declare a specific value to mean unset, whether it’s the type’s zero-value ("", 0, false, etc.) or something else ("unset"?)

Overall, I feel the gained benefit outweighs the potential troubles it may cause, especially if implemented as an opt-in option.

What Do You Think?

I recognize, though, that I am not the only theme developer for Hugo, nor am I anywhere near the most active (especially lately), so I’d like to get feedback both from Hugo developers and theme/module developers to see what you all think:

  • Would implementing recursive merging substantively affect your workflow as a theme/module developer?
  • Would implementing recursive merging be a huge undertaking as a Hugo developer? (From my understanding, it would mean replacing instances of one Viper function call with another that merges instead of replaces values)
  • Would implementing recursive merging (potentially) add value to you as a theme/module developer?
  • Do you think this feature is a good or bad idea (or indifferent)? Why?