diff --git a/templates/pongo/engine.go b/templates/pongo/engine.go
index bf71a23..eaee220 100644
--- a/templates/pongo/engine.go
+++ b/templates/pongo/engine.go
@@ -62,11 +62,25 @@ func (c *pongoComponent) Render(ctx context.Context, w io.Writer) error {
// {{ site_settings }} — bn.SiteSettingsData struct
// {{ page_meta }} — bn.PageMeta struct
func (e *Engine) MustPageTemplate(name string) templates.TemplateFunc {
- tpl := pongo2.Must(e.set.FromFile(name))
+ fn, err := e.PageTemplate(name)
+ if err != nil {
+ panic(err)
+ }
+ return fn
+}
+
+// PageTemplate is MustPageTemplate without the panic — for hosts compiling
+// artifact-supplied templates at load time (a malformed artifact must fail
+// the load, not the process).
+func (e *Engine) PageTemplate(name string) (templates.TemplateFunc, error) {
+ tpl, err := e.set.FromFile(name)
+ if err != nil {
+ return nil, err
+ }
return func(ctx context.Context, doc map[string]any) templates.HTMLComponent {
pongoCtx := e.buildPageContext(ctx, doc)
return &pongoComponent{tpl: tpl, ctx: pongoCtx}
- }
+ }, nil
}
// MustBlockTemplate parses a pongo2 block template and returns a BlockFunc.
@@ -75,7 +89,19 @@ func (e *Engine) MustPageTemplate(name string) templates.TemplateFunc {
// The content map fields are available directly as template variables.
// Request context is available under {{ ctx.url }}, {{ ctx.isEditor }}, etc.
func (e *Engine) MustBlockTemplate(name string) blocks.BlockFunc {
- tpl := pongo2.Must(e.set.FromFile(name))
+ fn, err := e.BlockTemplate(name)
+ if err != nil {
+ panic(err)
+ }
+ return fn
+}
+
+// BlockTemplate is MustBlockTemplate without the panic (see PageTemplate).
+func (e *Engine) BlockTemplate(name string) (blocks.BlockFunc, error) {
+ tpl, err := e.set.FromFile(name)
+ if err != nil {
+ return nil, err
+ }
return func(ctx context.Context, content map[string]any) string {
pongoCtx := buildBlockContext(ctx, content)
out, err := tpl.Execute(pongoCtx)
@@ -83,7 +109,7 @@ func (e *Engine) MustBlockTemplate(name string) blocks.BlockFunc {
return ""
}
return blocks.ProcessIcons(out)
- }
+ }, nil
}
// MustBlockTemplateWithDefaults is like MustBlockTemplate but merges default
@@ -129,7 +155,19 @@ func (e *Engine) MustTemplateOverride(name string) blocks.BlockFunc {
// {{ colors.muted }} — muted hex color
// (and all other EmailColors fields as lowercase keys)
func (e *Engine) MustEmailWrapper(name string) templates.EmailWrapperFunc {
- tpl := pongo2.Must(e.set.FromFile(name))
+ fn, err := e.EmailWrapper(name)
+ if err != nil {
+ panic(err)
+ }
+ return fn
+}
+
+// EmailWrapper is MustEmailWrapper without the panic (see PageTemplate).
+func (e *Engine) EmailWrapper(name string) (templates.EmailWrapperFunc, error) {
+ tpl, err := e.set.FromFile(name)
+ if err != nil {
+ return nil, err
+ }
return func(body string, ctx templates.EmailContext) string {
pongoCtx := pongo2.Context{
"body": body,
@@ -158,7 +196,7 @@ func (e *Engine) MustEmailWrapper(name string) templates.EmailWrapperFunc {
return body
}
return out
- }
+ }, nil
}
// renderComponent renders any HTMLComponent to a string.
diff --git a/templates/pongo/engine_test.go b/templates/pongo/engine_test.go
new file mode 100644
index 0000000..67d4ee5
--- /dev/null
+++ b/templates/pongo/engine_test.go
@@ -0,0 +1,66 @@
+package pongo
+
+import (
+ "strings"
+ "testing"
+ "testing/fstest"
+
+ "git.dev.alexdunmow.com/block/core/templates"
+)
+
+func TestNonPanickingCompileVariants(t *testing.T) {
+ fsys := fstest.MapFS{
+ "templates/theme/default.ninjatpl": {Data: []byte(`
x
", ectx); got != "