| // Copyright 2018 syzkaller project authors. All rights reserved. |
| // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. |
| |
| package html |
| |
| import ( |
| "fmt" |
| "html/template" |
| "net/url" |
| "reflect" |
| "strings" |
| texttemplate "text/template" |
| "time" |
| |
| "github.com/google/syzkaller/dashboard/dashapi" |
| "github.com/google/syzkaller/pkg/vcs" |
| ) |
| |
| func CreateGlob(glob string) *template.Template { |
| return template.Must(template.New("").Funcs(Funcs).ParseGlob(glob)) |
| } |
| |
| func CreateTextGlob(glob string) *texttemplate.Template { |
| return texttemplate.Must(texttemplate.New("").Funcs(Funcs).ParseGlob(glob)) |
| } |
| |
| var Funcs = template.FuncMap{ |
| "link": link, |
| "optlink": optlink, |
| "formatTime": FormatTime, |
| "formatDate": FormatDate, |
| "formatKernelTime": formatKernelTime, |
| "formatJSTime": formatJSTime, |
| "formatClock": formatClock, |
| "formatDuration": formatDuration, |
| "formatLateness": formatLateness, |
| "formatReproLevel": formatReproLevel, |
| "formatStat": formatStat, |
| "formatShortHash": formatShortHash, |
| "formatTagHash": formatTagHash, |
| "formatCommitTableTitle": formatCommitTableTitle, |
| "formatList": formatStringList, |
| "selectBisect": selectBisect, |
| "dereference": dereferencePointer, |
| "commitLink": commitLink, |
| } |
| |
| func selectBisect(rep *dashapi.BugReport) *dashapi.BisectResult { |
| if rep.BisectFix != nil { |
| return rep.BisectFix |
| } |
| return rep.BisectCause |
| } |
| |
| func link(url, text string) template.HTML { |
| text = template.HTMLEscapeString(text) |
| if url != "" { |
| text = fmt.Sprintf(`<a href="%v">%v</a>`, url, text) |
| } |
| return template.HTML(text) |
| } |
| |
| func optlink(url, text string) template.HTML { |
| if url == "" { |
| return template.HTML("") |
| } |
| return link(url, text) |
| } |
| |
| func FormatTime(t time.Time) string { |
| if t.IsZero() { |
| return "" |
| } |
| return t.Format("2006/01/02 15:04") |
| } |
| |
| func FormatDate(t time.Time) string { |
| if t.IsZero() { |
| return "" |
| } |
| return t.Format("2006/01/02") |
| } |
| |
| func formatKernelTime(t time.Time) string { |
| if t.IsZero() { |
| return "" |
| } |
| // This is how dates appear in git log. |
| return t.Format("Mon Jan 2 15:04:05 2006 -0700") |
| } |
| |
| func formatJSTime(t time.Time) string { |
| return t.Format("2006-01-02T15:04:05") // ISO 8601 without time zone |
| } |
| |
| func formatClock(t time.Time) string { |
| if t.IsZero() { |
| return "" |
| } |
| return t.Format("15:04") |
| } |
| |
| func formatDuration(d time.Duration) string { |
| if d == 0 { |
| return "" |
| } |
| days := int(d / (24 * time.Hour)) |
| hours := int(d / time.Hour % 24) |
| mins := int(d / time.Minute % 60) |
| if days >= 10 { |
| return fmt.Sprintf("%vd", days) |
| } else if days != 0 { |
| return fmt.Sprintf("%vd%02vh", days, hours) |
| } else if hours != 0 { |
| return fmt.Sprintf("%vh%02vm", hours, mins) |
| } |
| return fmt.Sprintf("%vm", mins) |
| } |
| |
| func formatLateness(now, t time.Time) string { |
| if t.IsZero() { |
| return "never" |
| } |
| d := now.Sub(t) |
| if d < 5*time.Minute { |
| return "now" |
| } |
| return formatDuration(d) |
| } |
| |
| func formatReproLevel(l dashapi.ReproLevel) string { |
| switch l { |
| case dashapi.ReproLevelSyz: |
| return "syz" |
| case dashapi.ReproLevelC: |
| return "C" |
| default: |
| return "" |
| } |
| } |
| |
| func formatStat(v int64) string { |
| if v == 0 { |
| return "" |
| } |
| return fmt.Sprint(v) |
| } |
| |
| func formatShortHash(v string) string { |
| const hashLen = 8 |
| if len(v) <= hashLen { |
| return v |
| } |
| return v[:hashLen] |
| } |
| |
| func formatTagHash(v string) string { |
| // Note: Fixes/References commit tags should include 12-char hash |
| // (see Documentation/process/submitting-patches.rst). Don't change this const. |
| const hashLen = 12 |
| if len(v) <= hashLen { |
| return v |
| } |
| return v[:hashLen] |
| } |
| |
| func formatCommitTableTitle(v string) string { |
| // This function is very specific to how we format tables in text emails. |
| // Truncate commit title so that whole line fits into 78 chars. |
| const commitTitleLen = 47 |
| if len(v) <= commitTitleLen { |
| return v |
| } |
| return v[:commitTitleLen-2] + ".." |
| } |
| |
| func formatStringList(list []string) string { |
| return strings.Join(list, ", ") |
| } |
| |
| func dereferencePointer(v interface{}) interface{} { |
| reflectValue := reflect.ValueOf(v) |
| if !reflectValue.IsNil() && reflectValue.Kind() == reflect.Ptr { |
| elem := reflectValue.Elem() |
| if elem.CanInterface() { |
| return elem.Interface() |
| } |
| } |
| return v |
| } |
| |
| func commitLink(repo, commit string) string { |
| return vcs.CommitLink(repo, commit) |
| } |
| |
| func AmendURL(baseURL, key, value string) string { |
| return TransformURL(baseURL, key, func(_ []string) []string { |
| if value == "" { |
| return nil |
| } |
| return []string{value} |
| }) |
| } |
| |
| func DropParam(baseURL, key, value string) string { |
| return TransformURL(baseURL, key, func(oldValues []string) []string { |
| if value == "" { |
| return nil |
| } |
| var newValues []string |
| for _, iterVal := range oldValues { |
| if iterVal != value { |
| newValues = append(newValues, iterVal) |
| } |
| } |
| return newValues |
| }) |
| } |
| |
| func TransformURL(baseURL, key string, f func([]string) []string) string { |
| if baseURL == "" { |
| return "" |
| } |
| parsed, err := url.Parse(baseURL) |
| if err != nil { |
| return "" |
| } |
| values := parsed.Query() |
| ret := f(values[key]) |
| if len(ret) == 0 { |
| values.Del(key) |
| } else { |
| values[key] = ret |
| } |
| parsed.RawQuery = values.Encode() |
| return parsed.String() |
| } |