| OLD | NEW |
| (Empty) |
| 1 // Copyright 2016 The LUCI Authors. All rights reserved. | |
| 2 // Use of this source code is governed under the Apache License, Version 2.0 | |
| 3 // that can be found in the LICENSE file. | |
| 4 | |
| 5 package settings | |
| 6 | |
| 7 import ( | |
| 8 "bytes" | |
| 9 "fmt" | |
| 10 "html/template" | |
| 11 "strings" | |
| 12 "time" | |
| 13 | |
| 14 "github.com/luci/luci-go/milo/api/resp" | |
| 15 ) | |
| 16 | |
| 17 // A collection of useful templating functions | |
| 18 | |
| 19 // funcMap is what gets fed into the template bundle. | |
| 20 var funcMap = template.FuncMap{ | |
| 21 "humanDuration": humanDuration, | |
| 22 "parseRFC3339": parseRFC3339, | |
| 23 "linkify": linkify, | |
| 24 "linkifySet": linkifySet, | |
| 25 "obfuscateEmail": obfuscateEmail, | |
| 26 "localTime": localTime, | |
| 27 "shortHash": shortHash, | |
| 28 "startswith": strings.HasPrefix, | |
| 29 "sub": sub, | |
| 30 "consoleHeader": consoleHeader, | |
| 31 } | |
| 32 | |
| 33 // localTime returns a <span> element with t in human format | |
| 34 // that will be converted to local timezone in the browser. | |
| 35 // Recommended usage: {{ .Date | localTime "N/A" }} | |
| 36 func localTime(ifZero string, t time.Time) template.HTML { | |
| 37 if t.IsZero() { | |
| 38 return template.HTML(template.HTMLEscapeString(ifZero)) | |
| 39 } | |
| 40 milliseconds := t.UnixNano() / 1e6 | |
| 41 return template.HTML(fmt.Sprintf( | |
| 42 `<span class="local-time" data-timestamp="%d">%s</span>`, | |
| 43 milliseconds, | |
| 44 t.Format(time.RFC850))) | |
| 45 } | |
| 46 | |
| 47 func consoleHeader(brs []resp.BuilderRef) template.HTML { | |
| 48 // First, split things into nice rows and find the max depth. | |
| 49 cat := make([][]string, len(brs)) | |
| 50 depth := 0 | |
| 51 for i, b := range brs { | |
| 52 cat[i] = b.Category | |
| 53 if len(cat[i]) > depth { | |
| 54 depth = len(cat[i]) | |
| 55 } | |
| 56 } | |
| 57 | |
| 58 result := "" | |
| 59 for row := 0; row < depth; row++ { | |
| 60 result += "<tr><th></th>" | |
| 61 // "" is the first node, " " is an empty node. | |
| 62 current := "" | |
| 63 colspan := 0 | |
| 64 for _, br := range cat { | |
| 65 colspan++ | |
| 66 var s string | |
| 67 if row >= len(br) { | |
| 68 s = " " | |
| 69 } else { | |
| 70 s = br[row] | |
| 71 } | |
| 72 if s != current || current == " " { | |
| 73 if current != "" || current == " " { | |
| 74 result += fmt.Sprintf(`<th colspan="%d">
%s</th>`, colspan, current) | |
| 75 colspan = 0 | |
| 76 } | |
| 77 current = s | |
| 78 } | |
| 79 } | |
| 80 if colspan != 0 { | |
| 81 result += fmt.Sprintf(`<th colspan="%d">%s</th>`, colspa
n, current) | |
| 82 } | |
| 83 result += "</tr>" | |
| 84 } | |
| 85 | |
| 86 // Last row: The actual builder shortnames. | |
| 87 result += "<tr><th></th>" | |
| 88 for _, br := range brs { | |
| 89 result += fmt.Sprintf("<th>%s</th>", br.ShortName) | |
| 90 } | |
| 91 result += "</tr>" | |
| 92 return template.HTML(result) | |
| 93 } | |
| 94 | |
| 95 // humanDuration translates d into a human readable string of x units y units, | |
| 96 // where x and y could be in days, hours, minutes, or seconds, whichever is the | |
| 97 // largest. | |
| 98 func humanDuration(d time.Duration) string { | |
| 99 t := int64(d.Seconds()) | |
| 100 day := t / 86400 | |
| 101 hr := (t % 86400) / 3600 | |
| 102 | |
| 103 if day > 0 { | |
| 104 if hr != 0 { | |
| 105 return fmt.Sprintf("%d days %d hrs", day, hr) | |
| 106 } | |
| 107 return fmt.Sprintf("%d days", day) | |
| 108 } | |
| 109 | |
| 110 min := (t % 3600) / 60 | |
| 111 if hr > 0 { | |
| 112 if min != 0 { | |
| 113 return fmt.Sprintf("%d hrs %d mins", hr, min) | |
| 114 } | |
| 115 return fmt.Sprintf("%d hrs", hr) | |
| 116 } | |
| 117 | |
| 118 sec := t % 60 | |
| 119 if min > 0 { | |
| 120 if sec != 0 { | |
| 121 return fmt.Sprintf("%d mins %d secs", min, sec) | |
| 122 } | |
| 123 return fmt.Sprintf("%d mins", min) | |
| 124 } | |
| 125 | |
| 126 if sec != 0 { | |
| 127 return fmt.Sprintf("%d secs", sec) | |
| 128 } | |
| 129 | |
| 130 if d > time.Millisecond { | |
| 131 return fmt.Sprintf("%d ms", d/time.Millisecond) | |
| 132 } | |
| 133 | |
| 134 return "0" | |
| 135 } | |
| 136 | |
| 137 // obfuscateEmail converts an email@address.com into email<junk>@address.com | |
| 138 // if it is an email. | |
| 139 func obfuscateEmail(email string) template.HTML { | |
| 140 email = template.HTMLEscapeString(email) | |
| 141 return template.HTML(strings.Replace( | |
| 142 email, "@", "<span style=\"display:none\">ohnoyoudont</span>@",
-1)) | |
| 143 } | |
| 144 | |
| 145 // parseRFC3339 parses time represented as a RFC3339 or RFC3339Nano string. | |
| 146 // If cannot parse, returns zero time. | |
| 147 func parseRFC3339(s string) time.Time { | |
| 148 t, err := time.Parse(time.RFC3339, s) | |
| 149 if err == nil { | |
| 150 return t | |
| 151 } | |
| 152 t, err = time.Parse(time.RFC3339Nano, s) | |
| 153 if err == nil { | |
| 154 return t | |
| 155 } | |
| 156 return time.Time{} | |
| 157 } | |
| 158 | |
| 159 // linkifyTemplate is the template used in "linkify". Because the template, | |
| 160 // itself recursively invokes "linkify", we will initialize it in explicitly | |
| 161 // in "init()". | |
| 162 // | |
| 163 // linkifySetTemplate is the template used in "linkifySet". | |
| 164 var linkifyTemplate, linkifySetTemplate *template.Template | |
| 165 | |
| 166 // linkify turns a resp.LinkSet struct into a canonical link. | |
| 167 func linkify(link *resp.Link) template.HTML { | |
| 168 if link == nil { | |
| 169 return "" | |
| 170 } | |
| 171 buf := bytes.Buffer{} | |
| 172 if err := linkifyTemplate.Execute(&buf, link); err != nil { | |
| 173 panic(err) | |
| 174 } | |
| 175 return template.HTML(buf.Bytes()) | |
| 176 } | |
| 177 | |
| 178 // linkifySet turns a resp.Link struct into a canonical link. | |
| 179 func linkifySet(linkSet resp.LinkSet) template.HTML { | |
| 180 if len(linkSet) == 0 { | |
| 181 return "" | |
| 182 } | |
| 183 buf := bytes.Buffer{} | |
| 184 if err := linkifySetTemplate.Execute(&buf, linkSet); err != nil { | |
| 185 panic(err) | |
| 186 } | |
| 187 return template.HTML(buf.Bytes()) | |
| 188 } | |
| 189 | |
| 190 // sub subtracts one number from another, because apparently go templates aren't | |
| 191 // smart enough to do that. | |
| 192 func sub(a, b int) int { | |
| 193 return a - b | |
| 194 } | |
| 195 | |
| 196 // shortHash abbriviates a git hash into 6 characters. | |
| 197 func shortHash(s string) string { | |
| 198 if len(s) > 6 { | |
| 199 return s[0:6] | |
| 200 } | |
| 201 return s | |
| 202 } | |
| 203 | |
| 204 func init() { | |
| 205 linkifySetTemplate = template.Must( | |
| 206 template.New("linkifySet"). | |
| 207 Funcs(template.FuncMap{ | |
| 208 "linkify": linkify, | |
| 209 }).Parse( | |
| 210 `{{ range $i, $link := . }}` + | |
| 211 `{{ if gt $i 0 }} {{ end }}` + | |
| 212 `{{ $link | linkify}}` + | |
| 213 `{{ end }}`)) | |
| 214 | |
| 215 linkifyTemplate = template.Must( | |
| 216 template.New("linkify"). | |
| 217 Parse( | |
| 218 `<a href="{{.URL}}">` + | |
| 219 `{{if .Img}}<img src="{{.Img}}"{{if .Alt
}} alt="{{.Alt}}"{{end}}>` + | |
| 220 `{{else if .Alias}}[{{.Label}}]` + | |
| 221 `{{else}}{{.Label}}{{end}}` + | |
| 222 `</a>`)) | |
| 223 } | |
| OLD | NEW |