From 1e71caa8717c91d48bdeed436ae55f9b7fdff155 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 | 28 ++++++++++++++- tpl/partials/init.go | 2 +- tpl/safe/init.go | 7 ++++ tpl/templates/integration_test.go | 35 +++++++++++++++++++ 4 files changed, 70 insertions(+), 2 deletions(-) diff --git a/tpl/internal/go_templates/texttemplate/hugo_template.go b/tpl/internal/go_templates/texttemplate/hugo_template.go index dab5a05a3d0..4a63990f798 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" @@ -288,10 +289,28 @@ func (s *state) evalField(dot reflect.Value, fieldName string, node parse.Node, panic("not reached") } +type TryValue struct { + Value any + 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. } @@ -390,6 +409,13 @@ func (s *state) evalCall(dot, fun reflect.Value, isBuiltin bool, node parse.Node s.at(node) s.errorf("error calling %s: %w", name, err) } + + // Added for Hugo. + if name == "try" { + fmt.Println("GOT", v) + return reflect.ValueOf(TryValue{unwrap(v).Interface(), nil}) + } + return unwrap(v) } diff --git a/tpl/partials/init.go b/tpl/partials/init.go index 2662b8894d1..722e047c2a4 100644 --- a/tpl/partials/init.go +++ b/tpl/partials/init.go @@ -36,7 +36,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 794c9d6f0f2..4dab20ed71e 100644 --- a/tpl/safe/init.go +++ b/tpl/safe/init.go @@ -73,6 +73,13 @@ func init() { [][2]string{}, ) + ns.AddMethodMapping(func(v any) (any, error) { + return v, nil + }, + []string{"try"}, + [][2]string{}, + ) + return ns } diff --git a/tpl/templates/integration_test.go b/tpl/templates/integration_test.go index fea2d7f6e07..9df0760cd02 100644 --- a/tpl/templates/integration_test.go +++ b/tpl/templates/integration_test.go @@ -83,3 +83,38 @@ post/doesnotexist.html: false `) } + +func TestTry(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +baseURL = 'http://example.com/' +-- layouts/index.html -- +{{ $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 }} + + ` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }, + ).Build() + + b.AssertFileContent("public/index.html", + "Value1: Hello Hugo|", + "Err2: template: index.html:7:52: executing \"index.html\" at : error calling Unmarshal: unmarshal failed: toml: expected character =", + ) +}