gojs/gojs.go

210 lines
5.2 KiB
Go
Raw Permalink Normal View History

2024-02-17 12:55:08 +08:00
package gojs
import (
"apigo.cloud/git/apigo/plugin"
"apigo.cloud/git/apigo/qjs"
"errors"
2024-02-18 13:20:58 +08:00
"fmt"
2024-02-17 12:55:08 +08:00
"github.com/ssgo/log"
"github.com/ssgo/u"
"regexp"
"strings"
)
var pluginNameMatcher = regexp.MustCompile(`(\w+?)\.`)
2024-02-18 13:20:58 +08:00
type JSRuntime struct {
freeJsValues []quickjs.Value
rt quickjs.Runtime
JsCtx *quickjs.Context
GoCtx *plugin.Context
logger *log.Logger
plugins map[string]*plugin.Plugin
}
2024-02-17 12:55:08 +08:00
2024-02-18 13:20:58 +08:00
func (rt *JSRuntime) Close() {
for _, v := range rt.freeJsValues {
v.Free()
}
rt.freeJsValues = make([]quickjs.Value, 0)
rt.JsCtx.Close()
rt.rt.Close()
}
func (rt *JSRuntime) initCode(code string) {
2024-02-17 12:55:08 +08:00
tryPlugins := map[string]bool{}
for _, m := range pluginNameMatcher.FindAllStringSubmatch(code, 1024) {
tryPlugins[m[1]] = true
}
for _, plg := range plugin.List() {
2024-02-18 13:20:58 +08:00
if tryPlugins[plg.Id] && rt.plugins[plg.Id] == nil {
rt.plugins[plg.Id] = &plg
rt.JsCtx.Globals().Set(plg.Id, MakeJsValueForPlugin(rt.GoCtx, plg.Objects, plg.Id, false))
2024-02-17 12:55:08 +08:00
if plg.JsCode != "" {
2024-02-18 13:20:58 +08:00
if result, err := rt.JsCtx.Eval(plg.JsCode); err != nil {
2024-02-17 12:55:08 +08:00
stack := GetJSError(err, plg.JsCode)
2024-02-18 13:20:58 +08:00
rt.logger.Error(err.Error(), "stack", stack)
2024-02-17 12:55:08 +08:00
} else {
result.Free()
}
}
}
}
2024-02-18 13:20:58 +08:00
}
2024-02-17 12:55:08 +08:00
2024-02-18 13:20:58 +08:00
func (rt *JSRuntime) run(code string) (out interface{}, err error, stack string) {
if r, err := rt.JsCtx.Eval(code); err == nil {
2024-02-17 12:55:08 +08:00
result := MakeFromJsValue(r)
r.Free()
2024-02-18 13:20:58 +08:00
return result, nil, ""
2024-02-17 12:55:08 +08:00
} else {
// 检查错误
stack := GetJSError(err, code)
2024-02-18 13:20:58 +08:00
rt.logger.Error(err.Error(), "stack", stack)
return nil, err, stack
2024-02-17 12:55:08 +08:00
}
}
2024-02-18 13:20:58 +08:00
func (rt *JSRuntime) Exec(code string) (err error, stack string) {
rt.initCode(code)
_, err, stack = rt.run(code)
return err, stack
}
func (rt *JSRuntime) Run(code string) (out interface{}, err error, stack string) {
rt.initCode(code)
return rt.run("(function(){" + code + "})()")
}
func SetPluginsConfig(conf map[string]plugin.Config) {
for _, plg := range plugin.List() {
if plg.Init != nil {
plg.Init(conf[plg.Id])
}
}
}
func New(globals map[string]interface{}, logger *log.Logger) *JSRuntime {
if logger == nil {
logger = log.DefaultLogger
}
// 初始化JS虚拟机
jsRt := quickjs.NewRuntime()
jsCtx := jsRt.NewContext()
goCtx := plugin.NewContext(map[string]interface{}{
"*log.Logger": logger,
"*quickjs.Context": jsCtx,
})
rt := &JSRuntime{
freeJsValues: make([]quickjs.Value, 0),
rt: jsRt,
JsCtx: jsCtx,
GoCtx: goCtx,
logger: logger,
plugins: map[string]*plugin.Plugin{},
}
rt.GoCtx.SetData("_freeJsValues", &rt.freeJsValues)
// 全局变量
if globals != nil {
for k, obj := range globals {
rt.JsCtx.Globals().Set(k, MakeJsValue(rt.GoCtx, obj, false))
}
}
// 注入 console
rt.JsCtx.Globals().Set("console", MakeJsValue(rt.GoCtx, map[string]interface{}{
"log": func(args ...interface{}) {
2024-03-08 11:45:13 +08:00
fmt.Println(makeStringArray(args, u.TextNone, u.BgNone)...)
2024-02-18 13:20:58 +08:00
},
"info": func(args ...interface{}) {
2024-03-08 11:45:13 +08:00
fmt.Println(makeStringArray(args, u.TextCyan, u.BgNone)...)
2024-02-18 13:20:58 +08:00
},
"warn": func(args ...interface{}) {
2024-03-08 11:45:13 +08:00
fmt.Println(makeStringArray(args, u.TextBlack, u.BgYellow)...)
2024-02-18 13:20:58 +08:00
},
"error": func(args ...interface{}) {
2024-03-08 11:45:13 +08:00
fmt.Println(makeStringArray(args, u.TextWhite, u.BgRed)...)
},
}, false))
// 注入 logger
rt.JsCtx.Globals().Set("logger", MakeJsValue(rt.GoCtx, map[string]interface{}{
"debug": func(message string, args *map[string]interface{}) {
rt.logger.Debug(message, makeMapToArray(args)...)
},
"info": func(message string, args *map[string]interface{}) {
rt.logger.Info(message, makeMapToArray(args)...)
},
"warn": func(message string, args *map[string]interface{}) {
rt.logger.Warning(message, makeMapToArray(args)...)
},
"error": func(message string, args *map[string]interface{}) {
rt.logger.Error(message, makeMapToArray(args)...)
2024-02-18 13:20:58 +08:00
},
}, false))
return rt
}
2024-03-08 11:45:13 +08:00
func makeMapToArray(args *map[string]interface{}) []interface{} {
outArgs := make([]interface{}, 0)
if args != nil {
for k, v := range *args {
outArgs = append(outArgs, k, v)
}
}
return outArgs
}
func makeStringArray(args []interface{}, color u.TextColor, bg u.BgColor) []interface{} {
stringArgs := make([]interface{}, len(args))
for i, v := range args {
if color != u.TextNone || bg != u.BgNone {
stringArgs[i] = u.Color(u.StringP(v), color, bg)
} else {
stringArgs[i] = u.StringP(v)
}
}
return stringArgs
}
2024-02-18 13:20:58 +08:00
func Run(code string, globals map[string]interface{}, logger *log.Logger) (out interface{}, err error, stack string) {
rt := New(globals, logger)
defer func() {
if err := recover(); err != nil {
rt.logger.Error(u.String(err))
}
rt.Close()
}()
return rt.Run(code)
}
2024-02-17 12:55:08 +08:00
var jsErrorCodeMatcher = regexp.MustCompile(`code:(\d+)`)
func GetJSError(err error, code string) string {
if err != nil {
var jsErr *quickjs.Error
if errors.As(err, &jsErr) {
// 在错误信息中加入代码
codeLines := strings.Split(code, "\n")
return jsErrorCodeMatcher.ReplaceAllStringFunc(jsErr.Stack, func(s2 string) string {
errorLineNumber := u.Int(jsErrorCodeMatcher.FindStringSubmatch(s2)[1])
errorLine := ""
if len(codeLines) >= errorLineNumber {
errorLine = codeLines[errorLineNumber-1]
}
return s2 + " ```" + errorLine + "```"
})
} else {
return err.Error()
}
} else {
return ""
}
}