Files
GiteaMailTemplates/tools/preview/funcs.go
T
KenanZhu fec3ace600
Release / Validate Templates (push) Canceled after 0s
Release / Package & Release (push) Canceled after 0s
Release / Update Latest Release Documentation (push) Canceled after 0s
chore: migrate mail themes to shared framework and locked upstream inputs
2026-10-09 18:34:36 +08:00

165 lines
4.1 KiB
Go

package preview
import (
"fmt"
"html/template"
"net/url"
"strings"
)
// TemplateFuncs maps Gitea's built-in template functions available in mail templates.
var TemplateFuncs = template.FuncMap{
"dict": Dict,
"AppUrl": func() string { return "https://gitea.com/" },
"AppName": func() string { return "Gitea" },
"AppDomain": func() string { return "gitea.com" },
"DotEscape": func(s string) string { return strings.ReplaceAll(s, ".", "\u200d.\u200d") },
"QueryEscape": url.QueryEscape,
"ShortSha": ShortSha,
"PathEscapeSegments": PathEscapeSegments,
"FormatByteSize": previewFormatByteSize,
"HTMLFormat": HTMLFormat,
"Dict": Dict,
"gt": Gt, // override built-in for JSON float64/int coercion
"lt": Lt,
"ge": Ge,
"le": Le,
}
// ShortSha truncates a commit SHA to 10 characters.
func ShortSha(s string) string {
if len(s) > 10 {
return s[:10]
}
return s
}
// PathEscapeSegments splits a path and URL-escapes each segment.
func PathEscapeSegments(s string) string {
parts := strings.Split(s, "/")
for i, p := range parts {
parts[i] = url.PathEscape(p)
}
return strings.Join(parts, "/")
}
// previewFormatByteSize converts JSON fixture numbers to Gitea's int64 input type.
func previewFormatByteSize(size any) (string, error) {
switch n := size.(type) {
case int64:
return FormatByteSize(n), nil
case float64:
return FormatByteSize(int64(n)), nil
default:
return "", fmt.Errorf("FormatByteSize: unsupported size type %T", size)
}
}
// FormatByteSize mirrors Gitea 28's IEC attachment size formatting.
func FormatByteSize(n int64) string {
const unit = 1024
if n < unit {
return fmt.Sprintf("%d B", n)
}
div, exp := int64(unit), 0
for nn := n / unit; nn >= unit && exp < 3; nn /= unit {
div *= unit
exp++
}
return fmt.Sprintf("%.1f %s", float64(n)/float64(div), []string{"KiB", "MiB", "GiB", "TiB"}[exp])
}
// HTMLFormat substitutes %s/%d placeholders with HTML-escaped values.
func HTMLFormat(fmtStr string, args ...any) template.HTML {
return template.HTML(fmt.Sprintf(fmtStr, escapedArgs(args)...))
}
// Dict builds a map from alternating key/value pairs (used by Gitea's Dict function).
func Dict(values ...any) (map[string]any, error) {
if len(values)%2 != 0 {
return nil, fmt.Errorf("odd number of Dict args")
}
dict := make(map[string]any, len(values)/2)
for i := 0; i < len(values); i += 2 {
key, ok := values[i].(string)
if !ok {
return nil, fmt.Errorf("Dict keys must be strings")
}
dict[key] = values[i+1]
}
return dict, nil
}
// toFloat64 coerces a value to float64 if numeric.
func toFloat64(v any) (float64, bool) {
switch val := v.(type) {
case float64:
return val, true
case float32:
return float64(val), true
case int:
return float64(val), true
case int64:
return float64(val), true
case int32:
return float64(val), true
case int16:
return float64(val), true
case int8:
return float64(val), true
case uint:
return float64(val), true
case uint64:
return float64(val), true
case uint32:
return float64(val), true
case uint16:
return float64(val), true
case uint8:
return float64(val), true
default:
return 0, false
}
}
// Gt is a type-coercing > comparison that handles JSON float64 vs int literals.
func Gt(a, b any) bool {
af, aok := toFloat64(a)
bf, bok := toFloat64(b)
if aok && bok {
return af > bf
}
// Fallback to string comparison
return fmt.Sprint(a) > fmt.Sprint(b)
}
// Lt is a type-coercing < comparison that handles JSON float64 vs int literals.
func Lt(a, b any) bool {
af, aok := toFloat64(a)
bf, bok := toFloat64(b)
if aok && bok {
return af < bf
}
return fmt.Sprint(a) < fmt.Sprint(b)
}
// Ge is a type-coercing >= comparison.
func Ge(a, b any) bool {
af, aok := toFloat64(a)
bf, bok := toFloat64(b)
if aok && bok {
return af >= bf
}
return fmt.Sprint(a) >= fmt.Sprint(b)
}
// Le is a type-coercing <= comparison.
func Le(a, b any) bool {
af, aok := toFloat64(a)
bf, bok := toFloat64(b)
if aok && bok {
return af <= bf
}
return fmt.Sprint(a) <= fmt.Sprint(b)
}