123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233 |
- package mjs
- import (
- "context"
- "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.NewCacheMap(context.Background(), 0, time.Hour*6),
- }
- }
- type Mjs struct {
- callbackModified func(name string) int64
- callbackContent func(name string) ([]byte, error)
- cache *cache.CacheMap
- }
- 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) {
- if exists {
- // check update need
- prog := val.(*jsProgram)
- // Why modified > prog.modified???
- 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{}, durationMax time.Duration) (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)
- }
- if durationMax > 0 {
- time.AfterFunc(durationMax, func() {
- vm.Interrupt(fmt.Sprintf("Exec script timeout (%v sec.)", 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("runtime", vm)
- vm.Set("log", func(vals ...interface{}) {
- log.Println(vals...)
- })
- 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) {
- time.Sleep(time.Millisecond * time.Duration(msec))
- })
- if _, exErr := vm.RunProgram(prog.prog); exErr != nil && exErr.Error() != "<nil>" && err == nil {
- err = exErr
- }
- return
- }
|