/
plugin.go
218 lines (191 loc) · 5.63 KB
/
plugin.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
package engine
import (
"encoding/json"
"net/rpc"
"net/rpc/jsonrpc"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/natefinch/pie"
"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)
}
// 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
}
// May cause a data race
//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)
}
// 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
}
// May cause a data race
//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
}
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
}
// May cause a data race
//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
}))
}