122 lines
		
	
	
		
			3.3 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			122 lines
		
	
	
		
			3.3 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/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 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{})
 | 
						|
	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>")
 | 
						|
}
 |