Updates #9737
This commit is contained in:
Bjørn Erik Pedersen 2022-04-19 16:56:49 +02:00
parent 723e3f4342
commit 5d2cbee989
4 changed files with 71 additions and 2 deletions

View file

@ -15,6 +15,7 @@ package template
import ( import (
"context" "context"
"fmt"
"io" "io"
"reflect" "reflect"
@ -255,10 +256,32 @@ func (s *state) evalField(dot reflect.Value, fieldName string, node parse.Node,
panic("not reached") 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 // 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] // 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. // 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 { if args != nil {
args = args[1:] // Zeroth arg is function name/node; not passed to function. 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) 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 return vv
} }

View file

@ -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. // should consider another way of adding it.
ns.AddMethodMapping(func() string { return "" }, ns.AddMethodMapping(func() string { return "" },
[]string{"return"}, []string{"return"},

View file

@ -70,6 +70,13 @@ func init() {
}, },
) )
ns.AddMethodMapping(func(v any) (any, error) {
return v, nil
},
[]string{"try"},
[][2]string{},
)
return ns return ns
} }

View file

@ -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",
)
}