orchard
mirrorEvery site I host, in one repo, along with the Cloudflare Tunnel and Caddy that front them. It's all Go, Vite, and SQLite, and it runs on a desktop at home with nothing listening on an inbound port.
blogbuncaddycloudflare-tunneldockergogolanghomelabhtml-templatemonorepoself-hostedseosqlitestatic-sitetypstuptime-monitoringviteweb-analytics
1package main
2
3import (
4 "fmt"
5 "html/template"
6 "strconv"
7 "strings"
8)
9
10var templateFuncs = template.FuncMap{
11 "dict": dict,
12 "json": jsonBlock,
13 "pct": pct,
14 "num": formatNum,
15 "add": func(a, b int) int { return a + b },
16 "hasPrefix": strings.HasPrefix,
17}
18
19// dict lets a partial take more than one value, since a Go template has a
20// single dot.
21func dict(pairs ...any) (map[string]any, error) {
22 if len(pairs)%2 != 0 {
23 return nil, fmt.Errorf("dict: odd number of arguments (%d)", len(pairs))
24 }
25 out := make(map[string]any, len(pairs)/2)
26 for i := 0; i < len(pairs); i += 2 {
27 key, ok := pairs[i].(string)
28 if !ok {
29 return nil, fmt.Errorf("dict: key %d is %T, want string", i, pairs[i])
30 }
31 out[key] = pairs[i+1]
32 }
33 return out, nil
34}
35
36// pct renders count as a whole-number percentage of total, and returns 0 rather
37// than dividing by zero.
38func pct(count, total int64) int64 {
39 if total <= 0 {
40 return 0
41 }
42 return count * 100 / total
43}
44
45// formatNum groups thousands with commas.
46func formatNum(v any) string {
47 var n int64
48 switch t := v.(type) {
49 case int:
50 n = int64(t)
51 case int64:
52 n = t
53 case *int64:
54 if t == nil {
55 return "0"
56 }
57 n = *t
58 case float64:
59 n = int64(t)
60 default:
61 return fmt.Sprint(v)
62 }
63
64 s := strconv.FormatInt(n, 10)
65 negative := strings.HasPrefix(s, "-")
66 s = strings.TrimPrefix(s, "-")
67
68 var b strings.Builder
69 for i, digit := range s {
70 if i > 0 && (len(s)-i)%3 == 0 {
71 b.WriteByte(',')
72 }
73 b.WriteRune(digit)
74 }
75 if negative {
76 return "-" + b.String()
77 }
78 return b.String()
79}