From 5d2cbee9890d159ca2c776e85eaabf032c2df7c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Tue, 19 Apr 2022 16:56:49 +0200 Subject: [PATCH] Add try Updates #9737 --- .../texttemplate/hugo_template.go | 30 +++++++++++++++- tpl/partials/init.go | 2 +- tpl/safe/init.go | 7 ++++ tpl/templates/templates_integration_test.go | 34 +++++++++++++++++++ 4 files changed, 71 insertions(+), 2 deletions(-) diff --git a/tpl/internal/go_templates/texttemplate/hugo_template.go b/tpl/internal/go_templates/texttemplate/hugo_template.go index 12dbe041217..0dbee02f733 100644 --- a/tpl/internal/go_templates/texttemplate/hugo_template.go +++ b/tpl/internal/go_templates/texttemplate/hugo_template.go @@ -15,6 +15,7 @@ package template import ( "context" + "fmt" "io" "reflect" @@ -255,10 +256,32 @@ func (s *state) evalField(dot reflect.Value, fieldName string, node parse.Node, panic("not reached") } +// TryValue is what gets returned when using the "try" keyword. +type TryValue struct { + // Value is the value returned by the function or method wrapped with "try". + // This will always be nil if Err is set. + Value any + // Err is the error returned by the function or method wrapped with "try". + // This will always be nil if Value is set. + Err error +} + // evalCall executes a function or method call. If it's a method, fun already has the receiver bound, so // it looks just like a function call. The arg list, if non-nil, includes (in the manner of the shell), arg[0] // as the function itself. -func (s *state) evalCall(dot, fun reflect.Value, isBuiltin bool, node parse.Node, name string, args []parse.Node, final reflect.Value, first ...reflect.Value) reflect.Value { +func (s *state) evalCall(dot, fun reflect.Value, isBuiltin bool, node parse.Node, name string, args []parse.Node, final reflect.Value, first ...reflect.Value) (val reflect.Value) { + // Added for Hugo. + if name == "try" { + defer func() { + if r := recover(); r != nil { + if err, ok := r.(error); ok { + val = reflect.ValueOf(TryValue{nil, err}) + } else { + val = reflect.ValueOf(TryValue{nil, fmt.Errorf("%v", r)}) + } + } + }() + } if args != nil { args = args[1:] // Zeroth arg is function name/node; not passed to function. } @@ -371,6 +394,11 @@ func (s *state) evalCall(dot, fun reflect.Value, isBuiltin bool, node parse.Node s.helper.OnCalled(s.ctx, s.prep, name, argv, vv) } + // Added for Hugo. + if name == "try" { + return reflect.ValueOf(TryValue{vv.Interface(), nil}) + } + return vv } diff --git a/tpl/partials/init.go b/tpl/partials/init.go index e9d901bbf63..6f7c0ffc1e7 100644 --- a/tpl/partials/init.go +++ b/tpl/partials/init.go @@ -38,7 +38,7 @@ func init() { }, ) - // TODO(bep) we need the return to be a valid identifier, but + // TODO(bep) we need the return to be a valid identifiers, but // should consider another way of adding it. ns.AddMethodMapping(func() string { return "" }, []string{"return"}, diff --git a/tpl/safe/init.go b/tpl/safe/init.go index 3b498e6dfb0..ea1469e8047 100644 --- a/tpl/safe/init.go +++ b/tpl/safe/init.go @@ -70,6 +70,13 @@ func init() { }, ) + ns.AddMethodMapping(func(v any) (any, error) { + return v, nil + }, + []string{"try"}, + [][2]string{}, + ) + return ns } diff --git a/tpl/templates/templates_integration_test.go b/tpl/templates/templates_integration_test.go index 301f783a573..aa5540862fe 100644 --- a/tpl/templates/templates_integration_test.go +++ b/tpl/templates/templates_integration_test.go @@ -93,3 +93,37 @@ Home: true `) } + +func TestTry(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +baseURL = 'http://example.com/' +-- layouts/index.html -- +Home. +{{ $g := try ("hello = \"Hello Hugo\"" | transform.Unmarshal) }} +{{ with $g.Err }} +Err1: {{ . }} +{{ else }} +Value1: {{ $g.Value.hello | safeHTML }}| +{{ end }} +{{ $g := try ("hello != \"Hello Hugo\"" | transform.Unmarshal) }} +{{ with $g.Err }} +Err2: {{ . | safeHTML }} +{{ else }} +Value2: {{ $g.Value.hello | safeHTML }}| +{{ end }} +Try upper: {{ (try ("hello" | upper)).Value }} +Try printf: {{ (try (printf "hello %s" "world")).Value }} +` + + b := hugolib.Test(t, files) + + b.AssertFileContent("public/index.html", + "Value1: Hello Hugo|", + "Err2: template: index.html:", + "Try upper: HELLO", + "Try printf: hello world", + ) +}