2022-08-28 12:43:25 +03:00
// Copyright 2022 The Gitea Authors. All rights reserved.
2022-11-27 21:20:29 +03:00
// SPDX-License-Identifier: MIT
2022-08-28 12:43:25 +03:00
package templates
import (
2023-04-14 08:19:11 +03:00
"bufio"
2022-10-08 00:02:24 +03:00
"bytes"
2023-08-08 04:22:47 +03:00
"context"
2023-04-08 16:15:22 +03:00
"errors"
2022-10-08 00:02:24 +03:00
"fmt"
2023-04-08 09:21:50 +03:00
"io"
"net/http"
"path/filepath"
2022-10-08 00:02:24 +03:00
"regexp"
"strconv"
"strings"
2023-04-30 15:22:23 +03:00
"sync"
2023-04-08 09:21:50 +03:00
"sync/atomic"
2023-04-08 16:15:22 +03:00
texttemplate "text/template"
2022-08-28 12:43:25 +03:00
2023-04-14 08:19:11 +03:00
"code.gitea.io/gitea/modules/assetfs"
2023-04-30 15:22:23 +03:00
"code.gitea.io/gitea/modules/graceful"
2022-08-28 12:43:25 +03:00
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
Make HTML template functions support context (#24056)
# Background
Golang template is not friendly for large projects, and Golang template
team is quite slow, related:
* `https://github.com/golang/go/issues/54450`
Without upstream support, we can also have our solution to make HTML
template functions support context.
It helps a lot, the above Golang template issue `#54450` explains a lot:
1. It makes `{{Locale.Tr}}` could be used in any template, without
passing unclear `(dict "root" . )` anymore.
2. More and more functions need `context`, like `avatar`, etc, we do not
need to do `(dict "Context" $.Context)` anymore.
3. Many request-related functions could be shared by parent&children
templates, like "user setting" / "system setting"
See the test `TestScopedTemplateSetFuncMap`, one template set, two
`Execute` calls with different `CtxFunc`.
# The Solution
Instead of waiting for upstream, this PR re-uses the escaped HTML
template trees, use `AddParseTree` to add related templates/trees to a
new template instance, then the new template instance can have its own
FuncMap , the function calls in the template trees will always use the
new template's FuncMap.
`template.New` / `template.AddParseTree` / `adding-FuncMap` are all
quite fast, so the performance is not affected.
The details:
1. Make a new `html/template/Template` for `all` templates
2. Add template code to the `all` template
3. Freeze the `all` template, reset its exec func map, it shouldn't
execute any template.
4. When a router wants to render a template by its `name`
1. Find the `name` in `all`
2. Find all its related sub templates
3. Escape all related templates (just like what the html template
package does)
4. Add the escaped parse-trees of related templates into a new (scoped)
`text/template/Template`
5. Add context-related func map into the new (scoped) text template
6. Execute the new (scoped) text template
7. To improve performance, the escaped templates are cached to `template
sets`
# FAQ
## There is a `unsafe` call, is this PR unsafe?
This PR is safe. Golang has strict language definition, it's safe to do
so: https://pkg.go.dev/unsafe#Pointer (1) Conversion of a *T1 to Pointer
to *T2
## What if Golang template supports such feature in the future?
The public structs/interfaces/functions introduced by this PR is quite
simple, the code of `HTMLRender` is not changed too much. It's very easy
to switch to the official mechanism if there would be one.
## Does this PR change the template execution behavior?
No, see the tests (welcome to design more tests if it's necessary)
---------
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Jason Song <i@wolfogre.com>
Co-authored-by: Giteabot <teabot@gitea.io>
2023-04-20 11:08:58 +03:00
"code.gitea.io/gitea/modules/templates/scopedtmpl"
2023-04-08 16:15:22 +03:00
"code.gitea.io/gitea/modules/util"
2022-08-28 12:43:25 +03:00
)
Make HTML template functions support context (#24056)
# Background
Golang template is not friendly for large projects, and Golang template
team is quite slow, related:
* `https://github.com/golang/go/issues/54450`
Without upstream support, we can also have our solution to make HTML
template functions support context.
It helps a lot, the above Golang template issue `#54450` explains a lot:
1. It makes `{{Locale.Tr}}` could be used in any template, without
passing unclear `(dict "root" . )` anymore.
2. More and more functions need `context`, like `avatar`, etc, we do not
need to do `(dict "Context" $.Context)` anymore.
3. Many request-related functions could be shared by parent&children
templates, like "user setting" / "system setting"
See the test `TestScopedTemplateSetFuncMap`, one template set, two
`Execute` calls with different `CtxFunc`.
# The Solution
Instead of waiting for upstream, this PR re-uses the escaped HTML
template trees, use `AddParseTree` to add related templates/trees to a
new template instance, then the new template instance can have its own
FuncMap , the function calls in the template trees will always use the
new template's FuncMap.
`template.New` / `template.AddParseTree` / `adding-FuncMap` are all
quite fast, so the performance is not affected.
The details:
1. Make a new `html/template/Template` for `all` templates
2. Add template code to the `all` template
3. Freeze the `all` template, reset its exec func map, it shouldn't
execute any template.
4. When a router wants to render a template by its `name`
1. Find the `name` in `all`
2. Find all its related sub templates
3. Escape all related templates (just like what the html template
package does)
4. Add the escaped parse-trees of related templates into a new (scoped)
`text/template/Template`
5. Add context-related func map into the new (scoped) text template
6. Execute the new (scoped) text template
7. To improve performance, the escaped templates are cached to `template
sets`
# FAQ
## There is a `unsafe` call, is this PR unsafe?
This PR is safe. Golang has strict language definition, it's safe to do
so: https://pkg.go.dev/unsafe#Pointer (1) Conversion of a *T1 to Pointer
to *T2
## What if Golang template supports such feature in the future?
The public structs/interfaces/functions introduced by this PR is quite
simple, the code of `HTMLRender` is not changed too much. It's very easy
to switch to the official mechanism if there would be one.
## Does this PR change the template execution behavior?
No, see the tests (welcome to design more tests if it's necessary)
---------
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Jason Song <i@wolfogre.com>
Co-authored-by: Giteabot <teabot@gitea.io>
2023-04-20 11:08:58 +03:00
type TemplateExecutor scopedtmpl . TemplateExecutor
2023-04-08 09:21:50 +03:00
type HTMLRender struct {
Make HTML template functions support context (#24056)
# Background
Golang template is not friendly for large projects, and Golang template
team is quite slow, related:
* `https://github.com/golang/go/issues/54450`
Without upstream support, we can also have our solution to make HTML
template functions support context.
It helps a lot, the above Golang template issue `#54450` explains a lot:
1. It makes `{{Locale.Tr}}` could be used in any template, without
passing unclear `(dict "root" . )` anymore.
2. More and more functions need `context`, like `avatar`, etc, we do not
need to do `(dict "Context" $.Context)` anymore.
3. Many request-related functions could be shared by parent&children
templates, like "user setting" / "system setting"
See the test `TestScopedTemplateSetFuncMap`, one template set, two
`Execute` calls with different `CtxFunc`.
# The Solution
Instead of waiting for upstream, this PR re-uses the escaped HTML
template trees, use `AddParseTree` to add related templates/trees to a
new template instance, then the new template instance can have its own
FuncMap , the function calls in the template trees will always use the
new template's FuncMap.
`template.New` / `template.AddParseTree` / `adding-FuncMap` are all
quite fast, so the performance is not affected.
The details:
1. Make a new `html/template/Template` for `all` templates
2. Add template code to the `all` template
3. Freeze the `all` template, reset its exec func map, it shouldn't
execute any template.
4. When a router wants to render a template by its `name`
1. Find the `name` in `all`
2. Find all its related sub templates
3. Escape all related templates (just like what the html template
package does)
4. Add the escaped parse-trees of related templates into a new (scoped)
`text/template/Template`
5. Add context-related func map into the new (scoped) text template
6. Execute the new (scoped) text template
7. To improve performance, the escaped templates are cached to `template
sets`
# FAQ
## There is a `unsafe` call, is this PR unsafe?
This PR is safe. Golang has strict language definition, it's safe to do
so: https://pkg.go.dev/unsafe#Pointer (1) Conversion of a *T1 to Pointer
to *T2
## What if Golang template supports such feature in the future?
The public structs/interfaces/functions introduced by this PR is quite
simple, the code of `HTMLRender` is not changed too much. It's very easy
to switch to the official mechanism if there would be one.
## Does this PR change the template execution behavior?
No, see the tests (welcome to design more tests if it's necessary)
---------
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Jason Song <i@wolfogre.com>
Co-authored-by: Giteabot <teabot@gitea.io>
2023-04-20 11:08:58 +03:00
templates atomic . Pointer [ scopedtmpl . ScopedTemplate ]
2023-04-08 09:21:50 +03:00
}
2023-04-30 15:22:23 +03:00
var (
htmlRender * HTMLRender
htmlRenderOnce sync . Once
)
2023-04-08 16:15:22 +03:00
var ErrTemplateNotInitialized = errors . New ( "template system is not initialized, check your log for errors" )
2023-08-08 04:22:47 +03:00
func ( h * HTMLRender ) HTML ( w io . Writer , status int , name string , data any , ctx context . Context ) error { //nolint:revive
2023-04-08 09:21:50 +03:00
if respWriter , ok := w . ( http . ResponseWriter ) ; ok {
if respWriter . Header ( ) . Get ( "Content-Type" ) == "" {
respWriter . Header ( ) . Set ( "Content-Type" , "text/html; charset=utf-8" )
2022-08-28 12:43:25 +03:00
}
2023-04-08 09:21:50 +03:00
respWriter . WriteHeader ( status )
}
2023-08-08 04:22:47 +03:00
t , err := h . TemplateLookup ( name , ctx )
2023-04-08 16:15:22 +03:00
if err != nil {
return texttemplate . ExecError { Name : name , Err : err }
}
return t . Execute ( w , data )
2023-04-08 09:21:50 +03:00
}
2023-08-08 04:22:47 +03:00
func ( h * HTMLRender ) TemplateLookup ( name string , ctx context . Context ) ( TemplateExecutor , error ) { //nolint:revive
2023-04-08 16:15:22 +03:00
tmpls := h . templates . Load ( )
if tmpls == nil {
return nil , ErrTemplateNotInitialized
}
2023-08-08 04:22:47 +03:00
m := NewFuncMap ( )
m [ "ctx" ] = func ( ) any { return ctx }
return tmpls . Executor ( name , m )
2023-04-08 09:21:50 +03:00
}
func ( h * HTMLRender ) CompileTemplates ( ) error {
2023-04-12 13:16:45 +03:00
assets := AssetFS ( )
Make HTML template functions support context (#24056)
# Background
Golang template is not friendly for large projects, and Golang template
team is quite slow, related:
* `https://github.com/golang/go/issues/54450`
Without upstream support, we can also have our solution to make HTML
template functions support context.
It helps a lot, the above Golang template issue `#54450` explains a lot:
1. It makes `{{Locale.Tr}}` could be used in any template, without
passing unclear `(dict "root" . )` anymore.
2. More and more functions need `context`, like `avatar`, etc, we do not
need to do `(dict "Context" $.Context)` anymore.
3. Many request-related functions could be shared by parent&children
templates, like "user setting" / "system setting"
See the test `TestScopedTemplateSetFuncMap`, one template set, two
`Execute` calls with different `CtxFunc`.
# The Solution
Instead of waiting for upstream, this PR re-uses the escaped HTML
template trees, use `AddParseTree` to add related templates/trees to a
new template instance, then the new template instance can have its own
FuncMap , the function calls in the template trees will always use the
new template's FuncMap.
`template.New` / `template.AddParseTree` / `adding-FuncMap` are all
quite fast, so the performance is not affected.
The details:
1. Make a new `html/template/Template` for `all` templates
2. Add template code to the `all` template
3. Freeze the `all` template, reset its exec func map, it shouldn't
execute any template.
4. When a router wants to render a template by its `name`
1. Find the `name` in `all`
2. Find all its related sub templates
3. Escape all related templates (just like what the html template
package does)
4. Add the escaped parse-trees of related templates into a new (scoped)
`text/template/Template`
5. Add context-related func map into the new (scoped) text template
6. Execute the new (scoped) text template
7. To improve performance, the escaped templates are cached to `template
sets`
# FAQ
## There is a `unsafe` call, is this PR unsafe?
This PR is safe. Golang has strict language definition, it's safe to do
so: https://pkg.go.dev/unsafe#Pointer (1) Conversion of a *T1 to Pointer
to *T2
## What if Golang template supports such feature in the future?
The public structs/interfaces/functions introduced by this PR is quite
simple, the code of `HTMLRender` is not changed too much. It's very easy
to switch to the official mechanism if there would be one.
## Does this PR change the template execution behavior?
No, see the tests (welcome to design more tests if it's necessary)
---------
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Jason Song <i@wolfogre.com>
Co-authored-by: Giteabot <teabot@gitea.io>
2023-04-20 11:08:58 +03:00
extSuffix := ".tmpl"
tmpls := scopedtmpl . NewScopedTemplate ( )
2023-04-30 15:22:23 +03:00
tmpls . Funcs ( NewFuncMap ( ) )
2023-04-12 13:16:45 +03:00
files , err := ListWebTemplateAssetNames ( assets )
if err != nil {
return nil
}
for _ , file := range files {
if ! strings . HasSuffix ( file , extSuffix ) {
2023-04-08 16:56:50 +03:00
continue
}
2023-04-12 13:16:45 +03:00
name := strings . TrimSuffix ( file , extSuffix )
2023-04-08 09:21:50 +03:00
tmpl := tmpls . New ( filepath . ToSlash ( name ) )
2023-04-12 13:16:45 +03:00
buf , err := assets . ReadFile ( file )
2023-04-08 09:21:50 +03:00
if err != nil {
return err
}
if _ , err = tmpl . Parse ( string ( buf ) ) ; err != nil {
return err
}
}
Make HTML template functions support context (#24056)
# Background
Golang template is not friendly for large projects, and Golang template
team is quite slow, related:
* `https://github.com/golang/go/issues/54450`
Without upstream support, we can also have our solution to make HTML
template functions support context.
It helps a lot, the above Golang template issue `#54450` explains a lot:
1. It makes `{{Locale.Tr}}` could be used in any template, without
passing unclear `(dict "root" . )` anymore.
2. More and more functions need `context`, like `avatar`, etc, we do not
need to do `(dict "Context" $.Context)` anymore.
3. Many request-related functions could be shared by parent&children
templates, like "user setting" / "system setting"
See the test `TestScopedTemplateSetFuncMap`, one template set, two
`Execute` calls with different `CtxFunc`.
# The Solution
Instead of waiting for upstream, this PR re-uses the escaped HTML
template trees, use `AddParseTree` to add related templates/trees to a
new template instance, then the new template instance can have its own
FuncMap , the function calls in the template trees will always use the
new template's FuncMap.
`template.New` / `template.AddParseTree` / `adding-FuncMap` are all
quite fast, so the performance is not affected.
The details:
1. Make a new `html/template/Template` for `all` templates
2. Add template code to the `all` template
3. Freeze the `all` template, reset its exec func map, it shouldn't
execute any template.
4. When a router wants to render a template by its `name`
1. Find the `name` in `all`
2. Find all its related sub templates
3. Escape all related templates (just like what the html template
package does)
4. Add the escaped parse-trees of related templates into a new (scoped)
`text/template/Template`
5. Add context-related func map into the new (scoped) text template
6. Execute the new (scoped) text template
7. To improve performance, the escaped templates are cached to `template
sets`
# FAQ
## There is a `unsafe` call, is this PR unsafe?
This PR is safe. Golang has strict language definition, it's safe to do
so: https://pkg.go.dev/unsafe#Pointer (1) Conversion of a *T1 to Pointer
to *T2
## What if Golang template supports such feature in the future?
The public structs/interfaces/functions introduced by this PR is quite
simple, the code of `HTMLRender` is not changed too much. It's very easy
to switch to the official mechanism if there would be one.
## Does this PR change the template execution behavior?
No, see the tests (welcome to design more tests if it's necessary)
---------
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Jason Song <i@wolfogre.com>
Co-authored-by: Giteabot <teabot@gitea.io>
2023-04-20 11:08:58 +03:00
tmpls . Freeze ( )
2023-04-08 09:21:50 +03:00
h . templates . Store ( tmpls )
return nil
}
2023-04-30 15:22:23 +03:00
// HTMLRenderer init once and returns the globally shared html renderer
func HTMLRenderer ( ) * HTMLRender {
htmlRenderOnce . Do ( initHTMLRenderer )
return htmlRender
}
2022-08-28 12:43:25 +03:00
2023-05-22 18:51:40 +03:00
func ReloadHTMLTemplates ( ) error {
2023-05-25 06:47:30 +03:00
log . Trace ( "Reloading HTML templates" )
2023-05-22 18:51:40 +03:00
if err := htmlRender . CompileTemplates ( ) ; err != nil {
log . Error ( "Template error: %v\n%s" , err , log . Stack ( 2 ) )
return err
}
return nil
}
2023-04-30 15:22:23 +03:00
func initHTMLRenderer ( ) {
2022-08-28 12:43:25 +03:00
rendererType := "static"
if ! setting . IsProd {
rendererType = "auto-reloading"
}
2023-04-30 15:22:23 +03:00
log . Debug ( "Creating %s HTML Renderer" , rendererType )
2022-08-28 12:43:25 +03:00
2023-04-30 15:22:23 +03:00
htmlRender = & HTMLRender { }
if err := htmlRender . CompileTemplates ( ) ; err != nil {
2023-04-14 08:19:11 +03:00
p := & templateErrorPrettier { assets : AssetFS ( ) }
2023-05-25 06:47:30 +03:00
wrapTmplErrMsg ( p . handleFuncNotDefinedError ( err ) )
wrapTmplErrMsg ( p . handleUnexpectedOperandError ( err ) )
wrapTmplErrMsg ( p . handleExpectedEndError ( err ) )
wrapTmplErrMsg ( p . handleGenericTemplateError ( err ) )
wrapTmplErrMsg ( fmt . Sprintf ( "CompileTemplates error: %v" , err ) )
2023-04-08 09:21:50 +03:00
}
2023-04-30 15:22:23 +03:00
2022-08-28 12:43:25 +03:00
if ! setting . IsProd {
2023-04-30 15:22:23 +03:00
go AssetFS ( ) . WatchLocalChanges ( graceful . GetManager ( ) . ShutdownContext ( ) , func ( ) {
2023-05-22 18:51:40 +03:00
_ = ReloadHTMLTemplates ( )
2022-08-28 12:43:25 +03:00
} )
}
}
2022-10-08 00:02:24 +03:00
2023-05-25 06:47:30 +03:00
func wrapTmplErrMsg ( msg string ) {
2023-04-14 08:19:11 +03:00
if msg == "" {
2022-10-08 00:02:24 +03:00
return
}
2023-05-25 06:47:30 +03:00
if setting . IsProd {
// in prod mode, Gitea must have correct templates to run
log . Fatal ( "Gitea can't run with template errors: %s" , msg )
}
2024-04-22 14:48:42 +03:00
// in dev mode, do not need to really exit, because the template errors could be fixed by developer soon and the templates get reloaded
log . Error ( "There are template errors but Gitea continues to run in dev mode: %s" , msg )
2022-10-08 00:02:24 +03:00
}
2023-04-14 08:19:11 +03:00
type templateErrorPrettier struct {
assets * assetfs . LayeredFS
2022-10-08 00:02:24 +03:00
}
2023-04-14 08:19:11 +03:00
var reGenericTemplateError = regexp . MustCompile ( ` ^template: (.*):([0-9]+): (.*) ` )
2022-10-08 00:02:24 +03:00
2023-04-14 08:19:11 +03:00
func ( p * templateErrorPrettier ) handleGenericTemplateError ( err error ) string {
groups := reGenericTemplateError . FindStringSubmatch ( err . Error ( ) )
2022-10-08 00:02:24 +03:00
if len ( groups ) != 4 {
2023-04-14 08:19:11 +03:00
return ""
2022-10-08 00:02:24 +03:00
}
2023-04-14 08:19:11 +03:00
tmplName , lineStr , message := groups [ 1 ] , groups [ 2 ] , groups [ 3 ]
return p . makeDetailedError ( message , tmplName , lineStr , - 1 , "" )
2022-10-08 00:02:24 +03:00
}
2023-04-14 08:19:11 +03:00
var reFuncNotDefinedError = regexp . MustCompile ( ` ^template: (.*):([0-9]+): (function "(.*)" not defined) ` )
2022-10-08 00:02:24 +03:00
2023-04-14 08:19:11 +03:00
func ( p * templateErrorPrettier ) handleFuncNotDefinedError ( err error ) string {
groups := reFuncNotDefinedError . FindStringSubmatch ( err . Error ( ) )
if len ( groups ) != 5 {
return ""
}
tmplName , lineStr , message , funcName := groups [ 1 ] , groups [ 2 ] , groups [ 3 ] , groups [ 4 ]
funcName , _ = strconv . Unquote ( ` " ` + funcName + ` " ` )
return p . makeDetailedError ( message , tmplName , lineStr , - 1 , funcName )
2022-10-08 00:02:24 +03:00
}
2023-04-14 08:19:11 +03:00
var reUnexpectedOperandError = regexp . MustCompile ( ` ^template: (.*):([0-9]+): (unexpected "(.*)" in operand) ` )
2022-10-08 00:02:24 +03:00
2023-04-14 08:19:11 +03:00
func ( p * templateErrorPrettier ) handleUnexpectedOperandError ( err error ) string {
groups := reUnexpectedOperandError . FindStringSubmatch ( err . Error ( ) )
if len ( groups ) != 5 {
return ""
2022-10-08 00:02:24 +03:00
}
2023-04-14 08:19:11 +03:00
tmplName , lineStr , message , unexpected := groups [ 1 ] , groups [ 2 ] , groups [ 3 ] , groups [ 4 ]
unexpected , _ = strconv . Unquote ( ` " ` + unexpected + ` " ` )
return p . makeDetailedError ( message , tmplName , lineStr , - 1 , unexpected )
}
2022-10-08 00:02:24 +03:00
2023-04-14 08:19:11 +03:00
var reExpectedEndError = regexp . MustCompile ( ` ^template: (.*):([0-9]+): (expected end; found (.*)) ` )
2022-10-08 00:02:24 +03:00
2023-04-14 08:19:11 +03:00
func ( p * templateErrorPrettier ) handleExpectedEndError ( err error ) string {
groups := reExpectedEndError . FindStringSubmatch ( err . Error ( ) )
if len ( groups ) != 5 {
return ""
}
tmplName , lineStr , message , unexpected := groups [ 1 ] , groups [ 2 ] , groups [ 3 ] , groups [ 4 ]
return p . makeDetailedError ( message , tmplName , lineStr , - 1 , unexpected )
}
2022-10-08 00:02:24 +03:00
2023-04-14 08:19:11 +03:00
var (
reTemplateExecutingError = regexp . MustCompile ( ` ^template: (.*):([1-9][0-9]*):([1-9][0-9]*): (executing .*) ` )
reTemplateExecutingErrorMsg = regexp . MustCompile ( ` ^executing "(.*)" at <(.*)>: ` )
)
2022-10-08 00:02:24 +03:00
2023-04-14 08:19:11 +03:00
func ( p * templateErrorPrettier ) handleTemplateRenderingError ( err error ) string {
if groups := reTemplateExecutingError . FindStringSubmatch ( err . Error ( ) ) ; len ( groups ) > 0 {
tmplName , lineStr , posStr , msgPart := groups [ 1 ] , groups [ 2 ] , groups [ 3 ] , groups [ 4 ]
target := ""
if groups = reTemplateExecutingErrorMsg . FindStringSubmatch ( msgPart ) ; len ( groups ) > 0 {
target = groups [ 2 ]
2022-10-08 00:02:24 +03:00
}
2023-04-14 08:19:11 +03:00
return p . makeDetailedError ( msgPart , tmplName , lineStr , posStr , target )
} else if execErr , ok := err . ( texttemplate . ExecError ) ; ok {
layerName := p . assets . GetFileLayerName ( execErr . Name + ".tmpl" )
return fmt . Sprintf ( "asset from: %s, %s" , layerName , err . Error ( ) )
}
2023-10-24 05:54:59 +03:00
return err . Error ( )
2023-04-14 08:19:11 +03:00
}
2022-10-08 00:02:24 +03:00
2023-04-14 08:19:11 +03:00
func HandleTemplateRenderingError ( err error ) string {
p := & templateErrorPrettier { assets : AssetFS ( ) }
return p . handleTemplateRenderingError ( err )
}
2022-10-08 00:02:24 +03:00
2023-04-14 08:19:11 +03:00
const dashSeparator = "----------------------------------------------------------------------"
2022-10-08 00:02:24 +03:00
2023-04-14 08:19:11 +03:00
func ( p * templateErrorPrettier ) makeDetailedError ( errMsg , tmplName string , lineNum , posNum any , target string ) string {
code , layer , err := p . assets . ReadLayeredFile ( tmplName + ".tmpl" )
if err != nil {
return fmt . Sprintf ( "template error: %s, and unable to find template file %q" , errMsg , tmplName )
}
line , err := util . ToInt64 ( lineNum )
if err != nil {
return fmt . Sprintf ( "template error: %s, unable to parse template %q line number %q" , errMsg , tmplName , lineNum )
}
pos , err := util . ToInt64 ( posNum )
if err != nil {
return fmt . Sprintf ( "template error: %s, unable to parse template %q pos number %q" , errMsg , tmplName , posNum )
2022-10-08 00:02:24 +03:00
}
2023-04-14 08:19:11 +03:00
detail := extractErrorLine ( code , int ( line ) , int ( pos ) , target )
2022-10-08 00:02:24 +03:00
2023-04-14 08:19:11 +03:00
var msg string
if pos >= 0 {
msg = fmt . Sprintf ( "template error: %s:%s:%d:%d : %s" , layer , tmplName , line , pos , errMsg )
} else {
msg = fmt . Sprintf ( "template error: %s:%s:%d : %s" , layer , tmplName , line , errMsg )
2023-03-20 23:56:48 +03:00
}
2023-04-14 08:19:11 +03:00
return msg + "\n" + dashSeparator + "\n" + detail + "\n" + dashSeparator
}
func extractErrorLine ( code [ ] byte , lineNum , posNum int , target string ) string {
b := bufio . NewReader ( bytes . NewReader ( code ) )
var line [ ] byte
var err error
for i := 0 ; i < lineNum ; i ++ {
if line , err = b . ReadBytes ( '\n' ) ; err != nil {
if i == lineNum - 1 && errors . Is ( err , io . EOF ) {
err = nil
2022-10-08 00:02:24 +03:00
}
2023-04-14 08:19:11 +03:00
break
2023-03-20 23:56:48 +03:00
}
2023-04-14 08:19:11 +03:00
}
if err != nil {
return fmt . Sprintf ( "unable to find target line %d" , lineNum )
}
2022-10-08 00:02:24 +03:00
2023-04-14 08:19:11 +03:00
line = bytes . TrimRight ( line , "\r\n" )
var indicatorLine [ ] byte
targetBytes := [ ] byte ( target )
targetLen := len ( targetBytes )
for i := 0 ; i < len ( line ) ; {
if posNum == - 1 && target != "" && bytes . HasPrefix ( line [ i : ] , targetBytes ) {
for j := 0 ; j < targetLen && i < len ( line ) ; j ++ {
indicatorLine = append ( indicatorLine , '^' )
i ++
}
} else if i == posNum {
indicatorLine = append ( indicatorLine , '^' )
i ++
} else {
if line [ i ] == '\t' {
indicatorLine = append ( indicatorLine , '\t' )
} else {
indicatorLine = append ( indicatorLine , ' ' )
}
i ++
2022-10-08 00:02:24 +03:00
}
}
2023-04-14 08:19:11 +03:00
// if the indicatorLine only contains spaces, trim it together
return strings . TrimRight ( string ( line ) + "\n" + string ( indicatorLine ) , " \t\r\n" )
2022-10-08 00:02:24 +03:00
}