forked from forgejo/forgejo
Merge template functions "dict/Dict/mergeinto" (#23932)
One of the steps in #23328 Before there were 3 different but similar functions: dict/Dict/mergeinto The code was just copied & pasted, no test. This PR defines a new stable `dict` function, it covers all the 3 old functions behaviors, only +160 -171 Future developers do not need to think about or guess the different dict functions, just use one: `dict` Why use `dict` but not `Dict`? Because there are far more `dict` than `Dict` in code already ......
This commit is contained in:
parent
5b89670a31
commit
36c0840cf1
16 changed files with 162 additions and 178 deletions
|
@ -8,7 +8,6 @@ import (
|
|||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html"
|
||||
"html/template"
|
||||
|
@ -219,20 +218,6 @@ func NewFuncMap() []template.FuncMap {
|
|||
"DisableImportLocal": func() bool {
|
||||
return !setting.ImportLocalPaths
|
||||
},
|
||||
"Dict": func(values ...interface{}) (map[string]interface{}, error) {
|
||||
if len(values)%2 != 0 {
|
||||
return nil, errors.New("invalid dict call")
|
||||
}
|
||||
dict := make(map[string]interface{}, len(values)/2)
|
||||
for i := 0; i < len(values); i += 2 {
|
||||
key, ok := values[i].(string)
|
||||
if !ok {
|
||||
return nil, errors.New("dict keys must be strings")
|
||||
}
|
||||
dict[key] = values[i+1]
|
||||
}
|
||||
return dict, nil
|
||||
},
|
||||
"Printf": fmt.Sprintf,
|
||||
"Escape": Escape,
|
||||
"Sec2Time": util.SecToTime,
|
||||
|
@ -242,35 +227,7 @@ func NewFuncMap() []template.FuncMap {
|
|||
"DefaultTheme": func() string {
|
||||
return setting.UI.DefaultTheme
|
||||
},
|
||||
// pass key-value pairs to a partial template which receives them as a dict
|
||||
"dict": func(values ...interface{}) (map[string]interface{}, error) {
|
||||
if len(values) == 0 {
|
||||
return nil, errors.New("invalid dict call")
|
||||
}
|
||||
|
||||
dict := make(map[string]interface{})
|
||||
return util.MergeInto(dict, values...)
|
||||
},
|
||||
/* like dict but merge key-value pairs into the first dict and return it */
|
||||
"mergeinto": func(root map[string]interface{}, values ...interface{}) (map[string]interface{}, error) {
|
||||
if len(values) == 0 {
|
||||
return nil, errors.New("invalid mergeinto call")
|
||||
}
|
||||
|
||||
dict := make(map[string]interface{})
|
||||
for key, value := range root {
|
||||
dict[key] = value
|
||||
}
|
||||
|
||||
return util.MergeInto(dict, values...)
|
||||
},
|
||||
"percentage": func(n int, values ...int) float32 {
|
||||
sum := 0
|
||||
for i := 0; i < len(values); i++ {
|
||||
sum += values[i]
|
||||
}
|
||||
return float32(n) * 100 / float32(sum)
|
||||
},
|
||||
"dict": dict,
|
||||
"CommentMustAsDiff": gitdiff.CommentMustAsDiff,
|
||||
"MirrorRemoteAddress": mirrorRemoteAddress,
|
||||
"NotificationSettings": func() map[string]interface{} {
|
||||
|
@ -413,52 +370,13 @@ func NewTextFuncMap() []texttmpl.FuncMap {
|
|||
},
|
||||
"EllipsisString": base.EllipsisString,
|
||||
"URLJoin": util.URLJoin,
|
||||
"Dict": func(values ...interface{}) (map[string]interface{}, error) {
|
||||
if len(values)%2 != 0 {
|
||||
return nil, errors.New("invalid dict call")
|
||||
}
|
||||
dict := make(map[string]interface{}, len(values)/2)
|
||||
for i := 0; i < len(values); i += 2 {
|
||||
key, ok := values[i].(string)
|
||||
if !ok {
|
||||
return nil, errors.New("dict keys must be strings")
|
||||
}
|
||||
dict[key] = values[i+1]
|
||||
}
|
||||
return dict, nil
|
||||
},
|
||||
"Printf": fmt.Sprintf,
|
||||
"Escape": Escape,
|
||||
"Sec2Time": util.SecToTime,
|
||||
"Printf": fmt.Sprintf,
|
||||
"Escape": Escape,
|
||||
"Sec2Time": util.SecToTime,
|
||||
"ParseDeadline": func(deadline string) []string {
|
||||
return strings.Split(deadline, "|")
|
||||
},
|
||||
"dict": func(values ...interface{}) (map[string]interface{}, error) {
|
||||
if len(values) == 0 {
|
||||
return nil, errors.New("invalid dict call")
|
||||
}
|
||||
|
||||
dict := make(map[string]interface{})
|
||||
|
||||
for i := 0; i < len(values); i++ {
|
||||
switch key := values[i].(type) {
|
||||
case string:
|
||||
i++
|
||||
if i == len(values) {
|
||||
return nil, errors.New("specify the key for non array values")
|
||||
}
|
||||
dict[key] = values[i]
|
||||
case map[string]interface{}:
|
||||
m := values[i].(map[string]interface{})
|
||||
for i, v := range m {
|
||||
dict[i] = v
|
||||
}
|
||||
default:
|
||||
return nil, errors.New("dict values must be maps")
|
||||
}
|
||||
}
|
||||
return dict, nil
|
||||
},
|
||||
"dict": dict,
|
||||
"QueryEscape": url.QueryEscape,
|
||||
"Eval": Eval,
|
||||
}}
|
||||
|
|
47
modules/templates/util.go
Normal file
47
modules/templates/util.go
Normal file
|
@ -0,0 +1,47 @@
|
|||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package templates
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
43
modules/templates/util_test.go
Normal file
43
modules/templates/util_test.go
Normal file
|
@ -0,0 +1,43 @@
|
|||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package templates
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestDict(t *testing.T) {
|
||||
type M map[string]any
|
||||
cases := []struct {
|
||||
args []any
|
||||
want map[string]any
|
||||
}{
|
||||
{[]any{"a", 1, "b", 2}, M{"a": 1, "b": 2}},
|
||||
{[]any{".", M{"base": 1}, "b", 2}, M{"base": 1, "b": 2}},
|
||||
{[]any{"a", 1, ".", M{"extra": 2}}, M{"a": 1, "extra": 2}},
|
||||
{[]any{"a", 1, ".", map[string]int{"int": 2}}, M{"a": 1, "int": 2}},
|
||||
{[]any{".", nil, "b", 2}, M{"b": 2}},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
got, err := dict(c.args...)
|
||||
if assert.NoError(t, err) {
|
||||
assert.EqualValues(t, c.want, got)
|
||||
}
|
||||
}
|
||||
|
||||
bads := []struct {
|
||||
args []any
|
||||
}{
|
||||
{[]any{"a", 1, "b"}},
|
||||
{[]any{1}},
|
||||
{[]any{struct{}{}}},
|
||||
}
|
||||
for _, c := range bads {
|
||||
_, err := dict(c.args...)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
}
|
|
@ -6,7 +6,6 @@ package util
|
|||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strconv"
|
||||
|
@ -117,29 +116,6 @@ func NormalizeEOL(input []byte) []byte {
|
|||
return tmp[:pos]
|
||||
}
|
||||
|
||||
// MergeInto merges pairs of values into a "dict"
|
||||
func MergeInto(dict map[string]interface{}, values ...interface{}) (map[string]interface{}, error) {
|
||||
for i := 0; i < len(values); i++ {
|
||||
switch key := values[i].(type) {
|
||||
case string:
|
||||
i++
|
||||
if i == len(values) {
|
||||
return nil, errors.New("specify the key for non array values")
|
||||
}
|
||||
dict[key] = values[i]
|
||||
case map[string]interface{}:
|
||||
m := values[i].(map[string]interface{})
|
||||
for i, v := range m {
|
||||
dict[i] = v
|
||||
}
|
||||
default:
|
||||
return nil, errors.New("dict values must be maps")
|
||||
}
|
||||
}
|
||||
|
||||
return dict, nil
|
||||
}
|
||||
|
||||
// CryptoRandomInt returns a crypto random integer between 0 and limit, inclusive
|
||||
func CryptoRandomInt(limit int64) (int64, error) {
|
||||
rInt, err := rand.Int(rand.Reader, big.NewInt(limit))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue