How to create variables that contain variables?

Hi, folks -

I’m told by @mattstratton that this is the place for Hugo template help, and I’m a bit lost re: variables. I’ve read https://discuss.gohugo.io/t/string-variable-concatenation-to-build-path-for-partial/701/2 and “the-never-ending-confusion-over-variables/804/13” (can’t have more links), and I’m trying to create a variable name built off other variables. I think I misunderstand how I can use print (or printf) correctly to do this.

I’ve checked in a commit that shows my current (failed) attempt: https://github.com/devopsdays/devopsdays-web/pull/119/commits/584b1e0b46e8473b20b62b0aeddbaaec413e2978

I have a specific hardcoded name producing the expected outcome (it gives me the right bio per-speaker), and when I try to generalize it so any city/year can use this solution (hackish, yes, but it produces what I need!), I’m missing something. The error I get for each program page where I’m trying to use that is this:

template: theme/talk/single.html:44:25: executing "theme/talk/single.html" at <index $cityyearspeak...>: error calling index: cannot index slice/array with type string

At first I thought I was failing to successfully create a string, but upon re-reading the error, I am wondering why that would be the case (wasn’t the hard-coded name a string?)

When I try printf as opposed to print, I don’t get the result I expect and it also doesn’t work:

{{ $cityyearspeakers := printf ".Site.Data.events." ($e.city | lower) $e.year ".speakers" | printf "%s" }}

results in:

.Site.Data.events.%!(EXTRA string=minneapolis, string=2016, string=.speakers)

(and doesn’t actually work to create the page at all).

Guidance as to what I should read (or try!) would be appreciated.

Bridget

1 Like

So, regarding the use of printf, I would do this:

{{ $cityyearspeakers := printf ".Site.Data.events.%s%s.speakers" ($e.city | lower) $e.year }}

But you can’t get where you want to go using that string. index doesn’t work that way. :slightly_smiling:

Try something like this:

{{ if isset (index .Site.Data.events (printf "%s%s" ($e.city | lower) $e.year) .id) }}

What you’re trying to do is pass each step as a parameter like this:

{{ if isset (index .Site.Data.events "dallas2016" .id) }}

Hope that helps.

NOTE: edited to add the index func.

1 Like

OK, so that helps with the isset, but if we want to get at the data from the file, we can’t use index…how could we build it?

Doh! I left out the index part!! Should be:

if isset (index .Site.Data.events (printf "%s%s" ($e.city | lower) $e.year) .id) }}

I edited my previous comment to add the index in there.

Thanks, @moorereason! I’ve committed a change (https://github.com/devopsdays/devopsdays-web/pull/119/commits/8a97d797461f7473d671cebb425f76e857f488f2) which shows me trying your approach.

I made slight syntax changes from exactly what you had (which didn’t quite work) because I got an error from isset saying it says two arguments, and I removed the additional /speakers/ subdirectory that I don’t really need. (I was looking ahead to a future differentiation that we don’t need right now.)

Since at this point I don’t get any value for $s I suspect I’m still doing something wrong. The build errors say:

[…]
ERROR: 2016/04/12 Error while rendering page events/2016-minneapolis/program/sarah-goff-dupont.md: reflect: call of reflect.Value.Type on zero Value
ERROR: 2016/04/12 reflect: call of reflect.Value.Interface on zero Value in theme/partials/upcoming_headline.html
0 of 9 drafts rendered

I considered that maybe I need to add the “.” between .Site.Data.events and minneapolis2016, but I tried it a few ways and don’t seem to have the syntax right.

Ideas appreciated.

Bridget

I would have personally designed the data tree to be something like this:

data/events/2016/minneapolis/meta.yml
data/events/2016/minneapolis/talks/nicole-forsgren.yml

I tried a little of that, and it works. But you would have to refactor a lot of things, so I’ll deal with what you have. :slight_smile: I was testing with 2016 Minneapolis and Nicole Forsgren.

This works for me in talk/single.html:

      {{ range $fname, $s := index .Site.Data.events (printf "%s%s" (lower $e.city) $e.year) }}
        {{ if eq $fname ($.Title | urlize) }}
          <h2>Speaker</h2>
          <h3>{{ $s.name }}</h3>
          <a href="https://twitter.com/{{ $s.twitter }}"><img width="32px" height="32px" alt="twitter" title="twitter" src="/images/twitter.png">@{{ $s.twitter }}</a>
          <br><br>
          {{ $s.bio | markdownify }}
        {{ end }}
      {{ end }}

I also changed partials/past.html for fun:

{{ $now := dateFormat "2006-01-02" $.Now }}
{{ range $k, $meta := $.Site.Data.events }}
  {{ if lt $meta.startdate $now }}
    {{ $year := print $meta.year }}
    {{ $.Scratch.SetInMap "past_years" $year $year }}
  {{ end }}
{{ end }}

{{ range $pastYear := $.Scratch.GetSortedMapValues "past_years" }}
  <div style="display:table-cell; vertical-align:top">
    <div style="margin:2px;">
      <h2>{{ $pastYear }}</h2><br/>

      {{ range $k, $meta := $.Site.Data.events }}
        {{ $year := print $meta.year }}
        {{ if and (eq $year $pastYear) (lt $meta.startdate $now) }}
          {{ $city := (index $.Site.Data.events $k "city") }}
          {{ $friendly := (index $.Site.Data.events $k "friendly") }}
          <a href="/events/{{ $friendly }}/">{{ $city }}</a><br/>
        {{ end }}
      {{ end }}
    </div>
  </div>
{{ end }}

One quirk: We really need to add a timeify function to hugo. In past.html, you have to format $.Now as a string and compare strings, which means you have to format the strings exactly the same or it could fail. It would be nicer to be able to convert to and compare time types:

{{ if lt ($meta.startdate | timeify) $.Now }}

Hopefully that’s enough to see what’s needed.

Thank you! I’ll start with this guidance and see where it leads me, and report back for posterity. (This is a new enough project that we may be able to refactor without too much trouble.)

Bridget

As the person who designed the initial data tree (back when it was a much simpler config file, LOL), I am completely in favor of refactoring to your solution. It certainly will make things easier given the way that dozens of people will be contributing to their own configuration files, and will have much less YAML to trip them up in a small commit (like just adding a sponsor, etc).

This is beyond helpful. I have learned a ton from the Hugo community, and the amazing help that I get is why I keep using Hugo for all my projects and tell everyone to do the same :slight_smile:

1 Like

I refactored the URL path too: /events/2016/minneapolis/.

If you do that, this may help get you started:

<!-- talk/single.html -->
{{ $path := split $.Source.File.Path "/" }}
{{ $year := index $path 1 }}
{{ $city := index $path 2 }}
{{ $e := index $.Site.Data.events $year $city "meta" }}

Glad to help. :+1:

Oh, yeah, we can’t really change the URL path because of legacy links to the previous site content (they have to be YYYY-city). Trust me, I’d rather do it your way.

We could handle it with .htaccess, but we are gonna be hosted on GH pages which won’t allow for that.

So we started to get a little tricky here, and I want to move everything into a similar structure to what was suggested.

So if we imagine this directory: /data/speakers/2016/minneapolis, the query to get at the data files is:

range $fname, $s := index .Site.Data.speakers.2016.minneapolis

This works.

This also works: range $fname, $s := index .Site.Data.speakers "2016" "minneapolis"

However, when I tried to apply the printf approach, as such, it does NOT work:

range $fname, $s := index .Site.Data.speakers (printf "%s%s%s" $e.year " " (lower $e.city) )

Basically, it doesn’t return any errors, it just doesn’t find anything.

Related - the .Site.Data query doesn’t like it if any of the folders in the data tree have hyphens in them (although if we can do the subfolder thing, it is moot; just a bit of a red herring.

Is there something obvious I’m missing in how to get that space in there so there are two arguments after .Site.Data.speakers?

You have to pass each item/level separately. You’re passing a list of keys (or indexes in the case of a slice/array) that index will traverse. You want this:

{{ range $fname, $s := index .Site.Data.speakers $e.year (lower $e.city) }}

You can’t do this:

{{ range $fname, $s := index .Site.Data.speakers "2016.minneapolis" }}
{{ range $fname, $s := index .Site.Data.speakers "2016 minneapolis" }}

I gave that a try - it doesn’t return any errors, but no data comes back either.

So {{ range $fname, $s := index .Site.Data.speakers $e.year (lower $e.city) }} doesn’t seem to be hitting the file at /data/speakers/2016minneapolis

However, if I change it to {{ range $fname, $s := index .Site.Data.speakers "2016" "minneapolis"}} then the talks page loads the speaker data.

I know that I need to pass both “2016” and “minneapolis” as separate string values. The issue seems to be with the (lower $e.city) being inside there; this code works:

 {{ range $fname, $s := index .Site.Data.speakers $e.year $city }}```

It's less elegant, but functional, so I'll take it as a win :slightly_smiling:

Edited to add: This code works:

` {{ range $fname, $s := index .Site.Data.speakers $e.year (printf "%s" (lower $e.city)) }}`

That’s odd. lower should already return a string. Is this committed in your master branch? If so, which file? I want to see this with my own eyes.

The functioning code is commited to master now. The thing that didn’t work never left my laptop :slightly_smiling:

https://github.com/devopsdays/devopsdays-web/blob/master/themes%2Fdevopsdays-legacy%2Flayouts%2Ftalk%2Fsingle.html

Not seeing the problem. This works:

{{ range $fname, $s := index .Site.Data.speakers $e.year (lower $e.city) }}

Tried in Hugo 0.15 and HEAD on Linux amd64.

Aaaaand…it’s working for me now (I’m on OS X). I wonder if something was cached during all my back and forth. I was restarting the hugo server after making changes, but maybe I missed a restart of it at some point?