diff options
author | wxiaoguang <wxiaoguang@gmail.com> | 2023-04-07 21:25:49 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-04-07 21:25:49 +0800 |
commit | 5b89670a318e52e271f65d96bfe1116d85d20988 (patch) | |
tree | ef83e90b0352df1c5fbb020e84b007ffd26f7506 /modules/templates/helper.go | |
parent | ecf34fcd899fecad9782eea3097a4c38f9fe258b (diff) | |
download | gitea-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.go | 57 |
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 +} |