summaryrefslogtreecommitdiffstats
path: root/modules/templates/util_dict.go
diff options
context:
space:
mode:
Diffstat (limited to 'modules/templates/util_dict.go')
-rw-r--r--modules/templates/util_dict.go121
1 files changed, 121 insertions, 0 deletions
diff --git a/modules/templates/util_dict.go b/modules/templates/util_dict.go
new file mode 100644
index 00000000..8d6376b5
--- /dev/null
+++ b/modules/templates/util_dict.go
@@ -0,0 +1,121 @@
+// 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>")
+}