summaryrefslogtreecommitdiffstats
path: root/modules/templates/helper.go
diff options
context:
space:
mode:
authorwxiaoguang <wxiaoguang@gmail.com>2023-04-07 21:25:49 +0800
committerGitHub <noreply@github.com>2023-04-07 21:25:49 +0800
commit5b89670a318e52e271f65d96bfe1116d85d20988 (patch)
treeef83e90b0352df1c5fbb020e84b007ffd26f7506 /modules/templates/helper.go
parentecf34fcd899fecad9782eea3097a4c38f9fe258b (diff)
downloadgitea-5b89670a318e52e271f65d96bfe1116d85d20988.tar.gz
gitea-5b89670a318e52e271f65d96bfe1116d85d20988.zip
Use a general Eval function for expressions in templates. (#23927)
One of the proposals in #23328 This PR introduces a simple expression calculator (templates/eval/eval.go), it can do basic expression calculations. Many untested template helper functions like `Mul` `Add` can be replaced by this new approach. Then these `Add` / `Mul` / `percentage` / `Subtract` / `DiffStatsWidth` could all use this `Eval`. And it provides enhancements for Golang templates, and improves readability. Some examples: ---- * Before: `{{Add (Mul $glyph.Row 12) 12}}` * After: `{{Eval $glyph.Row "*" 12 "+" 12}}` ---- * Before: `{{if lt (Add $i 1) (len $.Topics)}}` * After: `{{if Eval $i "+" 1 "<" (len $.Topics)}}` ## FAQ ### Why not use an existing expression package? We need a highly customized expression engine: * do the calculation on the fly, without pre-compiling * deal with int/int64/float64 types, to make the result could be used in Golang template. * make the syntax could be used in the Golang template directly * do not introduce too much complex or strange syntax, we just need a simple calculator. * it needs to strictly follow Golang template's behavior, for example, Golang template treats all non-zero values as truth, but many 3rd packages don't do so. ### What's the benefit? * Developers don't need to add more `Add`/`Mul`/`Sub`-like functions, they were getting more and more. Now, only one `Eval` is enough for all cases. * The new code reads better than old `{{Add (Mul $glyph.Row 12) 12}}`, the old one isn't familiar to most procedural programming developers (eg, the Golang expression syntax). * The `Eval` is fully covered by tests, many old `Add`/`Mul`-like functions were never tested. ### The performance? It doesn't use `reflect`, it doesn't need to parse or compile when used in Golang template, the performance is as fast as native Go template. ### Is it too complex? Could it be unstable? The expression calculator program is a common homework for computer science students, and it's widely used as a teaching and practicing purpose for developers. The algorithm is pretty well-known. The behavior can be clearly defined, it is stable.
Diffstat (limited to 'modules/templates/helper.go')
-rw-r--r--modules/templates/helper.go57
1 files changed, 18 insertions, 39 deletions
diff --git a/modules/templates/helper.go b/modules/templates/helper.go
index 1686e54834..56be050481 100644
--- a/modules/templates/helper.go
+++ b/modules/templates/helper.go
@@ -42,6 +42,7 @@ import (
"code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/svg"
+ "code.gitea.io/gitea/modules/templates/eval"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/gitdiff"
@@ -105,24 +106,9 @@ func NewFuncMap() []template.FuncMap {
"TimeSinceUnix": timeutil.TimeSinceUnix,
"FileSize": base.FileSize,
"LocaleNumber": LocaleNumber,
- "Subtract": base.Subtract,
"EntryIcon": base.EntryIcon,
"MigrationIcon": MigrationIcon,
- "Add": func(a ...int) int {
- sum := 0
- for _, val := range a {
- sum += val
- }
- return sum
- },
- "Mul": func(a ...int) int {
- sum := 1
- for _, val := range a {
- sum *= val
- }
- return sum
- },
- "ActionIcon": ActionIcon,
+ "ActionIcon": ActionIcon,
"DateFmtLong": func(t time.Time) string {
return t.Format(time.RFC1123Z)
},
@@ -377,7 +363,7 @@ func NewFuncMap() []template.FuncMap {
"QueryEscape": url.QueryEscape,
"DotEscape": DotEscape,
"Iterate": func(arg interface{}) (items []int64) {
- count := util.ToInt64(arg)
+ count, _ := util.ToInt64(arg)
for i := int64(0); i < count; i++ {
items = append(items, i)
}
@@ -397,6 +383,7 @@ func NewFuncMap() []template.FuncMap {
curBranch,
)
},
+ "Eval": Eval,
}}
}
@@ -472,28 +459,8 @@ func NewTextFuncMap() []texttmpl.FuncMap {
}
return dict, nil
},
- "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)
- },
- "Add": func(a ...int) int {
- sum := 0
- for _, val := range a {
- sum += val
- }
- return sum
- },
- "Mul": func(a ...int) int {
- sum := 1
- for _, val := range a {
- sum *= val
- }
- return sum
- },
"QueryEscape": url.QueryEscape,
+ "Eval": Eval,
}}
}
@@ -944,6 +911,18 @@ func mirrorRemoteAddress(ctx context.Context, m *repo_model.Repository, remoteNa
// LocaleNumber renders a number with a Custom Element, browser will render it with a locale number
func LocaleNumber(v interface{}) template.HTML {
- num := util.ToInt64(v)
+ num, _ := util.ToInt64(v)
return template.HTML(fmt.Sprintf(`<gitea-locale-number data-number="%d">%d</gitea-locale-number>`, num, num))
}
+
+// Eval the expression and return the result, see the comment of eval.Expr for details.
+// To use this helper function in templates, pass each token as a separate parameter.
+//
+// {{ $int64 := Eval $var "+" 1 }}
+// {{ $float64 := Eval $var "+" 1.0 }}
+//
+// Golang's template supports comparable int types, so the int64 result can be used in later statements like {{if lt $int64 10}}
+func Eval(tokens ...any) (any, error) {
+ n, err := eval.Expr(tokens...)
+ return n.Value, err
+}