Last item in a template range
Asked Answered
W

4

24

Given the template:

{{range $i, $e := .SomeField}}
        {{if $i}}, {{end}}
        $e.TheString
{{end}}

This can output:

one, two, three

If, however, I want to output:

one, two, and three

I'd need to know which is the last element in the range above.

I can set a variable that holds the length of the array .SomeField, but that will always be 3, and the $i value above will only ever get to 2. And you can't perform arithmetic in templates from what I've seen.

Is detecting the last value in a template range possible? Cheers.

Wigwam answered 13/3, 2014 at 1:26 Comment(0)
M
22

This is probably not the most elegant solution but it's the best I could find:

http://play.golang.org/p/MT91mLqk1s

package main

import (
    "os"
    "reflect"
    "text/template"
)

var fns = template.FuncMap{
    "last": func(x int, a interface{}) bool {
        return x == reflect.ValueOf(a).Len() - 1
    },
}


func main() {
    t := template.Must(template.New("abc").Funcs(fns).Parse(`{{range  $i, $e := .}}{{if $i}}, {{end}}{{if last $i $}}and {{end}}{{$e}}{{end}}.`))
    a := []string{"one", "two", "three"}
    t.Execute(os.Stdout, a)
}

Note: You can also do it without reflect using the len function (credit to Russ Cox): http://play.golang.org/p/V94BPN0uKD

c.f.

Mining answered 13/3, 2014 at 10:2 Comment(1)
Thanks, although I was looking for something more elegant, this does seem to be the way to do it currently. I hope the golang team fix this infelicity at some point. Thanks.Wigwam
W
4

We had same problem today when working with format in docker inspect command. The easiest way to get the last element without patching Docker was (the expression has been split into lines for ease of reading):

{{ $image := "" }}
{{ range split .ContainerImageName "/" }}
    {{ $image = . }}{{ end }}
{{ index (split $image ":") 0 }}

So in our case, we needed the image name without registry address and version. For example, image name like registry.domain.local/images/nginx:latest becomes nginx.

P.S: You need Go >= 1.11 to do the job (https://github.com/golang/go/issues/10608)

P.P.S: The question was about Go template but for those who had same problems with Docker here the configuration examples:

1) Using Go template in daemon.json

cat /etc/docker/daemon.json

{
    "log-driver": "syslog",
    "log-opts": {
        "syslog-address": "udp://127.0.0.1:20627",
        "tag": "{{ $image :=  \"\" }}{{ range split .ContainerImageName \"/\" }}{{ $image = . }}{{ end }}{{ index (split $image \":\") 0 }}/{{.Name}}"
}

2) Using Go template with -f option:

docker inspect \
  -f '{{ $image := "" }}{{ range split .Config.Image "/" }}{{ $image = . }}{{ end }}{{ index (split $image ":") 0 }}' \
  <container>
Windup answered 14/2, 2019 at 14:16 Comment(0)
U
2

I ran into this and solved it by adding an isLast helper function. It does require computing the length outside the helper, but it's a bit easier to read.

var helpers template.FuncMap = map[string]interface{}{
    "isLast": func(index int, len int) bool {
        return index+1 == len
    },
}

Which is used like:

{{$lenMyList := len .MyList}}
{{range $index, $item := .MyList}}
    <div>
        {{.SomeItemProp}}
        {{if (isLast $index $lenMyList)}}
            Last Item
        {{end}}
    </div>
{{end}}
Urissa answered 16/8, 2021 at 22:6 Comment(0)
C
1

Slightly more elegant solution, avoids calling len or sub/add on every iteration:

https://go.dev/play/p/oRznNPw-YCr

package main

import (
    "os"
    "text/template"
)

var fns = template.FuncMap{
    "eq": func(x, y interface{}) bool {
        return x == y
    },
    "sub": func(y, x int) int {
        return x - y
    },
}

func main() {
    t := template.Must(template.New("abc").Funcs(fns).Parse(`{{$last := (len . | sub 1)}}{{range  $i, $e := .}}{{if $i}}, {{end}}{{if eq $i $last}}and {{end}}{{$e}}{{end}}.`))
    a := []string{"one", "two", "three"}
    t.Execute(os.Stdout, a)
}
Christeenchristel answered 6/12, 2021 at 10:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.