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() == "" { 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() != "" { 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() != "" { 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() != "" && err == nil { err = exErr } return }