refactor(tools): modularize CLI with urfave/cli/v2, externalise template metadata
- Split monolithic build-preview.go into modular packages: tools.go (entry), cli/ (list/create/delete/preview commands), config/ (JSON loading), preview/ (rendering engine, funcs, locale) - Extract all template metadata into data/templates_config.json as single source of truth — no hardcoded template data in Go - Replace hand-rolled CLI parsing with github.com/urfave/cli/v2 - Add sensible defaults for --folder (../themes) and --config (./data/templates_config.json) — most commands now run bare - Add type-coercing gt/lt/ge/le template funcs for JSON float64 - Fix AppUrl to gitea.com matching official template convention - rendered.js now exports __REGISTRY__ and __PARAMS__ alongside __RENDERED__ — preview/index.html reads all from single source - Cross-validate all 11 template data contexts against official Gitea source (services/mailer/*.go + templates/mail/*.tmpl) - Update all docs (README, AGENTS, CONTRIBUTING, 5 languages) to reflect new CLI workflow with create scaffolding command Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
77854ee0ed
commit
0bf982ffb9
30 files changed
+1274
-540
No files matched your search
@@ -0,0 +1,159 @@
|
||||
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{
|
||||
"AppUrl": func() string { return "https://gitea.com/" },
|
||||
"AppName": func() string { return "Gitea" },
|
||||
"DotEscape": func(s string) template.HTML { return template.HTML(template.HTMLEscapeString(s)) },
|
||||
"QueryEscape": url.QueryEscape,
|
||||
"ShortSha": ShortSha,
|
||||
"PathEscapeSegments": PathEscapeSegments,
|
||||
"FileSize": FileSize,
|
||||
"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, "/")
|
||||
}
|
||||
|
||||
// FileSize formats a byte count as a human-readable string (e.g. "2.5 MB").
|
||||
func FileSize(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{"KB", "MB", "GB", "TB"}[exp])
|
||||
}
|
||||
|
||||
// HTMLFormat substitutes %s/%d placeholders with HTML-escaped values.
|
||||
func HTMLFormat(fmtStr string, args ...any) template.HTML {
|
||||
result := fmtStr
|
||||
for i, arg := range args {
|
||||
escaped := template.HTMLEscapeString(fmt.Sprint(arg))
|
||||
result = strings.Replace(result, fmt.Sprintf("%%[%d]s", i+1), escaped, 1)
|
||||
if !strings.Contains(result, fmt.Sprintf("%%[%d]s", i+1)) {
|
||||
result = strings.Replace(result, "%s", escaped, 1)
|
||||
result = strings.Replace(result, "%d", fmt.Sprint(arg), 1)
|
||||
}
|
||||
}
|
||||
return template.HTML(result)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
Reference in new issue
Block a user