mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-29 10:57:44 +09:00 
			
		
		
		
	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.

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 <dev.lh@web.de>
Co-authored-by: silverwind <me@silverwind.io>
			
			
This commit is contained in:
		| @@ -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) | ||||
|   | ||||
| @@ -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 "<pre>dumpVar: only available in dev mode</pre>" | ||||
| 	} | ||||
| 	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("<pre>" + html.EscapeString(dumpStr) + "</pre>") | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user