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.

util_dict.go 3.4KB

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