mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 21:28:11 +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, | 		"DotEscape":      DotEscape, | ||||||
| 		"HasPrefix":      strings.HasPrefix, | 		"HasPrefix":      strings.HasPrefix, | ||||||
| 		"EllipsisString": base.EllipsisString, | 		"EllipsisString": base.EllipsisString, | ||||||
|  | 		"DumpVar":        dumpVar, | ||||||
|  |  | ||||||
| 		"Json": func(in interface{}) string { | 		"Json": func(in interface{}) string { | ||||||
| 			out, err := json.Marshal(in) | 			out, err := json.Marshal(in) | ||||||
|   | |||||||
| @@ -5,7 +5,12 @@ package templates | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"html" | ||||||
|  | 	"html/template" | ||||||
| 	"reflect" | 	"reflect" | ||||||
|  |  | ||||||
|  | 	"code.gitea.io/gitea/modules/json" | ||||||
|  | 	"code.gitea.io/gitea/modules/setting" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func dictMerge(base map[string]any, arg any) bool { | func dictMerge(base map[string]any, arg any) bool { | ||||||
| @@ -45,3 +50,72 @@ func dict(args ...any) (map[string]any, error) { | |||||||
| 	} | 	} | ||||||
| 	return m, nil | 	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