code.gitea.io/gitea@v1.22.3/modules/templates/util_dict.go (about) 1 // Copyright 2023 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package templates 5 6 import ( 7 "fmt" 8 "html" 9 "html/template" 10 "reflect" 11 12 "code.gitea.io/gitea/modules/container" 13 "code.gitea.io/gitea/modules/json" 14 "code.gitea.io/gitea/modules/setting" 15 ) 16 17 func dictMerge(base map[string]any, arg any) bool { 18 if arg == nil { 19 return true 20 } 21 rv := reflect.ValueOf(arg) 22 if rv.Kind() == reflect.Map { 23 for _, k := range rv.MapKeys() { 24 base[k.String()] = rv.MapIndex(k).Interface() 25 } 26 return true 27 } 28 return false 29 } 30 31 // dict is a helper function for creating a map[string]any from a list of key-value pairs. 32 // If the key is dot ".", the value is merged into the base map, just like Golang template's dot syntax: dot means current 33 // The dot syntax is highly discouraged because it might cause unclear key conflicts. It's always good to use explicit keys. 34 func dict(args ...any) (map[string]any, error) { 35 if len(args)%2 != 0 { 36 return nil, fmt.Errorf("invalid dict constructor syntax: must have key-value pairs") 37 } 38 m := make(map[string]any, len(args)/2) 39 for i := 0; i < len(args); i += 2 { 40 key, ok := args[i].(string) 41 if !ok { 42 return nil, fmt.Errorf("invalid dict constructor syntax: unable to merge args[%d]", i) 43 } 44 if key == "." { 45 if ok = dictMerge(m, args[i+1]); !ok { 46 return nil, fmt.Errorf("invalid dict constructor syntax: dot arg[%d] must be followed by a dict", i) 47 } 48 } else { 49 m[key] = args[i+1] 50 } 51 } 52 return m, nil 53 } 54 55 func dumpVarMarshalable(v any, dumped container.Set[uintptr]) (ret any, ok bool) { 56 if v == nil { 57 return nil, true 58 } 59 e := reflect.ValueOf(v) 60 for e.Kind() == reflect.Pointer { 61 e = e.Elem() 62 } 63 if e.CanAddr() { 64 addr := e.UnsafeAddr() 65 if !dumped.Add(addr) { 66 return "[dumped]", false 67 } 68 defer dumped.Remove(addr) 69 } 70 switch e.Kind() { 71 case reflect.Bool, reflect.String, 72 reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, 73 reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, 74 reflect.Float32, reflect.Float64: 75 return e.Interface(), true 76 case reflect.Struct: 77 m := map[string]any{} 78 for i := 0; i < e.NumField(); i++ { 79 k := e.Type().Field(i).Name 80 if !e.Type().Field(i).IsExported() { 81 continue 82 } 83 v := e.Field(i).Interface() 84 m[k], _ = dumpVarMarshalable(v, dumped) 85 } 86 return m, true 87 case reflect.Map: 88 m := map[string]any{} 89 for _, k := range e.MapKeys() { 90 m[k.String()], _ = dumpVarMarshalable(e.MapIndex(k).Interface(), dumped) 91 } 92 return m, true 93 case reflect.Array, reflect.Slice: 94 var m []any 95 for i := 0; i < e.Len(); i++ { 96 v, _ := dumpVarMarshalable(e.Index(i).Interface(), dumped) 97 m = append(m, v) 98 } 99 return m, true 100 default: 101 return "[" + reflect.TypeOf(v).String() + "]", false 102 } 103 } 104 105 // dumpVar helps to dump a variable in a template, to help debugging and development. 106 func dumpVar(v any) template.HTML { 107 if setting.IsProd { 108 return "<pre>dumpVar: only available in dev mode</pre>" 109 } 110 m, ok := dumpVarMarshalable(v, make(container.Set[uintptr])) 111 var dumpStr string 112 jsonBytes, err := json.MarshalIndent(m, "", " ") 113 if err != nil { 114 dumpStr = fmt.Sprintf("dumpVar: unable to marshal %T: %v", v, err) 115 } else if ok { 116 dumpStr = fmt.Sprintf("dumpVar: %T\n%s", v, string(jsonBytes)) 117 } else { 118 dumpStr = fmt.Sprintf("dumpVar: unmarshalable %T\n%s", v, string(jsonBytes)) 119 } 120 return template.HTML("<pre>" + html.EscapeString(dumpStr) + "</pre>") 121 }