Skip to content

Commit 36c0840

Browse files
authored
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 ......
1 parent 5b89670 commit 36c0840

16 files changed

+162
-178
lines changed

modules/templates/helper.go

Lines changed: 5 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"bytes"
99
"context"
1010
"encoding/hex"
11-
"errors"
1211
"fmt"
1312
"html"
1413
"html/template"
@@ -219,20 +218,6 @@ func NewFuncMap() []template.FuncMap {
219218
"DisableImportLocal": func() bool {
220219
return !setting.ImportLocalPaths
221220
},
222-
"Dict": func(values ...interface{}) (map[string]interface{}, error) {
223-
if len(values)%2 != 0 {
224-
return nil, errors.New("invalid dict call")
225-
}
226-
dict := make(map[string]interface{}, len(values)/2)
227-
for i := 0; i < len(values); i += 2 {
228-
key, ok := values[i].(string)
229-
if !ok {
230-
return nil, errors.New("dict keys must be strings")
231-
}
232-
dict[key] = values[i+1]
233-
}
234-
return dict, nil
235-
},
236221
"Printf": fmt.Sprintf,
237222
"Escape": Escape,
238223
"Sec2Time": util.SecToTime,
@@ -242,35 +227,7 @@ func NewFuncMap() []template.FuncMap {
242227
"DefaultTheme": func() string {
243228
return setting.UI.DefaultTheme
244229
},
245-
// pass key-value pairs to a partial template which receives them as a dict
246-
"dict": func(values ...interface{}) (map[string]interface{}, error) {
247-
if len(values) == 0 {
248-
return nil, errors.New("invalid dict call")
249-
}
250-
251-
dict := make(map[string]interface{})
252-
return util.MergeInto(dict, values...)
253-
},
254-
/* like dict but merge key-value pairs into the first dict and return it */
255-
"mergeinto": func(root map[string]interface{}, values ...interface{}) (map[string]interface{}, error) {
256-
if len(values) == 0 {
257-
return nil, errors.New("invalid mergeinto call")
258-
}
259-
260-
dict := make(map[string]interface{})
261-
for key, value := range root {
262-
dict[key] = value
263-
}
264-
265-
return util.MergeInto(dict, values...)
266-
},
267-
"percentage": func(n int, values ...int) float32 {
268-
sum := 0
269-
for i := 0; i < len(values); i++ {
270-
sum += values[i]
271-
}
272-
return float32(n) * 100 / float32(sum)
273-
},
230+
"dict": dict,
274231
"CommentMustAsDiff": gitdiff.CommentMustAsDiff,
275232
"MirrorRemoteAddress": mirrorRemoteAddress,
276233
"NotificationSettings": func() map[string]interface{} {
@@ -413,52 +370,13 @@ func NewTextFuncMap() []texttmpl.FuncMap {
413370
},
414371
"EllipsisString": base.EllipsisString,
415372
"URLJoin": util.URLJoin,
416-
"Dict": func(values ...interface{}) (map[string]interface{}, error) {
417-
if len(values)%2 != 0 {
418-
return nil, errors.New("invalid dict call")
419-
}
420-
dict := make(map[string]interface{}, len(values)/2)
421-
for i := 0; i < len(values); i += 2 {
422-
key, ok := values[i].(string)
423-
if !ok {
424-
return nil, errors.New("dict keys must be strings")
425-
}
426-
dict[key] = values[i+1]
427-
}
428-
return dict, nil
429-
},
430-
"Printf": fmt.Sprintf,
431-
"Escape": Escape,
432-
"Sec2Time": util.SecToTime,
373+
"Printf": fmt.Sprintf,
374+
"Escape": Escape,
375+
"Sec2Time": util.SecToTime,
433376
"ParseDeadline": func(deadline string) []string {
434377
return strings.Split(deadline, "|")
435378
},
436-
"dict": func(values ...interface{}) (map[string]interface{}, error) {
437-
if len(values) == 0 {
438-
return nil, errors.New("invalid dict call")
439-
}
440-
441-
dict := make(map[string]interface{})
442-
443-
for i := 0; i < len(values); i++ {
444-
switch key := values[i].(type) {
445-
case string:
446-
i++
447-
if i == len(values) {
448-
return nil, errors.New("specify the key for non array values")
449-
}
450-
dict[key] = values[i]
451-
case map[string]interface{}:
452-
m := values[i].(map[string]interface{})
453-
for i, v := range m {
454-
dict[i] = v
455-
}
456-
default:
457-
return nil, errors.New("dict values must be maps")
458-
}
459-
}
460-
return dict, nil
461-
},
379+
"dict": dict,
462380
"QueryEscape": url.QueryEscape,
463381
"Eval": Eval,
464382
}}

modules/templates/util.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright 2023 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package templates
5+
6+
import (
7+
"fmt"
8+
"reflect"
9+
)
10+
11+
func dictMerge(base map[string]any, arg any) bool {
12+
if arg == nil {
13+
return true
14+
}
15+
rv := reflect.ValueOf(arg)
16+
if rv.Kind() == reflect.Map {
17+
for _, k := range rv.MapKeys() {
18+
base[k.String()] = rv.MapIndex(k).Interface()
19+
}
20+
return true
21+
}
22+
return false
23+
}
24+
25+
// dict is a helper function for creating a map[string]any from a list of key-value pairs.
26+
// If the key is dot ".", the value is merged into the base map, just like Golang template's dot syntax: dot means current
27+
// The dot syntax is highly discouraged because it might cause unclear key conflicts. It's always good to use explicit keys.
28+
func dict(args ...any) (map[string]any, error) {
29+
if len(args)%2 != 0 {
30+
return nil, fmt.Errorf("invalid dict constructor syntax: must have key-value pairs")
31+
}
32+
m := make(map[string]any, len(args)/2)
33+
for i := 0; i < len(args); i += 2 {
34+
key, ok := args[i].(string)
35+
if !ok {
36+
return nil, fmt.Errorf("invalid dict constructor syntax: unable to merge args[%d]", i)
37+
}
38+
if key == "." {
39+
if ok = dictMerge(m, args[i+1]); !ok {
40+
return nil, fmt.Errorf("invalid dict constructor syntax: dot arg[%d] must be followed by a dict", i)
41+
}
42+
} else {
43+
m[key] = args[i+1]
44+
}
45+
}
46+
return m, nil
47+
}

modules/templates/util_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright 2023 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package templates
5+
6+
import (
7+
"testing"
8+
9+
"github.com/stretchr/testify/assert"
10+
)
11+
12+
func TestDict(t *testing.T) {
13+
type M map[string]any
14+
cases := []struct {
15+
args []any
16+
want map[string]any
17+
}{
18+
{[]any{"a", 1, "b", 2}, M{"a": 1, "b": 2}},
19+
{[]any{".", M{"base": 1}, "b", 2}, M{"base": 1, "b": 2}},
20+
{[]any{"a", 1, ".", M{"extra": 2}}, M{"a": 1, "extra": 2}},
21+
{[]any{"a", 1, ".", map[string]int{"int": 2}}, M{"a": 1, "int": 2}},
22+
{[]any{".", nil, "b", 2}, M{"b": 2}},
23+
}
24+
25+
for _, c := range cases {
26+
got, err := dict(c.args...)
27+
if assert.NoError(t, err) {
28+
assert.EqualValues(t, c.want, got)
29+
}
30+
}
31+
32+
bads := []struct {
33+
args []any
34+
}{
35+
{[]any{"a", 1, "b"}},
36+
{[]any{1}},
37+
{[]any{struct{}{}}},
38+
}
39+
for _, c := range bads {
40+
_, err := dict(c.args...)
41+
assert.Error(t, err)
42+
}
43+
}

modules/util/util.go

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ package util
66
import (
77
"bytes"
88
"crypto/rand"
9-
"errors"
109
"fmt"
1110
"math/big"
1211
"strconv"
@@ -117,29 +116,6 @@ func NormalizeEOL(input []byte) []byte {
117116
return tmp[:pos]
118117
}
119118

120-
// MergeInto merges pairs of values into a "dict"
121-
func MergeInto(dict map[string]interface{}, values ...interface{}) (map[string]interface{}, error) {
122-
for i := 0; i < len(values); i++ {
123-
switch key := values[i].(type) {
124-
case string:
125-
i++
126-
if i == len(values) {
127-
return nil, errors.New("specify the key for non array values")
128-
}
129-
dict[key] = values[i]
130-
case map[string]interface{}:
131-
m := values[i].(map[string]interface{})
132-
for i, v := range m {
133-
dict[i] = v
134-
}
135-
default:
136-
return nil, errors.New("dict values must be maps")
137-
}
138-
}
139-
140-
return dict, nil
141-
}
142-
143119
// CryptoRandomInt returns a crypto random integer between 0 and limit, inclusive
144120
func CryptoRandomInt(limit int64) (int64, error) {
145121
rInt, err := rand.Int(rand.Reader, big.NewInt(limit))

templates/org/team/teams.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
</div>
3333
<div class="ui attached segment members">
3434
{{range .Members}}
35-
{{template "shared/user/avatarlink" Dict "Context" $.Context "user" .}}
35+
{{template "shared/user/avatarlink" dict "Context" $.Context "user" .}}
3636
{{end}}
3737
</div>
3838
<div class="ui bottom attached header">

templates/repo/diff/comments.tmpl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
{{if .OriginalAuthor}}
66
<span class="avatar"><img src="{{AppSubUrl}}/assets/img/avatar_default.png"></span>
77
{{else}}
8-
{{template "shared/user/avatarlink" Dict "Context" $.root.Context "user" .Poster}}
8+
{{template "shared/user/avatarlink" dict "Context" $.root.Context "user" .Poster}}
99
{{end}}
1010
<div class="content comment-container">
1111
<div class="ui top attached header comment-header gt-df gt-ac gt-sb">
@@ -42,8 +42,8 @@
4242
</div>
4343
{{end}}
4444
{{end}}
45-
{{template "repo/issue/view_content/add_reaction" Dict "ctxData" $.root "ActionURL" (Printf "%s/comments/%d/reactions" $.root.RepoLink .ID)}}
46-
{{template "repo/issue/view_content/context_menu" Dict "ctxData" $.root "item" . "delete" true "issue" false "diff" true "IsCommentPoster" (and $.root.IsSigned (eq $.root.SignedUserID .PosterID))}}
45+
{{template "repo/issue/view_content/add_reaction" dict "ctxData" $.root "ActionURL" (Printf "%s/comments/%d/reactions" $.root.RepoLink .ID)}}
46+
{{template "repo/issue/view_content/context_menu" dict "ctxData" $.root "item" . "delete" true "issue" false "diff" true "IsCommentPoster" (and $.root.IsSigned (eq $.root.SignedUserID .PosterID))}}
4747
</div>
4848
</div>
4949
<div class="ui attached segment comment-body">
@@ -60,7 +60,7 @@
6060
{{$reactions := .Reactions.GroupByType}}
6161
{{if $reactions}}
6262
<div class="ui attached segment reactions">
63-
{{template "repo/issue/view_content/reactions" Dict "ctxData" $.root "ActionURL" (Printf "%s/comments/%d/reactions" $.root.RepoLink .ID) "Reactions" $reactions}}
63+
{{template "repo/issue/view_content/reactions" dict "ctxData" $.root "ActionURL" (Printf "%s/comments/%d/reactions" $.root.RepoLink .ID) "Reactions" $reactions}}
6464
</div>
6565
{{end}}
6666
</div>

templates/repo/diff/section_split.tmpl

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -111,22 +111,22 @@
111111
<td class="add-comment-left" colspan="4">
112112
{{if gt (len $line.Comments) 0}}
113113
{{if eq $line.GetCommentSide "previous"}}
114-
{{template "repo/diff/conversation" mergeinto $.root "comments" $line.Comments}}
114+
{{template "repo/diff/conversation" dict "." $.root "comments" $line.Comments}}
115115
{{end}}
116116
{{end}}
117117
{{if gt (len $match.Comments) 0}}
118118
{{if eq $match.GetCommentSide "previous"}}
119-
{{template "repo/diff/conversation" mergeinto $.root "comments" $match.Comments}}
119+
{{template "repo/diff/conversation" dict "." $.root "comments" $match.Comments}}
120120
{{end}}
121121
{{end}}
122122
</td>
123123
<td class="add-comment-right" colspan="4">
124124
{{if eq $line.GetCommentSide "proposed"}}
125-
{{template "repo/diff/conversation" mergeinto $.root "comments" $line.Comments}}
125+
{{template "repo/diff/conversation" dict "." $.root "comments" $line.Comments}}
126126
{{end}}
127127
{{if gt (len $match.Comments) 0}}
128128
{{if eq $match.GetCommentSide "proposed"}}
129-
{{template "repo/diff/conversation" mergeinto $.root "comments" $match.Comments}}
129+
{{template "repo/diff/conversation" dict "." $.root "comments" $match.Comments}}
130130
{{end}}
131131
{{end}}
132132
</td>
@@ -137,13 +137,13 @@
137137
<td class="add-comment-left" colspan="4">
138138
{{if gt (len $line.Comments) 0}}
139139
{{if eq $line.GetCommentSide "previous"}}
140-
{{template "repo/diff/conversation" mergeinto $.root "comments" $line.Comments}}
140+
{{template "repo/diff/conversation" dict "." $.root "comments" $line.Comments}}
141141
{{end}}
142142
{{end}}
143143
</td>
144144
<td class="add-comment-right" colspan="4">
145145
{{if eq $line.GetCommentSide "proposed"}}
146-
{{template "repo/diff/conversation" mergeinto $.root "comments" $line.Comments}}
146+
{{template "repo/diff/conversation" dict "." $.root "comments" $line.Comments}}
147147
{{end}}
148148
</td>
149149
</tr>

templates/repo/diff/section_unified.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
{{if gt (len $line.Comments) 0}}
5858
<tr class="add-comment" data-line-type="{{DiffLineTypeToStr .GetType}}">
5959
<td class="add-comment-left add-comment-right" colspan="5">
60-
{{template "repo/diff/conversation" mergeinto $.root "comments" $line.Comments}}
60+
{{template "repo/diff/conversation" dict "." $.root "comments" $line.Comments}}
6161
</td>
6262
</tr>
6363
{{end}}

templates/repo/issue/list.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,7 @@
307307
</div>
308308
</div>
309309
</div>
310-
{{template "shared/issuelist" mergeinto . "listType" "repo"}}
310+
{{template "shared/issuelist" dict "." . "listType" "repo"}}
311311
</div>
312312
</div>
313313
{{template "base/footer" .}}

templates/repo/issue/milestone_issues.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@
198198
</div>
199199
</div>
200200
</div>
201-
{{template "shared/issuelist" mergeinto . "listType" "milestone"}}
201+
{{template "shared/issuelist" dict "." . "listType" "milestone"}}
202202
</div>
203203
</div>
204204
{{template "base/footer" .}}

templates/repo/issue/new_form.tmpl

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<div class="twelve wide column">
99
<div class="ui comments">
1010
<div class="comment">
11-
{{template "shared/user/avatarlink" Dict "Context" $.Context "user" .SignedUser}}
11+
{{template "shared/user/avatarlink" dict "Context" $.Context "user" .SignedUser}}
1212
<div class="ui segment content">
1313
<div class="field">
1414
<input name="title" id="issue_title" placeholder="{{.locale.Tr "repo.milestones.title"}}" value="{{if .TitleQuery}}{{.TitleQuery}}{{else if .IssueTemplateTitle}}{{.IssueTemplateTitle}}{{else}}{{.title}}{{end}}" tabindex="3" autofocus required maxlength="255" autocomplete="off">
@@ -20,15 +20,15 @@
2020
<input type="hidden" name="template-file" value="{{.TemplateFile}}">
2121
{{range .Fields}}
2222
{{if eq .Type "input"}}
23-
{{template "repo/issue/fields/input" Dict "Context" $.Context "item" .}}
23+
{{template "repo/issue/fields/input" dict "Context" $.Context "item" .}}
2424
{{else if eq .Type "markdown"}}
25-
{{template "repo/issue/fields/markdown" Dict "Context" $.Context "item" .}}
25+
{{template "repo/issue/fields/markdown" dict "Context" $.Context "item" .}}
2626
{{else if eq .Type "textarea"}}
27-
{{template "repo/issue/fields/textarea" Dict "Context" $.Context "item" .}}
27+
{{template "repo/issue/fields/textarea" dict "Context" $.Context "item" .}}
2828
{{else if eq .Type "dropdown"}}
29-
{{template "repo/issue/fields/dropdown" Dict "Context" $.Context "item" .}}
29+
{{template "repo/issue/fields/dropdown" dict "Context" $.Context "item" .}}
3030
{{else if eq .Type "checkboxes"}}
31-
{{template "repo/issue/fields/checkboxes" Dict "Context" $.Context "item" .}}
31+
{{template "repo/issue/fields/checkboxes" dict "Context" $.Context "item" .}}
3232
{{end}}
3333
{{end}}
3434
{{if .IsAttachmentEnabled}}

0 commit comments

Comments
 (0)