2023-04-07 16:39:08 +02:00
|
|
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
|
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
|
|
|
|
package templates
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
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 <dev.lh@web.de>
Co-authored-by: silverwind <me@silverwind.io>
2023-04-22 19:28:20 +02:00
|
|
|
"html"
|
|
|
|
"html/template"
|
2023-04-07 16:39:08 +02:00
|
|
|
"reflect"
|
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 <dev.lh@web.de>
Co-authored-by: silverwind <me@silverwind.io>
2023-04-22 19:28:20 +02:00
|
|
|
|
|
|
|
"code.gitea.io/gitea/modules/json"
|
|
|
|
"code.gitea.io/gitea/modules/setting"
|
2023-04-07 16:39:08 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
func dictMerge(base map[string]any, arg any) bool {
|
|
|
|
if arg == nil {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
rv := reflect.ValueOf(arg)
|
|
|
|
if rv.Kind() == reflect.Map {
|
|
|
|
for _, k := range rv.MapKeys() {
|
|
|
|
base[k.String()] = rv.MapIndex(k).Interface()
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// dict is a helper function for creating a map[string]any from a list of key-value pairs.
|
|
|
|
// If the key is dot ".", the value is merged into the base map, just like Golang template's dot syntax: dot means current
|
|
|
|
// The dot syntax is highly discouraged because it might cause unclear key conflicts. It's always good to use explicit keys.
|
|
|
|
func dict(args ...any) (map[string]any, error) {
|
|
|
|
if len(args)%2 != 0 {
|
|
|
|
return nil, fmt.Errorf("invalid dict constructor syntax: must have key-value pairs")
|
|
|
|
}
|
|
|
|
m := make(map[string]any, len(args)/2)
|
|
|
|
for i := 0; i < len(args); i += 2 {
|
|
|
|
key, ok := args[i].(string)
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("invalid dict constructor syntax: unable to merge args[%d]", i)
|
|
|
|
}
|
|
|
|
if key == "." {
|
|
|
|
if ok = dictMerge(m, args[i+1]); !ok {
|
|
|
|
return nil, fmt.Errorf("invalid dict constructor syntax: dot arg[%d] must be followed by a dict", i)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
m[key] = args[i+1]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return m, nil
|
|
|
|
}
|
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 <dev.lh@web.de>
Co-authored-by: silverwind <me@silverwind.io>
2023-04-22 19:28:20 +02:00
|
|
|
|
|
|
|
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>")
|
|
|
|
}
|