package core import ( "fmt" "html/template" "strings" "time" ) // FuncMap is bound to a single Ctx at render time. `mod` is the only // entry point a template needs; everything else is sugar. func FuncMap(c *Ctx) template.FuncMap { return template.FuncMap{ // mod runs a module. A template that calls it is what *causes* // the work -- views that don't ask, don't pay. "mod": func(name string, args ...string) (any, error) { return c.Mod(name, args...) }, // modOr swallows the error, for optional modules (no README, etc). "modOr": func(name string, args ...string) any { v, err := c.Mod(name, args...) if err != nil { return nil } return v }, "has": func(name string) bool { v, err := c.Mod(name) return err == nil && v != nil }, "url": func(parts ...string) string { return "/" + strings.Trim(strings.Join(parts, "/"), "/") }, "dict": dict, "date": func(t time.Time) string { if t.IsZero() { return "" } return t.Format("2006-01-02 15:04") }, "rfc3339": func(t time.Time) string { return t.Format(time.RFC3339) }, "trunc": func(n int, s string) string { if len(s) <= n { return s } return s[:n] + "\u2026" }, "bytes": humanBytes, "patch": renderPatch, "slug": slugify, "bytes64": func(n int) string { return humanBytes(int64(n)) }, } } // slugify makes a section name usable as a fragment id. func slugify(s string) string { var b strings.Builder prevDash := false for _, r := range strings.ToLower(s) { switch { case (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9'): b.WriteRune(r) prevDash = false case !prevDash && b.Len() > 0: b.WriteByte('-') prevDash = true } } return strings.TrimRight(b.String(), "-") } // renderPatch tags each diff line so the stylesheet can colour it. Doing // this here rather than in CSS lets the gutter carry the signal instead of // flooding whole rows with green and red. func renderPatch(p string) template.HTML { var b strings.Builder b.WriteString(`