package chrome import ( "fmt" "html/template" "time" ) // Funcs returns the template helpers every service was carrying its own copy // of. Merge into a service's FuncMap before its own helpers, so a service can // still shadow a name deliberately. func Funcs() template.FuncMap { return template.FuncMap{ "dict": Dict, "shortsha": ShortSHA, "reltime": RelTime, "abstime": AbsTime, } } // Dict builds a map from alternating key/value arguments, so a partial that // needs several fields can be invoked with an inline context: // {{template "x" (dict "A" .A "B" .B)}}. An odd argument count or a non-string // key is a template authoring error and surfaces as a render error. func Dict(kv ...any) (map[string]any, error) { if len(kv)%2 != 0 { return nil, fmt.Errorf("dict: expected an even number of arguments, got %d", len(kv)) } m := make(map[string]any, len(kv)/2) for i := 0; i < len(kv); i += 2 { k, ok := kv[i].(string) if !ok { return nil, fmt.Errorf("dict: key %d is not a string", i) } m[k] = kv[i+1] } return m, nil } // ShortSHA abbreviates an object id to its first 8 characters (or returns it // unchanged if shorter), the convention used everywhere commits are listed. func ShortSHA(s string) string { if len(s) > 8 { return s[:8] } return s } // RelTime is the coarse "3 hours ago" a listing wants, and AbsTime the exact // UTC stamp an investigation wants. Both exist, and both are shared, because // every service on the instance shows the same two columns and had grown its // own spelling of them: the copies disagreed about the future, printing "in 3 // hours" on one service and "just now" on the next for the same instant. // // A future instant gets the same arithmetic as a past one. "in 3 weeks" // answers "do I have to deal with this today" without the reader working it // out from a calendar stamp. func RelTime(t time.Time) string { d := time.Since(t) switch { case d < -time.Minute: return "in " + coarse(-d) case d < time.Minute: // Covers both an instant that has just passed and one about to, which // is also what two machines with unsynchronised clocks produce for the // same instant. return "just now" default: return coarse(d) + " ago" } } // AbsTime is the unambiguous stamp, one hover away from a RelTime. func AbsTime(t time.Time) string { return t.UTC().Format("2006-01-02 15:04:05 UTC") } // coarse names a positive duration in its largest whole unit. The direction is // the caller's to add, so that "in 3 weeks" and "3 weeks ago" cannot end up // counting in different units. func coarse(d time.Duration) string { switch { case d < time.Hour: return plural(int(d/time.Minute), "minute") case d < 24*time.Hour: return plural(int(d/time.Hour), "hour") case d < 30*24*time.Hour: return plural(int(d/(24*time.Hour)), "day") case d < 365*24*time.Hour: return plural(int(d/(30*24*time.Hour)), "month") default: return plural(int(d/(365*24*time.Hour)), "year") } } func plural(n int, unit string) string { if n == 1 { return "1 " + unit } return fmt.Sprintf("%d %ss", n, unit) }