code.gitea.io/gitea@v1.22.3/modules/templates/util_dict.go (about)

     1  // Copyright 2023 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package templates
     5  
     6  import (
     7  	"fmt"
     8  	"html"
     9  	"html/template"
    10  	"reflect"
    11  
    12  	"code.gitea.io/gitea/modules/container"
    13  	"code.gitea.io/gitea/modules/json"
    14  	"code.gitea.io/gitea/modules/setting"
    15  )
    16  
    17  func dictMerge(base map[string]any, arg any) bool {
    18  	if arg == nil {
    19  		return true
    20  	}
    21  	rv := reflect.ValueOf(arg)
    22  	if rv.Kind() == reflect.Map {
    23  		for _, k := range rv.MapKeys() {
    24  			base[k.String()] = rv.MapIndex(k).Interface()
    25  		}
    26  		return true
    27  	}
    28  	return false
    29  }
    30  
    31  // dict is a helper function for creating a map[string]any from a list of key-value pairs.
    32  // If the key is dot ".", the value is merged into the base map, just like Golang template's dot syntax: dot means current
    33  // The dot syntax is highly discouraged because it might cause unclear key conflicts. It's always good to use explicit keys.
    34  func dict(args ...any) (map[string]any, error) {
    35  	if len(args)%2 != 0 {
    36  		return nil, fmt.Errorf("invalid dict constructor syntax: must have key-value pairs")
    37  	}
    38  	m := make(map[string]any, len(args)/2)
    39  	for i := 0; i < len(args); i += 2 {
    40  		key, ok := args[i].(string)
    41  		if !ok {
    42  			return nil, fmt.Errorf("invalid dict constructor syntax: unable to merge args[%d]", i)
    43  		}
    44  		if key == "." {
    45  			if ok = dictMerge(m, args[i+1]); !ok {
    46  				return nil, fmt.Errorf("invalid dict constructor syntax: dot arg[%d] must be followed by a dict", i)
    47  			}
    48  		} else {
    49  			m[key] = args[i+1]
    50  		}
    51  	}
    52  	return m, nil
    53  }
    54  
    55  func dumpVarMarshalable(v any, dumped container.Set[uintptr]) (ret any, ok bool) {
    56  	if v == nil {
    57  		return nil, true
    58  	}
    59  	e := reflect.ValueOf(v)
    60  	for e.Kind() == reflect.Pointer {
    61  		e = e.Elem()
    62  	}
    63  	if e.CanAddr() {
    64  		addr := e.UnsafeAddr()
    65  		if !dumped.Add(addr) {
    66  			return "[dumped]", false
    67  		}
    68  		defer dumped.Remove(addr)
    69  	}
    70  	switch e.Kind() {
    71  	case reflect.Bool, reflect.String,
    72  		reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
    73  		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
    74  		reflect.Float32, reflect.Float64:
    75  		return e.Interface(), true
    76  	case reflect.Struct:
    77  		m := map[string]any{}
    78  		for i := 0; i < e.NumField(); i++ {
    79  			k := e.Type().Field(i).Name
    80  			if !e.Type().Field(i).IsExported() {
    81  				continue
    82  			}
    83  			v := e.Field(i).Interface()
    84  			m[k], _ = dumpVarMarshalable(v, dumped)
    85  		}
    86  		return m, true
    87  	case reflect.Map:
    88  		m := map[string]any{}
    89  		for _, k := range e.MapKeys() {
    90  			m[k.String()], _ = dumpVarMarshalable(e.MapIndex(k).Interface(), dumped)
    91  		}
    92  		return m, true
    93  	case reflect.Array, reflect.Slice:
    94  		var m []any
    95  		for i := 0; i < e.Len(); i++ {
    96  			v, _ := dumpVarMarshalable(e.Index(i).Interface(), dumped)
    97  			m = append(m, v)
    98  		}
    99  		return m, true
   100  	default:
   101  		return "[" + reflect.TypeOf(v).String() + "]", false
   102  	}
   103  }
   104  
   105  // dumpVar helps to dump a variable in a template, to help debugging and development.
   106  func dumpVar(v any) template.HTML {
   107  	if setting.IsProd {
   108  		return "<pre>dumpVar: only available in dev mode</pre>"
   109  	}
   110  	m, ok := dumpVarMarshalable(v, make(container.Set[uintptr]))
   111  	var dumpStr string
   112  	jsonBytes, err := json.MarshalIndent(m, "", "  ")
   113  	if err != nil {
   114  		dumpStr = fmt.Sprintf("dumpVar: unable to marshal %T: %v", v, err)
   115  	} else if ok {
   116  		dumpStr = fmt.Sprintf("dumpVar: %T\n%s", v, string(jsonBytes))
   117  	} else {
   118  		dumpStr = fmt.Sprintf("dumpVar: unmarshalable %T\n%s", v, string(jsonBytes))
   119  	}
   120  	return template.HTML("<pre>" + html.EscapeString(dumpStr) + "</pre>")
   121  }