-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathbapil
278 lines (232 loc) · 8.71 KB
/
bapil
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
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
--[[
BAPIL - Blocking API Loader - 2013 Sangar
This program is licensed under the MIT license.
http://opensource.org/licenses/mit-license.php
Pronounced "Bapple" (http://youtu.be/vE3roH38yks), this API provides
replacements for the original API loading and unloading facilities.
loadAPI blocks instead of printing an error if an API is already in the
process of being loaded from another coroutine. It's possible to provide a
timout. Also, it will not load an API again if it was already loaded, use
reloadAPI for that. It furthermore loads all APIs via a protected call,
which means you don't have to reboot the machine if an API fails loading.
Lastly, unloadAPI also resolves paths.
]]
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
-- API Loader API --
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
-- If this API was loaded before, reuse it to avoid loosing our state.
if bapil then
local env = getfenv()
for k, v in pairs(bapil) do
env[k] = v
end
return
end
-- Namespace forward declarations.
local private
-------------------------------------------------------------------------------
-- Public API --
-------------------------------------------------------------------------------
-- The current version of the API.
version = "1.0"
--[[
Alternative API loader with a few features the built-in one is missing.
This will block if the API is currently being loaded by another coroutine.
Note that this also means it'll be possible to create deadlocks (in the
most basic case by an API loading itself), so you'll have to make sure
there aren't any. Or use a timeout.
This will also try to resolve the specified path using the global path
variable (see path() and setPath()), if it's not an absolute path.
@param path the path to the API file to load.
@param timeout an optional timeout to wait if the API is already being
loaded by another coroutine. Set this to zero to return immediately if
the API is already being loaded by another coroutine.
@return true on success; (false, reason) on failure.
]]
function loadAPI(path, timeout)
-- Resolve the path and get the API's name from the file name.
path = resolveAPI(path)
local name = fs.getName(path):gsub("%.lua$", "")
-- Check if the API is already loaded.
if _G[name] then
if type(_G[name]) == "table" then
return true
else
return false, "trying to override global variable"
end
end
-- Thread guard. Wait if another coroutine already is loading this API.
timeout = timeout or math.huge
while private.loadingAPIs[name] and timeout > 0 do
timeout = timeout - 0.1
os.sleep(0.1)
end
if private.loadingAPIs[name] then
return false, "timeout"
end
-- If the API was loaded successfully by the other coroutine we are done.
-- Otherwise we can't be sure the other coroutine didn't just unload the
-- API again, so we'll have to try loading it again.
if _G[name] then
return true
end
-- Begin loading the API. Aquire lock.
private.loadingAPIs[name] = true
-- Wrapping it in a function for more linear indentation.
local result, reason
(function()
-- Try to load the file.
result, reason = loadfile(path)
if not result then
return
end
-- Try to execute the script. This can yield.
local environment = {__bapil__ = true}
local retval
setfenv(result, setmetatable(environment, {__index = _G}))
result, retval = pcall(result)
if not result then
reason = retval
return
end
local api
if retval == nil then
-- Great! Set the environment as the API's entry. Since os.loadAPI
-- copies all entries to a new table we do this here, too, though I
-- don't really see the point (but this ignores the tables metatable,
-- so we'd get different behavior if we skip this, which would be bad).
api = {}
for k, v in pairs(environment) do
api[k] = v
end
elseif type(retval) == "table" then
-- If the script returns table, use it as api.
api = retval
elseif type(retval) == "function" then
-- As api can be only table, let's wrap returned function to table
-- with metatable.
api = {}
api[name] = retval
setmetatable(api, {
__call = function (_, ...)
return retval(...)
end
})
else
result = false
reason = "attempt use " .. type(retval) .. " as api"
return
end
_G[name] = api
if type(api) == "table" then
-- If api has __load__ function, call it with the global environment.
local __load__ = api.__load__
if __load__ then
setfenv(__load__, _G)
__load__()
end
end
end)()
-- Done loading the API. Release lock.
private.loadingAPIs[name] = false
return result, reason
end
--[[
Unloads a global API.
@param the path to or name of the API to unload.
]]
function unloadAPI(path)
local name = fs.getName(path)
local api = _G[name]
if api and type(api) == "table" then
-- If api has __unload__ function, call it with the global environment.
local __unload__ = api.__unload__
if __unload__ then
setfenv(__unload__, _G)
__unload__()
end
end
os.unloadAPI(name)
end
--[[
Reloads an API.
This simply unloads and then loads the API again. It's only provided
because the original os.loadAPI() would always reload an API, so we want to
provide a similarly convenient alternative.
@param path the path to or name of the API.
@param timeout same as for bapil.loadAPI().
@return true on success; (false, reason) on failure.
]]
function reloadAPI(path, timeout)
unloadAPI(path)
return loadAPI(path, timeout)
end
--[[
The current path environment in which to look for APIs.
This is like shell.path() but for APIs loaded via this API. It's a string
of paths separated by colons in which to search for APIs.
@return the current path environment.
]]
function path()
return private.path
end
--[[
Set the path environment in which to look for APIs.
@param path the new path environment.
@see bapil.path()
]]
function setPath(path)
private.path = path
end
--[[
Tries to resolve a path using our internal path variable.
This is essentially a simplified version of what shell.resolveProgram()
does, but for APIs.
@param the path or name of the API.
@return the resolved path, which may be the same as the specified one.
]]
function resolveAPI(name)
if string.sub(name, 1, 1) == '/' or string.sub(name, 1, 1) == '\\' then
return name
end
for path in string.gmatch(private.path, "[^:]+") do
path = fs.combine(path, name)
if fs.exists(path) and not fs.isDir(path) then
return path
end
end
return name
end
--[[
Replaces the API loading related functions in the OS API.
@param restore whether to restore the original OS API functions.
]]
function hijackOSAPI(restore)
if restore then
if not os._bapil then return end
os.loadAPI = os._bapil.loadAPI
os.unloadAPI = os._bapil.unloadAPI
os._bapil = nil
else
if os._bapil then return end
os._bapil = {
loadAPI = os.loadAPI,
unloadAPI = os.unloadAPI
}
os.loadAPI = loadAPI
os.unloadAPI = unloadAPI
end
end
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
-- Internals --
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
-- Private namespace.
private = {}
-- The list of paths we can use to search for APIs when given relative paths.
private.path = "/rom/apis" .. (turtle and ":/rom/apis/turtle" or "") .. ":/apis"
-- Table of currently loading APIs.
private.loadingAPIs = {}