2024-05-24 11:28:15 +00:00
|
|
|
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
2018-09-29 08:33:54 +00:00
|
|
|
// Copyright 2018 The Gitea Authors. All rights reserved.
|
2014-04-10 18:20:58 +00:00
|
|
|
// Copyright 2014 The Gogs Authors. All rights reserved.
|
2022-11-27 18:20:29 +00:00
|
|
|
// SPDX-License-Identifier: MIT
|
2014-04-10 18:20:58 +00:00
|
|
|
|
2016-12-06 17:58:31 +00:00
|
|
|
package templates
|
2014-04-10 18:20:58 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2018-02-27 07:09:18 +00:00
|
|
|
"html"
|
2014-04-10 18:20:58 +00:00
|
|
|
"html/template"
|
2017-11-28 09:43:51 +00:00
|
|
|
"net/url"
|
2024-02-25 10:45:56 +00:00
|
|
|
"slices"
|
2014-04-10 18:20:58 +00:00
|
|
|
"strings"
|
|
|
|
"time"
|
2014-05-26 00:11:25 +00:00
|
|
|
|
2023-10-06 07:46:36 +00:00
|
|
|
user_model "code.gitea.io/gitea/models/user"
|
2016-11-10 16:24:48 +00:00
|
|
|
"code.gitea.io/gitea/modules/base"
|
2017-09-16 17:17:57 +00:00
|
|
|
"code.gitea.io/gitea/modules/markup"
|
2016-11-10 16:24:48 +00:00
|
|
|
"code.gitea.io/gitea/modules/setting"
|
2020-07-12 09:10:56 +00:00
|
|
|
"code.gitea.io/gitea/modules/svg"
|
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.
2023-04-07 13:25:49 +00:00
|
|
|
"code.gitea.io/gitea/modules/templates/eval"
|
2019-08-15 14:46:21 +00:00
|
|
|
"code.gitea.io/gitea/modules/timeutil"
|
|
|
|
"code.gitea.io/gitea/modules/util"
|
2019-09-06 02:20:09 +00:00
|
|
|
"code.gitea.io/gitea/services/gitdiff"
|
2014-04-10 18:20:58 +00:00
|
|
|
)
|
|
|
|
|
2016-11-25 06:23:48 +00:00
|
|
|
// NewFuncMap returns functions for injecting to templates
|
2023-04-30 12:22:23 +00:00
|
|
|
func NewFuncMap() template.FuncMap {
|
2023-07-04 18:36:08 +00:00
|
|
|
return map[string]any{
|
2023-08-08 01:22:47 +00:00
|
|
|
"ctx": func() any { return nil }, // template context function
|
|
|
|
|
2023-04-29 12:02:29 +00:00
|
|
|
"DumpVar": dumpVar,
|
|
|
|
|
2023-04-08 13:15:22 +00:00
|
|
|
// -----------------------------------------------------------------
|
|
|
|
// html/template related functions
|
2024-03-01 10:16:19 +00:00
|
|
|
"dict": dict, // it's lowercase because this name has been widely used. Our other functions should have uppercase names.
|
|
|
|
"Eval": Eval,
|
|
|
|
"SafeHTML": SafeHTML,
|
|
|
|
"HTMLFormat": HTMLFormat,
|
|
|
|
"HTMLEscape": HTMLEscape,
|
2024-03-13 13:32:30 +00:00
|
|
|
"QueryEscape": QueryEscape,
|
2024-03-01 10:16:19 +00:00
|
|
|
"JSEscape": JSEscapeSafe,
|
|
|
|
"SanitizeHTML": SanitizeHTML,
|
|
|
|
"URLJoin": util.URLJoin,
|
|
|
|
"DotEscape": DotEscape,
|
2023-04-08 13:15:22 +00:00
|
|
|
|
|
|
|
"PathEscape": url.PathEscape,
|
|
|
|
"PathEscapeSegments": util.PathEscapeSegments,
|
|
|
|
|
2023-04-22 18:16:22 +00:00
|
|
|
// utils
|
|
|
|
"StringUtils": NewStringUtils,
|
|
|
|
"SliceUtils": NewSliceUtils,
|
2023-04-29 12:02:29 +00:00
|
|
|
"JsonUtils": NewJsonUtils,
|
2023-04-08 13:15:22 +00:00
|
|
|
|
|
|
|
// -----------------------------------------------------------------
|
2024-04-07 16:19:25 +00:00
|
|
|
// svg / avatar / icon / color
|
2023-08-10 03:19:39 +00:00
|
|
|
"svg": svg.RenderHTML,
|
|
|
|
"EntryIcon": base.EntryIcon,
|
|
|
|
"MigrationIcon": MigrationIcon,
|
|
|
|
"ActionIcon": ActionIcon,
|
2024-04-07 16:19:25 +00:00
|
|
|
"SortArrow": SortArrow,
|
|
|
|
"ContrastColor": util.ContrastColor,
|
2023-04-08 13:15:22 +00:00
|
|
|
|
|
|
|
// -----------------------------------------------------------------
|
|
|
|
// time / number / format
|
2024-03-19 13:47:53 +00:00
|
|
|
"FileSize": FileSizePanic,
|
2023-04-08 13:15:22 +00:00
|
|
|
"CountFmt": base.FormatNumberSI,
|
|
|
|
"TimeSince": timeutil.TimeSince,
|
|
|
|
"TimeSinceUnix": timeutil.TimeSinceUnix,
|
2023-04-23 19:12:33 +00:00
|
|
|
"DateTime": timeutil.DateTime,
|
2023-04-08 13:15:22 +00:00
|
|
|
"Sec2Time": util.SecToTime,
|
|
|
|
"LoadTimes": func(startTime time.Time) string {
|
|
|
|
return fmt.Sprint(time.Since(startTime).Nanoseconds()/1e6) + "ms"
|
|
|
|
},
|
|
|
|
|
|
|
|
// -----------------------------------------------------------------
|
|
|
|
// setting
|
2016-03-06 21:40:04 +00:00
|
|
|
"AppName": func() string {
|
|
|
|
return setting.AppName
|
|
|
|
},
|
|
|
|
"AppSubUrl": func() string {
|
2016-11-27 10:14:25 +00:00
|
|
|
return setting.AppSubURL
|
2016-03-06 21:40:04 +00:00
|
|
|
},
|
2021-05-08 14:27:25 +00:00
|
|
|
"AssetUrlPrefix": func() string {
|
2021-04-28 12:35:06 +00:00
|
|
|
return setting.StaticURLPrefix + "/assets"
|
2019-10-22 12:11:01 +00:00
|
|
|
},
|
2016-03-06 21:40:04 +00:00
|
|
|
"AppUrl": func() string {
|
2023-02-09 16:31:30 +00:00
|
|
|
// The usage of AppUrl should be avoided as much as possible,
|
|
|
|
// because the AppURL(ROOT_URL) may not match user's visiting site and the ROOT_URL in app.ini may be incorrect.
|
|
|
|
// And it's difficult for Gitea to guess absolute URL correctly with zero configuration,
|
|
|
|
// because Gitea doesn't know whether the scheme is HTTP or HTTPS unless the reverse proxy could tell Gitea.
|
2016-11-27 10:14:25 +00:00
|
|
|
return setting.AppURL
|
2016-03-06 21:40:04 +00:00
|
|
|
},
|
|
|
|
"AppVer": func() string {
|
|
|
|
return setting.AppVer
|
|
|
|
},
|
2023-04-07 07:31:41 +00:00
|
|
|
"AppDomain": func() string { // documented in mail-templates.md
|
2016-03-06 21:40:04 +00:00
|
|
|
return setting.Domain
|
|
|
|
},
|
[FEAT] Repository flags
This implements "repository flags", a way for instance administrators to
assign custom flags to repositories. The idea is that custom templates
can look at these flags, and display banners based on them, Forgejo does
not provide anything built on top of it, just the foundation. The
feature is optional, and disabled by default. To enable it, set
`[repository].ENABLE_FLAGS = true`.
On the UI side, instance administrators will see a new "Manage flags"
tab on repositories, and a list of enabled tags (if any) on the
repository home page. The "Manage flags" page allows them to remove
existing flags, or add any new ones that are listed in
`[repository].SETTABLE_FLAGS`.
The model does not enforce that only the `SETTABLE_FLAGS` are present.
If the setting is changed, old flags may remain present in the database,
and anything that uses them, will still work. The repository flag
management page will allow an instance administrator to remove them, but
not set them, once removed.
Signed-off-by: Gergely Nagy <forgejo@gergo.csillger.hu>
(cherry picked from commit ba735ce2228f8dd7ca105e94b9baa1be058ebe37)
(cherry picked from commit f09f6e029b4fb2714b86cd32dc19255078ecc0ee)
(cherry picked from commit 2f8b0414892f6099f519bda63a9e0fbc8ba6cfc7)
(cherry picked from commit d3186ee5f41fac896c7d2341402fcd39dd250bf1)
2024-01-04 13:28:19 +00:00
|
|
|
"RepoFlagsEnabled": func() bool {
|
|
|
|
return setting.Repository.EnableFlags
|
|
|
|
},
|
2022-08-23 12:58:04 +00:00
|
|
|
"AssetVersion": func() string {
|
|
|
|
return setting.AssetVersion
|
|
|
|
},
|
2019-05-08 08:41:35 +00:00
|
|
|
"DefaultShowFullName": func() bool {
|
|
|
|
return setting.UI.DefaultShowFullName
|
|
|
|
},
|
2016-09-01 05:01:32 +00:00
|
|
|
"ShowFooterTemplateLoadTime": func() bool {
|
2023-04-22 23:38:25 +00:00
|
|
|
return setting.Other.ShowFooterTemplateLoadTime
|
2016-09-01 05:01:32 +00:00
|
|
|
},
|
2024-04-03 16:01:50 +00:00
|
|
|
"ShowFooterPoweredBy": func() bool {
|
|
|
|
return setting.Other.ShowFooterPoweredBy
|
|
|
|
},
|
2019-12-27 23:43:56 +00:00
|
|
|
"AllowedReactions": func() []string {
|
|
|
|
return setting.UI.Reactions
|
|
|
|
},
|
2021-06-29 14:28:38 +00:00
|
|
|
"CustomEmojis": func() map[string]string {
|
|
|
|
return setting.UI.CustomEmojisMap
|
|
|
|
},
|
2017-04-01 01:03:01 +00:00
|
|
|
"MetaAuthor": func() string {
|
|
|
|
return setting.UI.Meta.Author
|
|
|
|
},
|
|
|
|
"MetaDescription": func() string {
|
|
|
|
return setting.UI.Meta.Description
|
|
|
|
},
|
|
|
|
"MetaKeywords": func() string {
|
|
|
|
return setting.UI.Meta.Keywords
|
|
|
|
},
|
2021-02-19 23:06:56 +00:00
|
|
|
"EnableTimetracking": func() bool {
|
|
|
|
return setting.Service.EnableTimetracking
|
|
|
|
},
|
2017-09-12 09:25:42 +00:00
|
|
|
"DisableGitHooks": func() bool {
|
|
|
|
return setting.DisableGitHooks
|
|
|
|
},
|
2021-02-11 17:34:34 +00:00
|
|
|
"DisableWebhooks": func() bool {
|
|
|
|
return setting.DisableWebhooks
|
|
|
|
},
|
2018-08-24 05:00:22 +00:00
|
|
|
"DisableImportLocal": func() bool {
|
|
|
|
return !setting.ImportLocalPaths
|
|
|
|
},
|
2023-10-06 07:46:36 +00:00
|
|
|
"ThemeName": func(user *user_model.User) string {
|
|
|
|
if user == nil || user.Theme == "" {
|
|
|
|
return setting.UI.DefaultTheme
|
|
|
|
}
|
|
|
|
return user.Theme
|
2018-07-05 21:25:04 +00:00
|
|
|
},
|
2023-07-04 18:36:08 +00:00
|
|
|
"NotificationSettings": func() map[string]any {
|
|
|
|
return map[string]any{
|
2020-05-07 21:49:00 +00:00
|
|
|
"MinTimeout": int(setting.UI.Notification.MinTimeout / time.Millisecond),
|
|
|
|
"TimeoutStep": int(setting.UI.Notification.TimeoutStep / time.Millisecond),
|
|
|
|
"MaxTimeout": int(setting.UI.Notification.MaxTimeout / time.Millisecond),
|
|
|
|
"EventSourceUpdateTime": int(setting.UI.Notification.EventSourceUpdateTime / time.Millisecond),
|
2020-04-24 03:57:38 +00:00
|
|
|
}
|
|
|
|
},
|
2023-04-08 13:15:22 +00:00
|
|
|
"MermaidMaxSourceCharacters": func() int {
|
|
|
|
return setting.MermaidMaxSourceCharacters
|
|
|
|
},
|
2024-05-24 11:28:15 +00:00
|
|
|
"FederationEnabled": func() bool {
|
|
|
|
return setting.Federation.Enabled
|
|
|
|
},
|
2020-11-08 17:21:54 +00:00
|
|
|
|
2023-04-08 13:15:22 +00:00
|
|
|
// -----------------------------------------------------------------
|
|
|
|
// render
|
|
|
|
"RenderCommitMessage": RenderCommitMessage,
|
|
|
|
"RenderCommitMessageLinkSubject": RenderCommitMessageLinkSubject,
|
2020-11-08 17:21:54 +00:00
|
|
|
|
2023-04-08 13:15:22 +00:00
|
|
|
"RenderCommitBody": RenderCommitBody,
|
|
|
|
"RenderCodeBlock": RenderCodeBlock,
|
|
|
|
"RenderIssueTitle": RenderIssueTitle,
|
|
|
|
"RenderEmoji": RenderEmoji,
|
|
|
|
"ReactionToEmoji": ReactionToEmoji,
|
2020-06-24 22:23:05 +00:00
|
|
|
|
2023-04-29 12:02:29 +00:00
|
|
|
"RenderMarkdownToHtml": RenderMarkdownToHtml,
|
|
|
|
"RenderLabel": RenderLabel,
|
|
|
|
"RenderLabels": RenderLabels,
|
2023-04-08 13:15:22 +00:00
|
|
|
|
|
|
|
// -----------------------------------------------------------------
|
|
|
|
// misc
|
|
|
|
"ShortSha": base.ShortSha,
|
|
|
|
"ActionContent2Commits": ActionContent2Commits,
|
|
|
|
"IsMultilineCommitMessage": IsMultilineCommitMessage,
|
|
|
|
"CommentMustAsDiff": gitdiff.CommentMustAsDiff,
|
|
|
|
"MirrorRemoteAddress": mirrorRemoteAddress,
|
|
|
|
|
2023-04-29 12:02:29 +00:00
|
|
|
"FilenameIsImage": FilenameIsImage,
|
|
|
|
"TabSizeClass": TabSizeClass,
|
2023-04-30 12:22:23 +00:00
|
|
|
}
|
2019-11-07 13:34:28 +00:00
|
|
|
}
|
|
|
|
|
2024-02-25 10:45:56 +00:00
|
|
|
func HTMLFormat(s string, rawArgs ...any) template.HTML {
|
|
|
|
args := slices.Clone(rawArgs)
|
|
|
|
for i, v := range args {
|
|
|
|
switch v := v.(type) {
|
|
|
|
case nil, bool, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, template.HTML:
|
|
|
|
// for most basic types (including template.HTML which is safe), just do nothing and use it
|
|
|
|
case string:
|
|
|
|
args[i] = template.HTMLEscapeString(v)
|
|
|
|
case fmt.Stringer:
|
|
|
|
args[i] = template.HTMLEscapeString(v.String())
|
|
|
|
default:
|
|
|
|
args[i] = template.HTMLEscapeString(fmt.Sprint(v))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return template.HTML(fmt.Sprintf(s, args...))
|
|
|
|
}
|
|
|
|
|
|
|
|
// SafeHTML render raw as HTML
|
|
|
|
func SafeHTML(s any) template.HTML {
|
2024-02-14 21:48:45 +00:00
|
|
|
switch v := s.(type) {
|
|
|
|
case string:
|
|
|
|
return template.HTML(v)
|
|
|
|
case template.HTML:
|
|
|
|
return v
|
|
|
|
}
|
|
|
|
panic(fmt.Sprintf("unexpected type %T", s))
|
|
|
|
}
|
|
|
|
|
2024-03-01 10:16:19 +00:00
|
|
|
// SanitizeHTML sanitizes the input by pre-defined markdown rules
|
2024-03-04 12:02:45 +00:00
|
|
|
func SanitizeHTML(s string) template.HTML {
|
|
|
|
return template.HTML(markup.Sanitize(s))
|
2015-08-08 09:10:34 +00:00
|
|
|
}
|
|
|
|
|
2024-02-25 14:02:20 +00:00
|
|
|
func HTMLEscape(s any) template.HTML {
|
2024-02-14 21:48:45 +00:00
|
|
|
switch v := s.(type) {
|
|
|
|
case string:
|
|
|
|
return template.HTML(html.EscapeString(v))
|
|
|
|
case template.HTML:
|
|
|
|
return v
|
|
|
|
}
|
|
|
|
panic(fmt.Sprintf("unexpected type %T", s))
|
|
|
|
}
|
|
|
|
|
2024-02-18 09:52:02 +00:00
|
|
|
func JSEscapeSafe(s string) template.HTML {
|
|
|
|
return template.HTML(template.JSEscapeString(s))
|
|
|
|
}
|
|
|
|
|
2024-03-13 13:32:30 +00:00
|
|
|
func QueryEscape(s string) template.URL {
|
|
|
|
return template.URL(url.QueryEscape(s))
|
|
|
|
}
|
|
|
|
|
2022-03-23 12:34:20 +00:00
|
|
|
// DotEscape wraps a dots in names with ZWJ [U+200D] in order to prevent autolinkers from detecting these as urls
|
|
|
|
func DotEscape(raw string) string {
|
|
|
|
return strings.ReplaceAll(raw, ".", "\u200d.\u200d")
|
|
|
|
}
|
|
|
|
|
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.
2023-04-07 13:25:49 +00:00
|
|
|
// 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
|
|
|
|
}
|
2024-03-19 13:47:53 +00:00
|
|
|
|
|
|
|
func FileSizePanic(s int64) string {
|
|
|
|
panic("Usage of FileSize in templates is deprecated in Forgejo. Locale.TrSize should be used instead.")
|
|
|
|
}
|