mirror of
https://github.com/hanzoai/forge.git
synced 2026-10-30 15:00:39 +00:00
Source files keep their SPDX-License-Identifier and Hanzo copyright line; the Gitea, Gogs and LFS Test Server copyrights they used to repeat now live once in NOTICE, alongside LICENSE. The header lint accepts any copyright line above the SPDX line, so it no longer demands an upstream one. Co-authored-by: Hanzo Dev <[email protected]>
123 lines
3.4 KiB
Go
123 lines
3.4 KiB
Go
// Copyright 2026 Hanzo AI, Inc. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package templates
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"html"
|
|
"html/template"
|
|
"reflect"
|
|
|
|
"github.com/hanzoai/git/modules/container"
|
|
"github.com/hanzoai/git/modules/json"
|
|
"github.com/hanzoai/git/modules/setting"
|
|
)
|
|
|
|
func dictMerge(base map[string]any, arg any) bool {
|
|
if arg == nil {
|
|
return true
|
|
}
|
|
rv := reflect.ValueOf(arg)
|
|
if rv.Kind() == reflect.Map {
|
|
for _, k := range rv.MapKeys() {
|
|
base[k.String()] = rv.MapIndex(k).Interface()
|
|
}
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// dict is a helper function for creating a map[string]any from a list of key-value pairs.
|
|
// If the key is dot ".", the value is merged into the base map, just like Golang template's dot syntax: dot means current
|
|
// The dot syntax is highly discouraged because it might cause unclear key conflicts. It's always good to use explicit keys.
|
|
func dict(args ...any) (map[string]any, error) {
|
|
if len(args)%2 != 0 {
|
|
return nil, errors.New("invalid dict constructor syntax: must have key-value pairs")
|
|
}
|
|
m := make(map[string]any, len(args)/2)
|
|
for i := 0; i < len(args); i += 2 {
|
|
key, ok := args[i].(string)
|
|
if !ok {
|
|
return nil, fmt.Errorf("invalid dict constructor syntax: unable to merge args[%d]", i)
|
|
}
|
|
if key == "." {
|
|
if ok = dictMerge(m, args[i+1]); !ok {
|
|
return nil, fmt.Errorf("invalid dict constructor syntax: dot arg[%d] must be followed by a dict", i)
|
|
}
|
|
} else {
|
|
m[key] = args[i+1]
|
|
}
|
|
}
|
|
return m, nil
|
|
}
|
|
|
|
func dumpVarMarshalable(v any, dumped container.Set[uintptr]) (ret any, ok bool) {
|
|
if v == nil {
|
|
return nil, true
|
|
}
|
|
e := reflect.ValueOf(v)
|
|
for e.Kind() == reflect.Pointer {
|
|
e = e.Elem()
|
|
}
|
|
if e.CanAddr() {
|
|
addr := e.UnsafeAddr()
|
|
if !dumped.Add(addr) {
|
|
return "[dumped]", false
|
|
}
|
|
defer dumped.Remove(addr)
|
|
}
|
|
switch e.Kind() {
|
|
case reflect.Bool, reflect.String,
|
|
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
|
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
|
|
reflect.Float32, reflect.Float64:
|
|
return e.Interface(), true
|
|
case reflect.Struct:
|
|
m := map[string]any{}
|
|
for i := 0; i < e.NumField(); i++ {
|
|
k := e.Type().Field(i).Name
|
|
if !e.Type().Field(i).IsExported() {
|
|
continue
|
|
}
|
|
v := e.Field(i).Interface()
|
|
m[k], _ = dumpVarMarshalable(v, dumped)
|
|
}
|
|
return m, true
|
|
case reflect.Map:
|
|
m := map[string]any{}
|
|
for _, k := range e.MapKeys() {
|
|
m[k.String()], _ = dumpVarMarshalable(e.MapIndex(k).Interface(), dumped)
|
|
}
|
|
return m, true
|
|
case reflect.Array, reflect.Slice:
|
|
var m []any
|
|
for i := 0; i < e.Len(); i++ {
|
|
v, _ := dumpVarMarshalable(e.Index(i).Interface(), dumped)
|
|
m = append(m, v)
|
|
}
|
|
return m, true
|
|
default:
|
|
return "[" + reflect.TypeOf(v).String() + "]", false
|
|
}
|
|
}
|
|
|
|
// dumpVar helps to dump a variable in a template, to help debugging and development.
|
|
func dumpVar(v any) template.HTML {
|
|
if setting.IsProd {
|
|
return "<pre>dumpVar: only available in dev mode</pre>"
|
|
}
|
|
m, ok := dumpVarMarshalable(v, make(container.Set[uintptr]))
|
|
var dumpStr string
|
|
jsonBytes, err := json.MarshalIndent(m, "", " ")
|
|
if err != nil {
|
|
dumpStr = fmt.Sprintf("dumpVar: unable to marshal %T: %v", v, err)
|
|
} else if ok {
|
|
dumpStr = fmt.Sprintf("dumpVar: %T\n%s", v, string(jsonBytes))
|
|
} else {
|
|
dumpStr = fmt.Sprintf("dumpVar: unmarshalable %T\n%s", v, string(jsonBytes))
|
|
}
|
|
return template.HTML("<pre>" + html.EscapeString(dumpStr) + "</pre>")
|
|
}
|