| 
					
				 | 
			
			
				@@ -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 { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			// 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{}) (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 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+} 
			 |