000x000 2 yıl önce
işleme
f37d0db7df
8 değiştirilmiş dosya ile 370 ekleme ve 0 silme
  1. 21 0
      LICENSE
  2. 5 0
      content/inc
  3. 14 0
      content/main
  4. 8 0
      go.mod
  5. 29 0
      go.sum
  6. 232 0
      mjs.go
  7. 23 0
      program.go
  8. 38 0
      z_test.go

+ 21 - 0
LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 fcg-xvii
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 5 - 0
content/inc

@@ -0,0 +1,5 @@
+flush("iiiin", one)
+//include("inc2", {})
+
+//flush(initError('aaaaaa'))
+

+ 14 - 0
content/main

@@ -0,0 +1,14 @@
+flush("!!!!!")
+
+include("inc", {
+	one: 1,
+	two: 2
+})
+
+//flush("OKKO", fileExists("/tmp"))
+
+sleep(2000)
+
+//flush(initError('aaaaa'))
+
+//flush(one)

+ 8 - 0
go.mod

@@ -0,0 +1,8 @@
+module github.com/fcg-xvii/mjs
+
+go 1.12
+
+require (
+	git.ali33.ru/fcg-xvii/go-tools v0.0.0-20220506161429-319953bb5590 // indirect
+	github.com/dop251/goja v0.0.0-20220516123900-4418d4575a41 // indirect
+)

+ 29 - 0
go.sum

@@ -0,0 +1,29 @@
+git.ali33.ru/fcg-xvii/go-tools v0.0.0-20220506161429-319953bb5590 h1:e7c8b9TovkJN/cmAFYoNmiYCyjGd16HjXL3D/bG1ZCo=
+git.ali33.ru/fcg-xvii/go-tools v0.0.0-20220506161429-319953bb5590/go.mod h1:8XpQShSOR7fAiCJg56M2mhf1KxiqnTeGttW/CUuyyCk=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 h1:Izz0+t1Z5nI16/II7vuEo/nHjodOg0p7+OiDpjX5t1E=
+github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
+github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk=
+github.com/dop251/goja v0.0.0-20220516123900-4418d4575a41 h1:yRPjAkkuR/E/tsVG7QmhzEeEtD3P2yllxsT1/ftURb0=
+github.com/dop251/goja v0.0.0-20220516123900-4418d4575a41/go.mod h1:TQJQ+ZNyFVvUtUEtCZxBhfWiH7RJqR3EivNmvD6Waik=
+github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y=
+github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d/go.mod h1:DngW8aVqWbuLRMHItjPUyqdj+HWPvnQe8V8y1nDpIbM=
+github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
+github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

+ 232 - 0
mjs.go

@@ -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
+}

+ 23 - 0
program.go

@@ -0,0 +1,23 @@
+package mjs
+
+import (
+	"github.com/dop251/goja"
+)
+
+func initProgram(name, content string, modified int64, engine *Mjs) *jsProgram {
+	prog, err := goja.Compile(name, content, true)
+	return &jsProgram{
+		name:       name,
+		prog:       prog,
+		modified:   modified,
+		compileErr: err,
+	}
+}
+
+type jsProgram struct {
+	name       string
+	prog       *goja.Program
+	modified   int64
+	engine     *Mjs
+	compileErr error
+}

+ 38 - 0
z_test.go

@@ -0,0 +1,38 @@
+package mjs
+
+import (
+	"fmt"
+	"io/ioutil"
+	"log"
+	"os"
+	"testing"
+)
+
+var contentPath = "content"
+
+func filePath(name string) string {
+	return fmt.Sprintf("%v/%v", contentPath, name)
+}
+
+func modified(name string) (res int64) {
+	if info, err := os.Stat(filePath(name)); err == nil {
+		res = info.ModTime().Unix()
+	}
+	return
+}
+
+func content(name string) ([]byte, error) {
+	return ioutil.ReadFile(filePath(name))
+}
+
+func TestMJS(t *testing.T) {
+	mjs := New(modified, content)
+	for i := 0; i < 20; i++ {
+		modified, err := mjs.Exec("main", map[string]interface{}{
+			"flush": func(args ...interface{}) {
+				log.Println(args)
+			},
+		})
+		t.Log(err, err == nil, modified)
+	}
+}