137 lines
3.0 KiB
Go
137 lines
3.0 KiB
Go
package js
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"sync"
|
|
|
|
time "github.com/echlebek/timeproxy"
|
|
"github.com/robertkrimen/otto"
|
|
"github.com/robertkrimen/otto/parser"
|
|
)
|
|
|
|
var ottoCache *vmCache
|
|
var ottoOnce sync.Once
|
|
|
|
type JavascriptAssets interface {
|
|
Key() string
|
|
Scripts() (map[string]io.ReadCloser, error)
|
|
}
|
|
|
|
// SyntaxError is returned when a javascript expression could not be parsed.
|
|
type SyntaxError string
|
|
|
|
func (s SyntaxError) Error() string {
|
|
return string(s)
|
|
}
|
|
|
|
// NewSyntaxError creates a new SyntaxError.
|
|
func NewSyntaxError(err string, args ...interface{}) SyntaxError {
|
|
return SyntaxError(fmt.Sprintf(err, args...))
|
|
}
|
|
|
|
// ParseExpressions parses each JS expression and returns the first error that
|
|
// is encountered, or nil.
|
|
func ParseExpressions(expressions []string) error {
|
|
for i, expr := range expressions {
|
|
_, err := parser.ParseFile(nil, "", expr, 0)
|
|
if err != nil {
|
|
return NewSyntaxError("syntax error in expression %d: %s", i, err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func newOttoVM(assets JavascriptAssets) (*otto.Otto, error) {
|
|
ottoOnce.Do(func() {
|
|
ottoCache = newVMCache()
|
|
})
|
|
key := ""
|
|
if assets != nil {
|
|
key = assets.Key()
|
|
}
|
|
vm := ottoCache.Acquire(key)
|
|
if vm != nil {
|
|
return vm, nil
|
|
}
|
|
vm = otto.New()
|
|
if err := addTimeFuncs(vm); err != nil {
|
|
return nil, err
|
|
}
|
|
if assets != nil {
|
|
if err := addAssets(vm, assets); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
ottoCache.Init(key, vm)
|
|
return ottoCache.Acquire(key), nil
|
|
}
|
|
|
|
func addAssets(vm *otto.Otto, assets JavascriptAssets) error {
|
|
scripts, err := assets.Scripts()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() {
|
|
for _, script := range scripts {
|
|
_ = script.Close()
|
|
}
|
|
}()
|
|
for name, script := range scripts {
|
|
if _, err := vm.Eval(script); err != nil {
|
|
return fmt.Errorf("error evaluating %s: %s", name, err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func addTimeFuncs(vm *otto.Otto) error {
|
|
funcs := map[string]interface{}{
|
|
// hour returns the hour within the day
|
|
"hour": func(args ...interface{}) interface{} {
|
|
if len(args) == 0 {
|
|
return 0
|
|
}
|
|
t := time.Unix(toInt64(args[0]), 0).UTC()
|
|
return t.Hour()
|
|
},
|
|
// weekday returns the number representation of the day of the week, where
|
|
// Sunday = 0
|
|
"weekday": func(args ...interface{}) interface{} {
|
|
if len(args) == 0 {
|
|
return 0
|
|
}
|
|
t := time.Unix(toInt64(args[0]), 0).UTC()
|
|
return t.Weekday()
|
|
},
|
|
}
|
|
for k, v := range funcs {
|
|
if err := vm.Set(k, v); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Evaluate evaluates the javascript expression with parameters applied.
|
|
// If scripts is non-nil, then the scripts will be evaluated in the
|
|
// expression's runtime context before the expression is evaluated.
|
|
func Evaluate(expr string, parameters interface{}, assets JavascriptAssets) (bool, error) {
|
|
jsvm, err := newOttoVM(assets)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if params, ok := parameters.(map[string]interface{}); ok {
|
|
for name, value := range params {
|
|
if err := jsvm.Set(name, value); err != nil {
|
|
return false, err
|
|
}
|
|
}
|
|
}
|
|
value, err := jsvm.Run(expr)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return value.ToBoolean()
|
|
}
|