122 lines
		
	
	
		
			3.4 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			122 lines
		
	
	
		
			3.4 KiB
		
	
	
	
		
			Go
		
	
	
	
| // Copyright 2023 The Gitea Authors. All rights reserved.
 | |
| // SPDX-License-Identifier: MIT
 | |
| 
 | |
| package templates
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"html"
 | |
| 	"html/template"
 | |
| 	"reflect"
 | |
| 
 | |
| 	"code.gitea.io/gitea/modules/container"
 | |
| 	"code.gitea.io/gitea/modules/json"
 | |
| 	"code.gitea.io/gitea/modules/setting"
 | |
| )
 | |
| 
 | |
| 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
 | |
| }
 | |
| 
 | |
| func dumpVarMarshalable(v any, dumped container.Set[uintptr]) (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.Add(addr) {
 | |
| 			return "[dumped]", false
 | |
| 		}
 | |
| 		defer dumped.Remove(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, make(container.Set[uintptr]))
 | |
| 	var dumpStr string
 | |
| 	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>")
 | |
| }
 |