123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121 |
- // Copyright 2023 The Gitea Authors. All rights reserved.
- // SPDX-License-Identifier: MIT
-
- package templates
-
- import (
- "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, fmt.Errorf("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>")
- }
|