Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom resolvers #186

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 45 additions & 2 deletions eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,41 @@ 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)
}

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 {
f ResolverFunc
}

func (r *defaultResolver) Resolve(name string) (reflect.Value, error) {
return r.f(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{f: r.defaultResolve}
}

// Context returns the current context value
Expand Down Expand Up @@ -139,6 +167,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
Expand Down Expand Up @@ -171,8 +204,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
}
Expand Down Expand Up @@ -223,6 +265,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
Expand Down
15 changes: 14 additions & 1 deletion exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,24 @@ func (scope VarMap) SetWriter(name string, v SafeWriter) VarMap {
return scope
}

func (t *Template) Runtime() *Runtime {
st := pool_State.Get().(*Runtime)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will give you any runtime from the pool, possibly a new one, not the one executing the template.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead, you should create a runtime, then create a custom resolver to which you pass the runtime, then set the resolver in the runtime, then execute the template.

return st
}

func (t *Template) ExecuteWith(st *Runtime, w io.Writer, variables VarMap, data interface{}) (err error) {
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
Expand Down