package main import ( "embed" "fmt" "html" "html/template" "io" "path" "regexp" "strings" "time" ) //go:embed templates/*.html var templateFS embed.FS //go:embed static/style.css var cssContent []byte //go:embed static/radiant.svg var logoContent []byte //go:embed static/fonts/* var fontsFS embed.FS //go:embed static/avatars/* var avatarsFS embed.FS //go:embed static/js/hirad.js var hiradJS []byte //go:embed static/js/hiril.js var hirilJS []byte type templateSet struct { templates map[string]*template.Template } var funcMap = template.FuncMap{ "shortHash": func(h string) string { if len(h) > 8 { return h[:8] } return h }, "timeAgo": func(t time.Time) template.HTML { d := time.Since(t) var ago string switch { case d < time.Minute: ago = "just now" case d < time.Hour: m := int(d.Minutes()) if m == 1 { ago = "1 minute" } else { ago = fmt.Sprintf("%d minutes", m) } case d < 24*time.Hour: h := int(d.Hours()) if h == 1 { ago = "1 hour" } else { ago = fmt.Sprintf("%d hours", h) } case d < 30*24*time.Hour: days := int(d.Hours() / 24) if days == 1 { ago = "1 day" } else { ago = fmt.Sprintf("%d days", days) } case d < 365*24*time.Hour: months := int(d.Hours() / 24 / 30) if months == 1 { ago = "1 month" } else { ago = fmt.Sprintf("%d months", months) } default: years := int(d.Hours() / 24 / 365) if years == 1 { ago = "1 year" } else { ago = fmt.Sprintf("%d years", years) } } full := t.Format("2006-01-02 15:04:05 -0700") return template.HTML(fmt.Sprintf(``, full, ago)) }, "formatDate": func(t time.Time) string { return t.Format("2006-01-02 15:04:05 -0700") }, "add": func(a, b int) int { return a + b }, "diffFileName": func(f DiffFile) string { if f.NewName != "" { return f.NewName } return f.OldName }, "statusLabel": func(s string) string { switch s { case "A": return "added" case "D": return "deleted" case "M": return "modified" case "R": return "renamed" } return s }, "formatSize": func(size int64) string { switch { case size < 1024: return fmt.Sprintf("%d B", size) case size < 1024*1024: return fmt.Sprintf("%.1f KiB", float64(size)/1024) default: return fmt.Sprintf("%.1f MiB", float64(size)/(1024*1024)) } }, "diffBar": func(added, deleted int) template.HTML { total := added + deleted if total == 0 { return "" } const maxBlocks = 5 blocks := maxBlocks if total < blocks { blocks = total } addBlocks := 0 if total > 0 { addBlocks = (added*blocks + total - 1) / total } if addBlocks > blocks { addBlocks = blocks } delBlocks := blocks - addBlocks var b strings.Builder b.WriteString(``) for i := 0; i < addBlocks; i++ { b.WriteString(``) } for i := 0; i < delBlocks; i++ { b.WriteString(``) } for i := addBlocks + delBlocks; i < maxBlocks; i++ { b.WriteString(``) } b.WriteString(``) return template.HTML(b.String()) }, "langClass": func(name string) string { ext := path.Ext(name) switch ext { case ".rad": return "language-radiance" case ".ril": return "language-ril" } return "" }, "parentPath": func(p string) string { dir := path.Dir(p) if dir == "." { return "" } return dir }, "indent": func(depth int) string { if depth == 0 { return "" } return fmt.Sprintf("padding-left: %grem", float64(depth)*1.2) }, "formatBody": func(s string) template.HTML { s = strings.TrimSpace(s) if s == "" { return "" } var b strings.Builder // Split into alternating prose / fenced-code-block segments. for s != "" { // Find the next opening fence. openIdx := strings.Index(s, "```") if openIdx < 0 { formatParagraphs(&b, s) break } // Prose before the fence. if openIdx > 0 { formatParagraphs(&b, s[:openIdx]) } // Skip the opening ``` and any language tag on the same line. rest := s[openIdx+3:] if nl := strings.Index(rest, "\n"); nl >= 0 { rest = rest[nl+1:] } else { // ``` at end of input with nothing after — treat as prose. formatParagraphs(&b, s[openIdx:]) break } // Find the closing fence. closeIdx := strings.Index(rest, "```") var code string if closeIdx < 0 { code = rest s = "" } else { code = rest[:closeIdx] s = rest[closeIdx+3:] } code = strings.TrimRight(code, "\n") b.WriteString("
")
			b.WriteString(html.EscapeString(code))
			b.WriteString("
") } return template.HTML(b.String()) }, "discussionPath": func(baseURL, repo string) string { if repo == "" { return baseURL + "/discussions" } return baseURL + "/" + repo + "/discussions" }, "autolink": func(s string) template.HTML { var b strings.Builder last := 0 for _, loc := range urlRe.FindAllStringIndex(s, -1) { // Strip trailing punctuation. end := loc[1] for end > loc[0] && strings.ContainsRune(".,;:!?)]", rune(s[end-1])) { end-- } b.WriteString(html.EscapeString(s[last:loc[0]])) u := s[loc[0]:end] b.WriteString(``) b.WriteString(html.EscapeString(u)) b.WriteString(``) last = end } b.WriteString(html.EscapeString(s[last:])) return template.HTML(b.String()) }, } var urlRe = regexp.MustCompile(`https?://[^\s<>"'` + "`" + `\x00-\x1f]+`) // inlineRe matches (in order): backtick code, angle-bracket links, bare URLs, or *italic*. var inlineRe = regexp.MustCompile("`" + `([^` + "`" + `\n]+)` + "`" + `|<(https?://[^\s<>]+)>` + `|(https?://[^\s<>"'` + "`" + `\x00-\x1f]+)` + `|\*([^\s*][^*]*[^\s*])\*|\*([^\s*])\*`) // formatParagraphs splits prose text on blank lines and writes

elements // with inline formatting into b. func formatParagraphs(b *strings.Builder, s string) { for _, p := range strings.Split(s, "\n\n") { p = strings.TrimSpace(p) if p == "" { continue } b.WriteString("

") b.WriteString(formatInline(p)) b.WriteString("

") } } // formatInline applies inline formatting to a paragraph of raw text. // It handles backtick `code`, *italic*, links, and bare URLs. func formatInline(p string) string { var b strings.Builder last := 0 for _, m := range inlineRe.FindAllStringSubmatchIndex(p, -1) { start, end := m[0], m[1] b.WriteString(html.EscapeString(p[last:start])) switch { case m[2] >= 0: // backtick code: group 1 code := p[m[2]:m[3]] b.WriteString("") b.WriteString(html.EscapeString(code)) b.WriteString("") case m[4] >= 0: // angle-bracket link: group 2 u := p[m[4]:m[5]] b.WriteString(``) b.WriteString(html.EscapeString(u)) b.WriteString("") case m[6] >= 0: // bare URL: group 3 // Strip trailing punctuation. urlEnd := m[7] for urlEnd > m[6] && strings.ContainsRune(".,;:!?)]", rune(p[urlEnd-1])) { urlEnd-- } u := p[m[6]:urlEnd] b.WriteString(``) b.WriteString(html.EscapeString(u)) b.WriteString("") // Anything between stripped punctuation and regex end is plain text. b.WriteString(html.EscapeString(p[urlEnd:end])) case m[8] >= 0: // italic (multi-char): group 4 inner := p[m[8]:m[9]] b.WriteString("") b.WriteString(html.EscapeString(inner)) b.WriteString("") case m[10] >= 0: // italic (single-char): group 5 inner := p[m[10]:m[11]] b.WriteString("") b.WriteString(html.EscapeString(inner)) b.WriteString("") } last = end } b.WriteString(html.EscapeString(p[last:])) return b.String() } func loadTemplates() (*templateSet, error) { layoutContent, err := templateFS.ReadFile("templates/layout.html") if err != nil { return nil, fmt.Errorf("read layout: %w", err) } pages := []string{"index", "home", "log", "commit", "refs", "error", "discussions", "discussion", "discussion_new", "login"} ts := &templateSet{ templates: make(map[string]*template.Template, len(pages)), } for _, page := range pages { t := template.New("layout").Funcs(funcMap) t, err = t.Parse(string(layoutContent)) if err != nil { return nil, fmt.Errorf("parse layout: %w", err) } pageContent, err := templateFS.ReadFile(fmt.Sprintf("templates/%s.html", page)) if err != nil { return nil, fmt.Errorf("read %s: %w", page, err) } t, err = t.Parse(string(pageContent)) if err != nil { return nil, fmt.Errorf("parse %s: %w", page, err) } ts.templates[page] = t } return ts, nil } func (ts *templateSet) render(w io.Writer, name string, data any) { t, ok := ts.templates[name] if !ok { fmt.Fprintf(w, "template %q not found", name) return } if err := t.Execute(w, data); err != nil { fmt.Fprintf(w, "template error: %v", err) } }