Cannot access theme params

I’m trying to add configurable sidebar widgets to my theme, where users can declare new widgets using partials and some configurations under Params, then specify what order they want those widgets to appear in. The theme comes with its own widgets, which are defined in the theme’s configuration file.

My code tries to access the widget’s configuration from the site’s Params and fall back to the theme’s configuration. Thing is, trying to directly access the theme’s configuration through $.Site.Params.bluestnight (theme name is BluestNight) returns nil. Am I doing something wrong, or is this a bug in Hugo?

As a side note, I think Hugo could/should implement a more granular combining of Params: I would expect the widget definitions in the theme config to be available through $.Site.Params.sidebar.* even though the site has defined $.Site.Params.sidebar.order. This issue came about when I tried to work around it.

Theme configuration:

[params.sidebar]
    [params.sidebar.patreon]
        path = "widgets/patreon.html"
        cache = true
    [params.sidebar.search]
        path = "widgets/search.html"
        cache = true
    [params.sidebar.recent]
        path = "widgets/recent.html"
    [params.sidebar.events]
        path = "widgets/events.html"
        cache = true
    [params.sidebar.categories]
        path = "widgets/categories.html"
        cache = true
    [params.sidebar.taglist]
        path = "widgets/taglist.html"
        cache = true

Site configuration:

[params.sidebar]
    order = [
        "search",
        "patreon",
        "categories",
        "recents"
    ]

Code:

{{/* $.Scratch.Get "sidebar" set to a comma-separated string in above code */}}
{{/* based on $.Site.Params.sidebar.order or previous method of enabling widgets */}}
{{- $widgets := (split (strings.TrimRight "," ($.Scratch.Get "sidebar")) ",") -}}

    {{- $sidebar := $.Site.Params.sidebar -}}                              
    {{- $bnsidebar := $.Site.Params.bluestnight.sidebar -}}
    
    {{- $scope := . -}}                                                    
    {{- range $name := $widgets -}}                                        
        {{- $sb := cond (or (eq nil $sidebar) (eq nil (index $sidebar $name))) $bnsidebar $sidebar -}}                                                
        {{- if eq nil $sb -}}
            {{- errorf "Sidebar widget definitions are nil: %#v" $sb -}}   
        {{- else if eq nil (index $sb $name) -}}                           
            {{- errorf "Could not find definition for widget: %s" $name -}}
        {{- end -}} 
            
        {{- $widget := index $sb $name -}}                                 
        {{- if $widget.cache -}}                                           
            {{- partialCached (string $widget.path) $scope -}}             
        {{- else -}}
            {{- partial (string $widget.path) $scope -}}
        {{- end -}}
    {{- end -}}

I’m not sure what is happening in your situation. But I have been thinking a little about this in relation to the “tjeme composition” PR. The namespace in this case is the theme name which is the folder the user happens to clone the theme into. I think we have to do better, but not sure how. That said.

If this theme sits below /themes/bluestnight then .Site.Params.bluestnight should work. But you really need to show a full project and not just some snippets.

As to your expectations: If you could write a specification of how you feel this should work, which describes the different kind of ambigouity, I will read it.

I think we have to do better, but not sure how. That said.

Can’t the theme’s name be pulled from the theme.toml file in the root of the theme’s directory, with a possible fallback to the directory name if the file doesn’t exist?

If this theme sits below /themes/bluestnight then .Site.Params.bluestnight should work.

The theme sits at /themes/BluestNight but neither .Site.Params.bluestnight nor .Site.Params.BluestNight work - I did all lowercase because I saw in the commit for the feature that the theme name was lowercased before applying the configuration.

But you really need to show a full project and not just some snippets.

I’m pretty sure I had included everything relevant to the issue at hand, but I’ve (temporarily) made the git repository for my site public on GitLab so you or someone else can view the code. The master branch contains the configuration changes that I would expect to work and has the theme included as a submodule. The relevant files, from the root of the website, are:

As to your expectations: If you could write a specification of how you feel this should work, which describes the different kind of ambigouity, I will read it.

How “official” of specifications are you looking for? I’ve never written any sort of official specification document before, but I suppose can put together a description of what I would be expecting.

After messing around with my code a bit more, I’m finding that it finds .Site.Params.BluestNight if I define it as such in the theme’s config.toml file. So instead of the configuration file looking like this:

[params]
[params.sidebar]
    [params.sidebar.patreon]
        path = "widgets/patreon.html"
        cache = true
    [params.sidebar.search]
        path = "widgets/search.html"
        cache = true
# ...

It works if it looks like this instead:

[params]
[params.BluestNight]
[params.BluestNight.sidebar]
    [params.BluestNight.sidebar.patreon]
        path = "widgets/patreon.html"
        cache = true
    [params.BluestNight.sidebar.search]
        path = "widgets/search.html"
        cache = true
# ...

It seems like Hugo is failing to namespace the configuration to .Site.Params.BluestNight and applies it more like a base configuration file, like calling

hugo --config config.toml,themes/BluestNight/config.toml
# or
hugo --config themes/BluestNight/config.toml,config.toml

… I’m not sure which takes precedence, because I don’t use that flag and the docs aren’t clear.

I also printed the full contents of .Site and found no namespacing of params in any way, which seems to confirm my theory that no namespacing is happening.

This was done with Hugo 0.40.3 downloaded from GitHub, as well.

Ran into this issue again while trying to create an overlay theme for “extra” features on top of my own base theme.

For reference, the theme has a plugin system of sorts, where CSS and JavaScript files for outside shortcodes, sidebar widgets, and other things can be included in the appropriate places at the appropriate times (i.e. when the thing is actually used on the page). I decided to define these plugins in the params.BluestNight namespace that I created, rather than the general user params one.

This leads to the base theme’s plugin definitions being overwritten and consequently a large number of errors. @bep mentioned writing a specification of what I would expect, however I’m not sure how detailed or technical that is expected to be.

Below is an example of site and theme configurations and how I would expect them to get combined.

Theme:

[params]
    param1 = "foo"
    param2 = "bar"
    param3 = "baz"
[params.nested]
    param4 = [1, 2, 4]
    param5 = "test"

Site:

[params]
    param1 = ""
    param2 = "lorem"
[params.nested]
    param4 = [3, 4, 5]
[params.other]
    param6 = true

Expected result:

[params]
    param1 = ""
    param2 = "lorem"
    param3 = "baz"
[params.nested]
    param4 = [3, 4, 5]
    param5 = "test"
[params.other]
    param6 = true
[params.theme]
    param1 = "foo"
    param2 = "bar"
    param3 = "baz"
[params.theme.nested]
    param4 = [1, 2, 4]
    param5 = "test"

Current result (extrapolated from current behavior, not tested)

[params]
    param1 = ""
    param2 = "lorem"
    param3 = "baz"
[params.nested]
    param4 = [3, 4, 5]
[params.other]
    param6 = true

Essentially, the behavior seems to indicate that the merging of values is happening on a surface level, rather than recursively. With the examples above, the current behavior completely overwrites any maps/objects/dicts/what have you with that found in the site, removing nested values defined in the theme’s config and not the site. Thus, params.nested.param5 completely disappears, even though the site config never defined it. I would expect the theme’s value to persist in that case.

When it comes to empty but defined values, such as the empty string, those should overwrite non-empty values from the theme, such as with params.param1. Slices/arrays should not be merged, but instead overwritten, like with params.nested.param4.

I have not yet tested if I can still access theme configuration items from the theme’s namespace in params, but the previous behavior seemed to be missing that piece.

What I’m not sure about are configurations written in JSON, which allows literal undefined values. If there is a way to determine if a parsed undefined value was specified versus simply missing, the specified value should be preferred while the missing value is ignored. If there is no way to tell the difference though, I suppose people would need to be warned that the literal “undefined” would be ignored? I imagine setting it to "" instead would cause the value to be “unset” without causing parsing issues.

Does this help specify my expectations for the composition behavior?

Regarding specifying multiple themes, the configurations should be overlaid as above, in order of least to most precedence, ending with the site configuration.

I’ve written a small library that I intend to use in my own projects for merging the contents of configuration files following what I described above. You can find the source code here, if that clears up my expectations of how Hugo would merge theme/site configurations.

@Shadow53 it seems that your merging tools should help me on "params" from config.toml in component seems not being merged, but your repo https://git.shadow53.com/Shadow53/merge-config seems down.

Is it available elsewhere ? Would be helpfull for me. Thanks.