From fee5f2aee7538550750be466e30d65eda7afe599 Mon Sep 17 00:00:00 2001 From: Tony Holdstock-Brown Date: Tue, 22 Dec 2020 03:18:22 -0800 Subject: [PATCH 1/3] Implement custom resolvers --- eval.go | 45 +++++++++++++++++++++++++++++++++++++++++++-- exec.go | 12 +++++++++++- 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/eval.go b/eval.go index 0bf85e4..a2b57d1 100644 --- a/eval.go +++ b/eval.go @@ -70,13 +70,39 @@ func (w *escapeeWriter) Write(b []byte) (int, error) { return 0, nil } +// Resolver resolves identifiers to variables, context, and functions. +type Resolver interface { + Resolve(name string) (reflect.Value, error) +} + +// defaultResolver wraps and delegates to Runtime, fulfilling the Resolver interface. +// +// Runtime declares the resolve function which checks context, variables, globals, +// and default variables. +type defaultResolver struct { + *Runtime +} + +func (r *defaultResolver) Resolve(name string) (reflect.Value, error) { + return r.Runtime.resolve(name) +} + // Runtime this type holds the state of the execution of an template type Runtime struct { *escapeeWriter *scope content func(*Runtime, Expression) - context reflect.Value + + // customResolver represents a custom Resolver used to interpret and evaluate + // templates. + customResolver Resolver +} + +// DefaultResolver returns the default resolver for Jet, which checks +// the context, variables, globals, and default variables in that order. +func (r *Runtime) DefaultResolver() Resolver { + return &defaultResolver{Runtime: r} } // Context returns the current context value @@ -139,6 +165,11 @@ func (state *Runtime) setValue(name string, val reflect.Value) error { return fmt.Errorf("could not assign %q = %v because variable %q is uninitialised", name, val, name) } +// WithResolver sets a custom resolver for use within this runtime. +func (state *Runtime) WithResolver(r Resolver) { + state.customResolver = r +} + // LetGlobal sets or initialises a variable in the top-most template scope. func (state *Runtime) LetGlobal(name string, val interface{}) { sc := state.scope @@ -171,8 +202,17 @@ func (state *Runtime) SetOrLet(name string, val interface{}) { } } -// Resolve resolves a value from the execution context. +// resolve resolves a value from the execution context using the custom resolver +// if provided. func (state *Runtime) resolve(name string) (reflect.Value, error) { + if state.customResolver == nil { + return state.defaultResolve(name) + } + return state.customResolver.Resolve(name) +} + +// defaultResolve resolves a value from the execution context. +func (state *Runtime) defaultResolve(name string) (reflect.Value, error) { if name == "." { return state.context, nil } @@ -223,6 +263,7 @@ func (st *Runtime) recover(err *error) { // reset state scope and context just to be safe (they might not be cleared properly if there was a panic while using the state) st.scope = &scope{} st.context = reflect.Value{} + st.customResolver = nil pool_State.Put(st) if recovered := recover(); recovered != nil { var ok bool diff --git a/exec.go b/exec.go index e79c98b..ae8dfed 100644 --- a/exec.go +++ b/exec.go @@ -40,11 +40,21 @@ func (scope VarMap) SetWriter(name string, v SafeWriter) VarMap { return scope } +func (t *Template) ExecuteWith(r Resolver, w io.Writer, variables VarMap, data interface{}) (err error) { + st := pool_State.Get().(*Runtime) + st.WithResolver(r) + return t.execute(st, w, variables, data) +} + // Execute executes the template into w. func (t *Template) Execute(w io.Writer, variables VarMap, data interface{}) (err error) { st := pool_State.Get().(*Runtime) - defer st.recover(&err) + return t.execute(st, w, variables, data) +} +// Execute executes the template into w. +func (t *Template) execute(st *Runtime, w io.Writer, variables VarMap, data interface{}) (err error) { + defer st.recover(&err) st.blocks = t.processedBlocks st.variables = variables st.set = t.set From 65836628b5f15e5aa7dff432ce44d5f9c227663d Mon Sep 17 00:00:00 2001 From: Tony Holdstock-Brown Date: Tue, 22 Dec 2020 03:28:46 -0800 Subject: [PATCH 2/3] Expose runtime --- exec.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/exec.go b/exec.go index ae8dfed..d5b7077 100644 --- a/exec.go +++ b/exec.go @@ -40,9 +40,12 @@ func (scope VarMap) SetWriter(name string, v SafeWriter) VarMap { return scope } -func (t *Template) ExecuteWith(r Resolver, w io.Writer, variables VarMap, data interface{}) (err error) { +func (t *Template) Runtime() *Runtime { st := pool_State.Get().(*Runtime) - st.WithResolver(r) + return st +} + +func (t *Template) ExecuteWith(st *Runtime, w io.Writer, variables VarMap, data interface{}) (err error) { return t.execute(st, w, variables, data) } From 9dd5163f652c3304391c98ec46785db444aa821e Mon Sep 17 00:00:00 2001 From: Tony Holdstock-Brown Date: Tue, 22 Dec 2020 03:44:26 -0800 Subject: [PATCH 3/3] Make resolver non-recursive with customs --- eval.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/eval.go b/eval.go index a2b57d1..891fe30 100644 --- a/eval.go +++ b/eval.go @@ -75,16 +75,18 @@ type Resolver interface { Resolve(name string) (reflect.Value, error) } +type ResolverFunc func(name string) (reflect.Value, error) + // defaultResolver wraps and delegates to Runtime, fulfilling the Resolver interface. // // Runtime declares the resolve function which checks context, variables, globals, // and default variables. type defaultResolver struct { - *Runtime + f ResolverFunc } func (r *defaultResolver) Resolve(name string) (reflect.Value, error) { - return r.Runtime.resolve(name) + return r.f(name) } // Runtime this type holds the state of the execution of an template @@ -102,7 +104,7 @@ type Runtime struct { // DefaultResolver returns the default resolver for Jet, which checks // the context, variables, globals, and default variables in that order. func (r *Runtime) DefaultResolver() Resolver { - return &defaultResolver{Runtime: r} + return &defaultResolver{f: r.defaultResolve} } // Context returns the current context value