From c0d105609f120839c91679b0548b9438155c4bba Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sun, 23 Apr 2023 01:28:20 +0800 Subject: [PATCH] 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 Co-authored-by: silverwind --- modules/templates/helper.go | 1 + modules/templates/util.go | 74 +++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/modules/templates/helper.go b/modules/templates/helper.go index 24687a4606..42680827eb 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -74,6 +74,7 @@ func NewFuncMap() []template.FuncMap { "DotEscape": DotEscape, "HasPrefix": strings.HasPrefix, "EllipsisString": base.EllipsisString, + "DumpVar": dumpVar, "Json": func(in interface{}) string { out, err := json.Marshal(in) diff --git a/modules/templates/util.go b/modules/templates/util.go index 13f3a56808..c83f22449c 100644 --- a/modules/templates/util.go +++ b/modules/templates/util.go @@ -5,7 +5,12 @@ package templates import ( "fmt" + "html" + "html/template" "reflect" + + "code.gitea.io/gitea/modules/json" + "code.gitea.io/gitea/modules/setting" ) func dictMerge(base map[string]any, arg any) bool { @@ -45,3 +50,72 @@ func dict(args ...any) (map[string]any, error) { } return m, nil } + +func dumpVarMarshalable(v any, dumped map[uintptr]bool) (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[addr] { + return "[dumped]", false + } + dumped[addr] = true + defer delete(dumped, 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 "
dumpVar: only available in dev mode
" + } + m, ok := dumpVarMarshalable(v, map[uintptr]bool{}) + dumpStr := "" + 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("
" + html.EscapeString(dumpStr) + "
") +}