|
@@ -0,0 +1,232 @@
|
|
|
|
+package mjs
|
|
|
|
+
|
|
|
|
+import (
|
|
|
|
+ "errors"
|
|
|
|
+ "fmt"
|
|
|
|
+ "log"
|
|
|
|
+ "time"
|
|
|
|
+
|
|
|
|
+ "git.ali33.ru/fcg-xvii/go-tools/cache"
|
|
|
|
+ "github.com/dop251/goja"
|
|
|
|
+)
|
|
|
|
+
|
|
|
|
+func New(modified func(name string) int64, content func(name string) ([]byte, error)) *Mjs {
|
|
|
|
+ return &Mjs{
|
|
|
|
+ callbackModified: modified,
|
|
|
|
+ callbackContent: content,
|
|
|
|
+ cache: cache.NewMap(time.Hour*6, 0),
|
|
|
|
+ durationMax: time.Second * 300,
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+type Mjs struct {
|
|
|
|
+ callbackModified func(name string) int64
|
|
|
|
+ callbackContent func(name string) ([]byte, error)
|
|
|
|
+ cache *cache.CacheMap
|
|
|
|
+ durationMax time.Duration
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (s *Mjs) program(name string) (prog *jsProgram, err error) {
|
|
|
|
+ res, check := s.cache.GetCheck(name, func(key, val interface{}, exists bool) (rKey, rVal interface{}, needUpdate bool) {
|
|
|
|
+ log.Println("OK")
|
|
|
|
+ if exists {
|
|
|
|
+
|
|
|
|
+ prog := val.(*jsProgram)
|
|
|
|
+
|
|
|
|
+ if modified := s.callbackModified(name); modified == 0 || modified <= prog.modified {
|
|
|
|
+ needUpdate = false
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ t := s.callbackModified(name)
|
|
|
|
+ if t == 0 {
|
|
|
|
+ err = fmt.Errorf("DOCUMENT %v NOT FOUND", name)
|
|
|
|
+ return nil, nil, false
|
|
|
|
+ }
|
|
|
|
+ var content []byte
|
|
|
|
+ if content, err = s.callbackContent(name); err != nil {
|
|
|
|
+ return nil, nil, false
|
|
|
|
+ }
|
|
|
|
+ item := initProgram(name, string(content), t, s)
|
|
|
|
+ return name, item, true
|
|
|
|
+ })
|
|
|
|
+ if check {
|
|
|
|
+ prog = res.(*jsProgram)
|
|
|
|
+ err = prog.compileErr
|
|
|
|
+ } else {
|
|
|
|
+ err = fmt.Errorf("Document %v is not defined", name)
|
|
|
|
+ }
|
|
|
|
+ return
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (s *Mjs) Exec(name string, data map[string]interface{}) (modified int64, err error) {
|
|
|
|
+
|
|
|
|
+ defer func() {
|
|
|
|
+ if err != nil && err.Error() == "<nil>" {
|
|
|
|
+ err = nil
|
|
|
|
+ }
|
|
|
|
+ if r := recover(); r != nil && fmt.Sprint(r) != "runtime error: invalid memory address or nil pointer dereference" {
|
|
|
|
+ err = fmt.Errorf("%v", r)
|
|
|
|
+ }
|
|
|
|
+ }()
|
|
|
|
+
|
|
|
|
+ var prog *jsProgram
|
|
|
|
+ if prog, err = s.program(name); err != nil {
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ modified = prog.modified
|
|
|
|
+
|
|
|
|
+ vm := goja.New()
|
|
|
|
+ for k, v := range data {
|
|
|
|
+ vm.Set(k, v)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ time.AfterFunc(s.durationMax, func() {
|
|
|
|
+ vm.Interrupt(fmt.Sprintf("Exec script timeout (%v sec.)", s.durationMax/time.Second))
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ var incBefore []string
|
|
|
|
+ mods, currentMod, parentMod := make(map[string]goja.Value), name, ""
|
|
|
|
+
|
|
|
|
+ checkIncBefore := func(name string) bool {
|
|
|
|
+ for _, v := range incBefore {
|
|
|
|
+ if v == name {
|
|
|
|
+ return true
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return false
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ getMod := func(name string) goja.Value {
|
|
|
|
+ if parentMod == name {
|
|
|
|
+ err = fmt.Errorf("include loop detected %v in module %v", name, name)
|
|
|
|
+ return nil
|
|
|
|
+ }
|
|
|
|
+ if mod, check := mods[name]; check {
|
|
|
|
+ return mod
|
|
|
|
+ } else {
|
|
|
|
+ if !checkIncBefore(name) {
|
|
|
|
+ var prog *jsProgram
|
|
|
|
+ if prog, err = s.program(name); err != nil {
|
|
|
|
+ err = fmt.Errorf("%v in module %v", err, parentMod)
|
|
|
|
+ vm.Interrupt(nil)
|
|
|
|
+ return nil
|
|
|
|
+ } else {
|
|
|
|
+ if prog.modified > modified {
|
|
|
|
+ modified = prog.modified
|
|
|
|
+ }
|
|
|
|
+ if _, sErr := vm.RunProgram(prog.prog); sErr == nil {
|
|
|
|
+ incBefore = append(incBefore, name)
|
|
|
|
+ return mods[name]
|
|
|
|
+ } else if err == nil && sErr.Error() != "<nil>" {
|
|
|
|
+ err = fmt.Errorf("%v in module %v", sErr, parentMod)
|
|
|
|
+ }
|
|
|
|
+ return nil
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return nil
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ vm.Set("define", func(vals ...interface{}) {
|
|
|
|
+ fCall, tmp := goja.FunctionCall{}, currentMod
|
|
|
|
+ parentMod = tmp
|
|
|
|
+ for _, v := range vals {
|
|
|
|
+ if call, check := v.(func(goja.FunctionCall) goja.Value); check {
|
|
|
|
+ currentMod = tmp
|
|
|
|
+ parentMod = currentMod
|
|
|
|
+ mods[currentMod] = call(fCall)
|
|
|
|
+ return
|
|
|
|
+ } else {
|
|
|
|
+ currentMod = fmt.Sprint(v)
|
|
|
|
+ fCall.Arguments = append(fCall.Arguments, getMod(currentMod))
|
|
|
|
+ if err != nil && err.Error() != "<nil>" {
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ vm.Set("require", func(vals ...interface{}) {
|
|
|
|
+ fCall := goja.FunctionCall{}
|
|
|
|
+ for _, v := range vals {
|
|
|
|
+ if call, check := v.(func(goja.FunctionCall) goja.Value); check {
|
|
|
|
+ call(fCall)
|
|
|
|
+ return
|
|
|
|
+ } else {
|
|
|
|
+ currentMod = fmt.Sprint(v)
|
|
|
|
+ fCall.Arguments = append(fCall.Arguments, getMod(currentMod))
|
|
|
|
+ if err != nil {
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ vm.Set("initError", func(errText string) error {
|
|
|
|
+ return errors.New(errText)
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ vm.Set("fileExists", func(path string) bool {
|
|
|
|
+ return s.callbackModified(path) > 0
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ vm.Set("include", func(vals ...interface{}) {
|
|
|
|
+ if len(vals) == 0 {
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ name := fmt.Sprint(vals[0])
|
|
|
|
+ params := make(map[string]interface{})
|
|
|
|
+ if len(vals) == 2 {
|
|
|
|
+ if m, check := vals[1].(map[string]interface{}); check {
|
|
|
|
+ params = m
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ var prog *jsProgram
|
|
|
|
+ if prog, err = s.program(name); err == nil {
|
|
|
|
+ tmp := currentMod
|
|
|
|
+ currentMod = prog.name
|
|
|
|
+ if prog.modified > modified {
|
|
|
|
+ modified = prog.modified
|
|
|
|
+ }
|
|
|
|
+ current := make(map[string]interface{})
|
|
|
|
+ for k, v := range params {
|
|
|
|
+ if val := vm.Get(k); val != nil {
|
|
|
|
+ current[k] = val
|
|
|
|
+ }
|
|
|
|
+ vm.Set(k, v)
|
|
|
|
+ }
|
|
|
|
+ if _, sErr := vm.RunProgram(prog.prog); sErr == nil {
|
|
|
|
+ for k, _ := range params {
|
|
|
|
+ if c, check := current[k]; check {
|
|
|
|
+ vm.Set(k, c)
|
|
|
|
+ } else {
|
|
|
|
+ vm.Set(k, nil)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ currentMod = tmp
|
|
|
|
+ } else if err == nil {
|
|
|
|
+ err = sErr
|
|
|
|
+ vm.Interrupt(nil)
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ err = fmt.Errorf("%v in module %v", err, currentMod)
|
|
|
|
+ vm.Interrupt(err)
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ vm.Set("exit", func(vals ...interface{}) {
|
|
|
|
+ vm.Interrupt(err)
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ vm.Set("sleep", func(msec int) {
|
|
|
|
+ log.Println("sleep")
|
|
|
|
+ time.Sleep(time.Millisecond * time.Duration(msec))
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ if _, exErr := vm.RunProgram(prog.prog); exErr != nil && exErr.Error() != "<nil>" && err == nil {
|
|
|
|
+ err = exErr
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return
|
|
|
|
+}
|