123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242 |
- package engine
- import (
- "encoding/json"
- "net/rpc"
- "net/rpc/jsonrpc"
- "os"
- "path/filepath"
- "runtime"
- "strings"
- "github.com/natefinch/pie"
- lua "github.com/xyproto/gopher-lua"
- "github.com/xyproto/textoutput"
- )
- type luaPlugin struct {
- client *rpc.Client
- }
- const namespace = "Lua"
- func (lp *luaPlugin) LuaCode(pluginPath string) (luacode string, err error) {
- return luacode, lp.client.Call(namespace+".Code", pluginPath, &luacode)
- }
- func (lp *luaPlugin) LuaHelp() (luahelp string, err error) {
- return luahelp, lp.client.Call(namespace+".Help", "", &luahelp)
- }
- // LoadPluginFunctions takes a Lua state and a TextOutput
- // (the TextOutput struct should be nil if not in a REPL)
- func (ac *Config) LoadPluginFunctions(L *lua.LState, o *textoutput.TextOutput) {
- // Expose the functionality of a given plugin (executable file).
- // If on Windows, ".exe" is added to the path.
- // Returns true of successful.
- L.SetGlobal("Plugin", L.NewFunction(func(L *lua.LState) int {
- path := L.ToString(1)
- givenPath := path
- if runtime.GOOS == "windows" {
- path = path + ".exe"
- }
- if !ac.fs.Exists(path) {
- path = filepath.Join(ac.serverDirOrFilename, path)
- }
- // Keep the plugin running in the background?
- keepRunning := false
- if L.GetTop() >= 2 {
- keepRunning = L.ToBool(2)
- }
- // Connect with the Plugin
- client, err := pie.StartProviderCodec(jsonrpc.NewClientCodec, os.Stderr, path)
- if err != nil {
- if o != nil {
- o.Err("[Plugin] Could not run plugin!")
- o.Err("Error: " + err.Error())
- }
- L.Push(lua.LBool(false)) // Fail
- return 1 // number of results
- }
- if !keepRunning {
- // Close the client once this function has completed
- defer client.Close()
- }
- p := &luaPlugin{client}
- // Retrieve the Lua code
- luacode, err := p.LuaCode(givenPath)
- if err != nil {
- if o != nil {
- o.Err("[Plugin] Could not call the LuaCode function!")
- o.Err("Error: " + err.Error())
- }
- L.Push(lua.LBool(false)) // Fail
- return 1 // number of results
- }
- // Retrieve the help text
- luahelp, err := p.LuaHelp()
- if err != nil {
- if o != nil {
- o.Err("[Plugin] Could not call the LuaHelp function!")
- o.Err("Error: " + err.Error())
- }
- L.Push(lua.LBool(false)) // Fail
- return 1 // number of results
- }
- // Run luacode on the current LuaState
- luacode = strings.TrimSpace(luacode)
- if L.DoString(luacode) != nil {
- if o != nil {
- o.Err("[Plugin] Error in Lua code provided by plugin!")
- o.Err("Error: " + err.Error())
- }
- L.Push(lua.LBool(false)) // Fail
- return 1 // number of results
- }
- // If in a REPL, output the Plugin help text
- if o != nil {
- luahelp = strings.TrimSpace(luahelp)
- // Add syntax highlighting and output the text
- o.Println(highlight(o, luahelp))
- }
- L.Push(lua.LBool(true)) // Success
- return 1 // number of results
- }))
- // Retrieve the code from the Lua.Code function of the plugin
- L.SetGlobal("PluginCode", L.NewFunction(func(L *lua.LState) int {
- path := L.ToString(1)
- givenPath := path
- if runtime.GOOS == "windows" {
- path = path + ".exe"
- }
- if !ac.fs.Exists(path) {
- path = filepath.Join(ac.serverDirOrFilename, path)
- }
- // Keep the plugin running in the background?
- keepRunning := false
- if L.GetTop() >= 2 {
- keepRunning = L.ToBool(2)
- }
- // Connect with the Plugin
- client, err := pie.StartProviderCodec(jsonrpc.NewClientCodec, os.Stderr, path)
- if err != nil {
- if o != nil {
- o.Err("[PluginCode] Could not run plugin!")
- o.Err("Error: " + err.Error())
- }
- L.Push(lua.LString("")) // Fail
- return 1 // number of results
- }
- if !keepRunning {
- // Close the client once this function has completed
- defer client.Close()
- }
- p := &luaPlugin{client}
- // Retrieve the Lua code
- luacode, err := p.LuaCode(givenPath)
- if err != nil {
- if o != nil {
- o.Err("[PluginCode] Could not call the LuaCode function!")
- o.Err("Error: " + err.Error())
- }
- L.Push(lua.LString("")) // Fail
- return 1 // number of results
- }
- L.Push(lua.LString(luacode))
- return 1 // number of results
- }))
- // Call a function exposed by a plugin (executable file)
- // Returns either nil (fail) or a string (success)
- L.SetGlobal("CallPlugin", L.NewFunction(func(L *lua.LState) int {
- if L.GetTop() < 2 {
- if o != nil {
- o.Err("[CallPlugin] Needs at least 2 arguments")
- }
- L.Push(lua.LString("")) // Fail
- return 1 // number of results
- }
- path := L.ToString(1)
- if runtime.GOOS == "windows" {
- path = path + ".exe"
- }
- if !ac.fs.Exists(path) {
- path = filepath.Join(ac.serverDirOrFilename, path)
- }
- fn := L.ToString(2)
- var args []lua.LValue
- if L.GetTop() > 2 {
- for i := 3; i <= L.GetTop(); i++ {
- args = append(args, L.Get(i))
- }
- }
- // Connect with the Plugin
- logto := os.Stderr
- if o != nil {
- logto = os.Stdout
- }
- // Keep the plugin running in the background?
- keepRunning := false
- client, err := pie.StartProviderCodec(jsonrpc.NewClientCodec, logto, path)
- if err != nil {
- if o != nil {
- o.Err("[CallPlugin] Could not run plugin!")
- o.Err("Error: " + err.Error())
- }
- L.Push(lua.LString("")) // Fail
- return 1 // number of results
- }
- if !keepRunning {
- // Close the client once this function has completed
- defer client.Close()
- }
- jsonargs, err := json.Marshal(args)
- if err != nil {
- if o != nil {
- o.Err("[CallPlugin] Error when marshalling arguments to JSON")
- o.Err("Error: " + err.Error())
- }
- L.Push(lua.LString("")) // Fail
- return 1 // number of results
- }
- // Attempt to call the given function name
- var jsonreply []byte
- if err := client.Call(namespace+"."+fn, jsonargs, &jsonreply); err != nil {
- if o != nil {
- o.Err("[CallPlugin] Error when calling function!")
- o.Err("Function: " + namespace + "." + fn)
- o.Err("JSON Arguments: " + string(jsonargs))
- o.Err("Error: " + err.Error())
- }
- L.Push(lua.LString("")) // Fail
- return 1 // number of results
- }
- L.Push(lua.LString(jsonreply)) // Resulting string
- return 1 // number of results
- }))
- }
|