Skip to content

Commit c0d1056

Browse files
wxiaoguangdelvhsilverwind
authored
Add DumpVar helper function to help debugging templates (#24262)
I guess many contributors might agree that it's really difficult to write Golang template. The dot syntax `.` confuses everyone: what variable it is .... So, we can use a `{{DumpVar .ContextUser}}` to look into every variable now. ![image](https://user-images.githubusercontent.com/2114189/233692383-f3c8f24d-4465-45f8-839b-b63e00731559.png) And it can even dump the whole `ctx.Data` by `{{DumpVar .}}`: ``` dumpVar: templates.Vars { "AllLangs": [ { "Lang": "id-ID", "Name": "Bahasa Indonesia" }, ... "Context": "[dumped]", "ContextUser": { "AllowCreateOrganization": true, "AllowGitHook": false, "AllowImportLocal": false, ... "TemplateLoadTimes": "[func() string]", "TemplateName": "user/profile", "Title": "Full'\u003cspan\u003e Name", "Total": 7, "UnitActionsGlobalDisabled": false, "UnitIssuesGlobalDisabled": false, "UnitProjectsGlobalDisabled": false, "UnitPullsGlobalDisabled": false, "UnitWikiGlobalDisabled": false, "locale": { "Lang": "en-US", "LangName": "English", "Locale": {} } ... --------- Co-authored-by: delvh <[email protected]> Co-authored-by: silverwind <[email protected]>
1 parent 3cc8737 commit c0d1056

File tree

2 files changed

+75
-0
lines changed

2 files changed

+75
-0
lines changed

modules/templates/helper.go

+1
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ func NewFuncMap() []template.FuncMap {
7474
"DotEscape": DotEscape,
7575
"HasPrefix": strings.HasPrefix,
7676
"EllipsisString": base.EllipsisString,
77+
"DumpVar": dumpVar,
7778

7879
"Json": func(in interface{}) string {
7980
out, err := json.Marshal(in)

modules/templates/util.go

+74
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@ package templates
55

66
import (
77
"fmt"
8+
"html"
9+
"html/template"
810
"reflect"
11+
12+
"code.gitea.io/gitea/modules/json"
13+
"code.gitea.io/gitea/modules/setting"
914
)
1015

1116
func dictMerge(base map[string]any, arg any) bool {
@@ -45,3 +50,72 @@ func dict(args ...any) (map[string]any, error) {
4550
}
4651
return m, nil
4752
}
53+
54+
func dumpVarMarshalable(v any, dumped map[uintptr]bool) (ret any, ok bool) {
55+
if v == nil {
56+
return nil, true
57+
}
58+
e := reflect.ValueOf(v)
59+
for e.Kind() == reflect.Pointer {
60+
e = e.Elem()
61+
}
62+
if e.CanAddr() {
63+
addr := e.UnsafeAddr()
64+
if dumped[addr] {
65+
return "[dumped]", false
66+
}
67+
dumped[addr] = true
68+
defer delete(dumped, 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, map[uintptr]bool{})
111+
dumpStr := ""
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+
}

0 commit comments

Comments
 (0)