mirror of https://github.com/go-gitea/gitea.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
122 lines
3.4 KiB
122 lines
3.4 KiB
// Copyright 2023 The Gitea Authors. All rights reserved. |
|
// SPDX-License-Identifier: MIT |
|
|
|
package templates |
|
|
|
import ( |
|
"errors" |
|
"fmt" |
|
"html" |
|
"html/template" |
|
"reflect" |
|
|
|
"code.gitea.io/gitea/modules/container" |
|
"code.gitea.io/gitea/modules/json" |
|
"code.gitea.io/gitea/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>") |
|
}
|
|
|