-1

My program gets data from a TOML file for an embedded struct named Data:

===
[custom]
Data = [                                                                      
  { filename = "fb.jpg", alt = "FB logo", url = "facebook.com" },              
  { filename = "pi.jpg", alt = "Pinterest logo", url = "pinterest.com"},      
  { filename = "twit.jpg", alt = "Twitter logo", url = "twitter.com"},      
]
===

It's declared as interface so I don't know the field names in advance. I want to loop through the rows and build an image in HTML. I can iterate like this successfully. This obviously prints out each column value at a time:

{{ with .FrontMatter.Custom.Data }}   
        {{ range $_, $value := . }}                                            
                {{ range $_, $t := $value}} {{$t}}, {{end}}
        {{ end }}
{{ end }}

But for the life of me I can't get access to the individual fields. I want to do this but it causes a runtime error:

            {{ range $_, $t := $value}} {{index $value "filename" }}, {{end}}

How can I get hold of $value.filename, $value.alt and so on?

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
tomcam
  • 4,625
  • 3
  • 19
  • 22
  • Have you tried $value.filename? – Burak Serdar Nov 10 '19 at 03:34
  • Yes. It returns an "executing 'appname' at <$value.Filename>: range can't iterate over fb.jpg'. The fieldnames aren't known at compile time. – tomcam Nov 10 '19 at 03:59
  • I realized that the key value is available, so I tried this but got a runtime error. I cannot seem to use the eq function with a variable name: for the inner loop (sorry, can't figure out how to format a comment; the instructions on the formatting page seem to be misleading because my br tag just showed up as a literal): `{{range $k, $t := $value}} {{if eq $k "filename" }} Filename: {{$t}} {{end}} {{end}}` – tomcam Nov 10 '19 at 04:57

1 Answers1

1

It's difficult to be sure of my answer because you have not provided details of the toml library you are using (I'll assume BurntSushi) or the data structure that this generates (including the output of fmt.Printf("%v", outVar), where outVar is the interface with your unmarshalled toml, would be useful).

However if my assumptions are correct then the issue seems to be case; in your toml file you have [custom] but in the template it is {{ with .FrontMatter.Custom.Data }} (note the c in custom is a different case). This does not really make sense as you say that iterating works but using your template and the data you provided it all seems OK (I'm guessing some detail is missing).

The following example code works as expected:

package main

import (
    "bytes"
    "fmt"
    "github.com/BurntSushi/toml"
    "html/template"
)

func main() {
    dataToml := `
    [custom]
Data = [                                                                      
  { filename = "fb.jpg", alt = "FB logo", url = "facebook.com" },              
  { filename = "pi.jpg", alt = "Pinterest logo", url = "pinterest.com"},      
  { filename = "twit.jpg", alt = "Twitter logo", url = "twitter.com"},      
]`

    var data interface{}
    if _, err := toml.Decode(dataToml, &data); err != nil {
        panic(err)
    }
    fmt.Printf("data: %v\n", data)

    tmpl := `{{ with .custom.Data }}   
        {{ range $_, $value := . }}         
        {{index $value "filename" }}                                  
        {{ end }}
{{ end }}`

    t := template.Must(template.New("tomlStuff").Parse(tmpl))

    var b bytes.Buffer
    err := t.ExecuteTemplate(&b, "tomlStuff", data)
    if err != nil {
        panic(err)
    }
    fmt.Printf("%v\n", b.String())
}

Its quite possible that I've missed something; if so perhaps you could modify my example in the go playground such that it duplicates your issue?

Brits
  • 14,829
  • 2
  • 18
  • 31
  • I can't thank you enough. It's a compound data structure, as shown here in the Playground: https://play.golang.org/p/BCzVYfPZVHU with minimal versions of the real data structures. And they do work, to a degree. For example, the output of `{{ .FrontMatter.Custom.Data }}` is indeed `[map[alt:FB logo filename:fb.jpg url:facebook.com] map[alt:Pinterest logo filename:pi.jpg url:pinterest.com] map[alt:Twitter logo filename:twit.jpg url:twitter.com]]` Likewise `{{ .Page }}` does show the contents of that data (sub) structure. The version I created doesn't segfault, but nor does it show data. – tomcam Nov 10 '19 at 06:57
  • OK - when you create the data struct it's taking a copy of FrontMatterConfigs. Calling toml.Decode is overwriting the original version; not the copy. https://play.golang.org/p/xdgG47yJaAG works OK. – Brits Nov 10 '19 at 07:38
  • Can't thank you enough. You have led me on the right path after a miserable couple of days. Here is a complete version that works on the playground. The exact same template causes a segfault on my machine in production. So it's clear that my code is suspect. https://play.golang.org/p/yoSBa89ffAe – tomcam Nov 11 '19 at 01:48
  • 1
    No worries - debugging templates can be difficult and I've spent ages looking for a complex solution in the past when the issue was something really simple. – Brits Nov 11 '19 at 03:46