plugin.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. package engine
  2. import (
  3. "encoding/json"
  4. "net/rpc"
  5. "net/rpc/jsonrpc"
  6. "os"
  7. "path/filepath"
  8. "runtime"
  9. "strings"
  10. "github.com/natefinch/pie"
  11. lua "github.com/xyproto/gopher-lua"
  12. "github.com/xyproto/textoutput"
  13. )
  14. type luaPlugin struct {
  15. client *rpc.Client
  16. }
  17. const namespace = "Lua"
  18. func (lp *luaPlugin) LuaCode(pluginPath string) (luacode string, err error) {
  19. return luacode, lp.client.Call(namespace+".Code", pluginPath, &luacode)
  20. }
  21. func (lp *luaPlugin) LuaHelp() (luahelp string, err error) {
  22. return luahelp, lp.client.Call(namespace+".Help", "", &luahelp)
  23. }
  24. // LoadPluginFunctions takes a Lua state and a TextOutput
  25. // (the TextOutput struct should be nil if not in a REPL)
  26. func (ac *Config) LoadPluginFunctions(L *lua.LState, o *textoutput.TextOutput) {
  27. // Expose the functionality of a given plugin (executable file).
  28. // If on Windows, ".exe" is added to the path.
  29. // Returns true of successful.
  30. L.SetGlobal("Plugin", L.NewFunction(func(L *lua.LState) int {
  31. path := L.ToString(1)
  32. givenPath := path
  33. if runtime.GOOS == "windows" {
  34. path = path + ".exe"
  35. }
  36. if !ac.fs.Exists(path) {
  37. path = filepath.Join(ac.serverDirOrFilename, path)
  38. }
  39. // Keep the plugin running in the background?
  40. keepRunning := false
  41. if L.GetTop() >= 2 {
  42. keepRunning = L.ToBool(2)
  43. }
  44. // Connect with the Plugin
  45. client, err := pie.StartProviderCodec(jsonrpc.NewClientCodec, os.Stderr, path)
  46. if err != nil {
  47. if o != nil {
  48. o.Err("[Plugin] Could not run plugin!")
  49. o.Err("Error: " + err.Error())
  50. }
  51. L.Push(lua.LBool(false)) // Fail
  52. return 1 // number of results
  53. }
  54. if !keepRunning {
  55. // Close the client once this function has completed
  56. defer client.Close()
  57. }
  58. p := &luaPlugin{client}
  59. // Retrieve the Lua code
  60. luacode, err := p.LuaCode(givenPath)
  61. if err != nil {
  62. if o != nil {
  63. o.Err("[Plugin] Could not call the LuaCode function!")
  64. o.Err("Error: " + err.Error())
  65. }
  66. L.Push(lua.LBool(false)) // Fail
  67. return 1 // number of results
  68. }
  69. // Retrieve the help text
  70. luahelp, err := p.LuaHelp()
  71. if err != nil {
  72. if o != nil {
  73. o.Err("[Plugin] Could not call the LuaHelp function!")
  74. o.Err("Error: " + err.Error())
  75. }
  76. L.Push(lua.LBool(false)) // Fail
  77. return 1 // number of results
  78. }
  79. // Run luacode on the current LuaState
  80. luacode = strings.TrimSpace(luacode)
  81. if L.DoString(luacode) != nil {
  82. if o != nil {
  83. o.Err("[Plugin] Error in Lua code provided by plugin!")
  84. o.Err("Error: " + err.Error())
  85. }
  86. L.Push(lua.LBool(false)) // Fail
  87. return 1 // number of results
  88. }
  89. // If in a REPL, output the Plugin help text
  90. if o != nil {
  91. luahelp = strings.TrimSpace(luahelp)
  92. // Add syntax highlighting and output the text
  93. o.Println(highlight(o, luahelp))
  94. }
  95. L.Push(lua.LBool(true)) // Success
  96. return 1 // number of results
  97. }))
  98. // Retrieve the code from the Lua.Code function of the plugin
  99. L.SetGlobal("PluginCode", L.NewFunction(func(L *lua.LState) int {
  100. path := L.ToString(1)
  101. givenPath := path
  102. if runtime.GOOS == "windows" {
  103. path = path + ".exe"
  104. }
  105. if !ac.fs.Exists(path) {
  106. path = filepath.Join(ac.serverDirOrFilename, path)
  107. }
  108. // Keep the plugin running in the background?
  109. keepRunning := false
  110. if L.GetTop() >= 2 {
  111. keepRunning = L.ToBool(2)
  112. }
  113. // Connect with the Plugin
  114. client, err := pie.StartProviderCodec(jsonrpc.NewClientCodec, os.Stderr, path)
  115. if err != nil {
  116. if o != nil {
  117. o.Err("[PluginCode] Could not run plugin!")
  118. o.Err("Error: " + err.Error())
  119. }
  120. L.Push(lua.LString("")) // Fail
  121. return 1 // number of results
  122. }
  123. if !keepRunning {
  124. // Close the client once this function has completed
  125. defer client.Close()
  126. }
  127. p := &luaPlugin{client}
  128. // Retrieve the Lua code
  129. luacode, err := p.LuaCode(givenPath)
  130. if err != nil {
  131. if o != nil {
  132. o.Err("[PluginCode] Could not call the LuaCode function!")
  133. o.Err("Error: " + err.Error())
  134. }
  135. L.Push(lua.LString("")) // Fail
  136. return 1 // number of results
  137. }
  138. L.Push(lua.LString(luacode))
  139. return 1 // number of results
  140. }))
  141. // Call a function exposed by a plugin (executable file)
  142. // Returns either nil (fail) or a string (success)
  143. L.SetGlobal("CallPlugin", L.NewFunction(func(L *lua.LState) int {
  144. if L.GetTop() < 2 {
  145. if o != nil {
  146. o.Err("[CallPlugin] Needs at least 2 arguments")
  147. }
  148. L.Push(lua.LString("")) // Fail
  149. return 1 // number of results
  150. }
  151. path := L.ToString(1)
  152. if runtime.GOOS == "windows" {
  153. path = path + ".exe"
  154. }
  155. if !ac.fs.Exists(path) {
  156. path = filepath.Join(ac.serverDirOrFilename, path)
  157. }
  158. fn := L.ToString(2)
  159. var args []lua.LValue
  160. if L.GetTop() > 2 {
  161. for i := 3; i <= L.GetTop(); i++ {
  162. args = append(args, L.Get(i))
  163. }
  164. }
  165. // Connect with the Plugin
  166. logto := os.Stderr
  167. if o != nil {
  168. logto = os.Stdout
  169. }
  170. // Keep the plugin running in the background?
  171. keepRunning := false
  172. client, err := pie.StartProviderCodec(jsonrpc.NewClientCodec, logto, path)
  173. if err != nil {
  174. if o != nil {
  175. o.Err("[CallPlugin] Could not run plugin!")
  176. o.Err("Error: " + err.Error())
  177. }
  178. L.Push(lua.LString("")) // Fail
  179. return 1 // number of results
  180. }
  181. if !keepRunning {
  182. // Close the client once this function has completed
  183. defer client.Close()
  184. }
  185. jsonargs, err := json.Marshal(args)
  186. if err != nil {
  187. if o != nil {
  188. o.Err("[CallPlugin] Error when marshalling arguments to JSON")
  189. o.Err("Error: " + err.Error())
  190. }
  191. L.Push(lua.LString("")) // Fail
  192. return 1 // number of results
  193. }
  194. // Attempt to call the given function name
  195. var jsonreply []byte
  196. if err := client.Call(namespace+"."+fn, jsonargs, &jsonreply); err != nil {
  197. if o != nil {
  198. o.Err("[CallPlugin] Error when calling function!")
  199. o.Err("Function: " + namespace + "." + fn)
  200. o.Err("JSON Arguments: " + string(jsonargs))
  201. o.Err("Error: " + err.Error())
  202. }
  203. L.Push(lua.LString("")) // Fail
  204. return 1 // number of results
  205. }
  206. L.Push(lua.LString(jsonreply)) // Resulting string
  207. return 1 // number of results
  208. }))
  209. }