aboutsummaryrefslogtreecommitdiffstats
path: root/modules
diff options
context:
space:
mode:
authorwxiaoguang <wxiaoguang@gmail.com>2023-04-07 22:39:08 +0800
committerGitHub <noreply@github.com>2023-04-07 09:39:08 -0500
commit36c0840cf1696912b1872cfa4ebc4b241dabd693 (patch)
treec3951e2bb0f1723043bd7fdcec0ab50c19d6ef3e /modules
parent5b89670a318e52e271f65d96bfe1116d85d20988 (diff)
downloadgitea-36c0840cf1696912b1872cfa4ebc4b241dabd693.tar.gz
gitea-36c0840cf1696912b1872cfa4ebc4b241dabd693.zip
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 ......
Diffstat (limited to 'modules')
-rw-r--r--modules/templates/helper.go92
-rw-r--r--modules/templates/util.go47
-rw-r--r--modules/templates/util_test.go43
-rw-r--r--modules/util/util.go24
4 files changed, 95 insertions, 111 deletions
diff --git a/modules/templates/helper.go b/modules/templates/helper.go
index 56be050481..407b2c64f5 100644
--- a/modules/templates/helper.go
+++ b/modules/templates/helper.go
@@ -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,
}}
diff --git a/modules/templates/util.go b/modules/templates/util.go
new file mode 100644
index 0000000000..13f3a56808
--- /dev/null
+++ b/modules/templates/util.go
@@ -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
+}
diff --git a/modules/templates/util_test.go b/modules/templates/util_test.go
new file mode 100644
index 0000000000..dfa691c5e2
--- /dev/null
+++ b/modules/templates/util_test.go
@@ -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)
+ }
+}
diff --git a/modules/util/util.go b/modules/util/util.go
index 782b905bec..148817bd47 100644
--- a/modules/util/util.go
+++ b/modules/util/util.go
@@ -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))