2026-01-24 13:11:49 +08:00
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package templates
import (
"context"
"html/template"
"io"
"net/http"
"slices"
"strings"
"sync"
texttemplate "text/template"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
)
type pageRenderer struct {
tmplRenderer * tmplRender
}
func ( r * pageRenderer ) funcMap ( ctx context . Context ) template . FuncMap {
2026-04-04 01:56:31 +08:00
pageFuncMap := newFuncMapWebPage ( )
2026-01-24 13:11:49 +08:00
pageFuncMap [ "ctx" ] = func ( ) any { return ctx }
return pageFuncMap
}
func ( r * pageRenderer ) funcMapDummy ( ) template . FuncMap {
2026-04-04 01:56:31 +08:00
dummyFuncMap := newFuncMapWebPage ( )
2026-01-24 13:11:49 +08:00
dummyFuncMap [ "ctx" ] = func ( ) any { return nil } // for template compilation only, no context available
return dummyFuncMap
}
func ( r * pageRenderer ) TemplateLookup ( tmpl string , templateCtx context . Context ) ( TemplateExecutor , error ) { //nolint:revive // we don't use ctx, only pass it to the template executor
return r . tmplRenderer . Templates ( ) . Executor ( tmpl , r . funcMap ( templateCtx ) )
}
func ( r * pageRenderer ) HTML ( w io . Writer , status int , tplName TplName , data any , templateCtx context . Context ) error { //nolint:revive // we don't use ctx, only pass it to the template executor
name := string ( tplName )
if respWriter , ok := w . ( http . ResponseWriter ) ; ok {
if respWriter . Header ( ) . Get ( "Content-Type" ) == "" {
respWriter . Header ( ) . Set ( "Content-Type" , "text/html; charset=utf-8" )
}
respWriter . WriteHeader ( status )
}
t , err := r . TemplateLookup ( name , templateCtx )
if err != nil {
return texttemplate . ExecError { Name : name , Err : err }
}
return t . Execute ( w , data )
}
var PageRenderer = sync . OnceValue ( func ( ) * pageRenderer {
rendererType := util . Iif ( setting . IsProd , "static" , "auto-reloading" )
log . Debug ( "Creating %s HTML Renderer" , rendererType )
assetFS := AssetFS ( )
tr := & tmplRender {
collectTemplateNames : func ( ) ( [ ] string , error ) {
names , err := assetFS . ListAllFiles ( "." , true )
if err != nil {
return nil , err
}
names = slices . DeleteFunc ( names , func ( file string ) bool {
return strings . HasPrefix ( file , "mail/" ) || ! strings . HasSuffix ( file , ".tmpl" )
} )
for i , file := range names {
names [ i ] = strings . TrimSuffix ( file , ".tmpl" )
}
return names , nil
} ,
readTemplateContent : func ( name string ) ( [ ] byte , error ) {
return assetFS . ReadFile ( name + ".tmpl" )
} ,
}
pr := & pageRenderer { tmplRenderer : tr }
if err := tr . recompileTemplates ( pr . funcMapDummy ( ) ) ; err != nil {
processStartupTemplateError ( err )
}
if ! setting . IsProd {
go AssetFS ( ) . WatchLocalChanges ( graceful . GetManager ( ) . ShutdownContext ( ) , func ( ) {
if err := tr . recompileTemplates ( pr . funcMapDummy ( ) ) ; err != nil {
log . Error ( "Template error: %v\n%s" , err , log . Stack ( 2 ) )
}
} )
}
return pr
} )
func PageRendererReload ( ) error {
return PageRenderer ( ) . tmplRenderer . recompileTemplates ( PageRenderer ( ) . funcMapDummy ( ) )
}