2019-07-09 07:04:49 +00:00
-- Load in expect from the module path.
2019-05-30 18:36:28 +00:00
--
2019-07-09 07:04:49 +00:00
-- Ideally we'd use require, but that is part of the shell, and so is not
-- available to the BIOS or any APIs. All APIs load this using dofile, but that
-- has not been defined at this point.
local expect
do
2019-07-27 10:34:36 +00:00
local h = fs.open ( " rom/modules/main/cc/expect.lua " , " r " )
2019-07-09 07:04:49 +00:00
local f , err = loadstring ( h.readAll ( ) , " @expect.lua " )
h.close ( )
if not f then error ( err ) end
expect = f ( ) . expect
2019-05-30 18:36:28 +00:00
end
2017-05-01 13:32:39 +00:00
if _VERSION == " Lua 5.1 " then
-- If we're on Lua 5.1, install parts of the Lua 5.2/5.3 API so that programs can be written against it
2019-03-10 12:24:55 +00:00
local type = type
2017-05-01 13:32:39 +00:00
local nativeload = load
local nativeloadstring = loadstring
local nativesetfenv = setfenv
2019-03-10 12:24:55 +00:00
2020-04-10 09:27:53 +00:00
-- Historically load/loadstring would handle the chunk name as if it has
2019-03-10 12:24:55 +00:00
-- been prefixed with "=". We emulate that behaviour here.
local function prefix ( chunkname )
if type ( chunkname ) ~= " string " then return chunkname end
local head = chunkname : sub ( 1 , 1 )
if head == " = " or head == " @ " then
return chunkname
else
return " = " .. chunkname
end
end
2020-04-18 09:09:40 +00:00
function load ( x , name , mode , env )
2019-05-30 18:36:28 +00:00
expect ( 1 , x , " function " , " string " )
expect ( 2 , name , " string " , " nil " )
expect ( 3 , mode , " string " , " nil " )
expect ( 4 , env , " table " , " nil " )
2020-04-18 09:09:40 +00:00
local ok , p1 , p2 = pcall ( function ( )
2017-05-01 13:32:39 +00:00
if type ( x ) == " string " then
2020-04-18 09:09:40 +00:00
local result , err = nativeloadstring ( x , name )
2017-05-01 13:32:39 +00:00
if result then
if env then
env._ENV = env
2020-04-18 09:09:40 +00:00
nativesetfenv ( result , env )
2017-05-01 13:32:39 +00:00
end
return result
else
return nil , err
end
else
2020-04-18 09:09:40 +00:00
local result , err = nativeload ( x , name )
2017-05-01 13:32:39 +00:00
if result then
if env then
env._ENV = env
2020-04-18 09:09:40 +00:00
nativesetfenv ( result , env )
2017-05-01 13:32:39 +00:00
end
return result
else
return nil , err
end
end
2020-04-18 09:09:40 +00:00
end )
2017-05-01 13:32:39 +00:00
if ok then
return p1 , p2
else
2020-04-18 09:09:40 +00:00
error ( p1 , 2 )
2018-12-17 17:22:15 +00:00
end
2017-05-01 13:32:39 +00:00
end
if _CC_DISABLE_LUA51_FEATURES then
-- Remove the Lua 5.1 features that will be removed when we update to Lua 5.2, for compatibility testing.
-- See "disable_lua51_functions" in ComputerCraft.cfg
setfenv = nil
getfenv = nil
loadstring = nil
unpack = nil
math.log10 = nil
table.maxn = nil
2017-05-31 08:10:56 +00:00
else
2020-04-18 09:09:40 +00:00
loadstring = function ( string , chunkname ) return nativeloadstring ( string , prefix ( chunkname ) ) end
2019-03-10 12:24:55 +00:00
2017-05-31 08:10:56 +00:00
-- Inject a stub for the old bit library
_G.bit = {
bnot = bit32.bnot ,
band = bit32.band ,
bor = bit32.bor ,
bxor = bit32.bxor ,
brshift = bit32.arshift ,
blshift = bit32.lshift ,
2019-12-07 10:33:47 +00:00
blogic_rshift = bit32.rshift ,
2017-05-31 08:10:56 +00:00
}
2017-05-01 13:32:39 +00:00
end
end
-- Install lua parts of the os api
function os . version ( )
2017-05-06 21:26:45 +00:00
return " CraftOS 1.8 "
2017-05-01 13:32:39 +00:00
end
2020-04-18 09:09:40 +00:00
function os . pullEventRaw ( sFilter )
return coroutine.yield ( sFilter )
2017-05-01 13:32:39 +00:00
end
2020-04-18 09:09:40 +00:00
function os . pullEvent ( sFilter )
local eventData = table.pack ( os.pullEventRaw ( sFilter ) )
2017-05-01 13:32:39 +00:00
if eventData [ 1 ] == " terminate " then
2020-04-18 09:09:40 +00:00
error ( " Terminated " , 0 )
2017-05-01 13:32:39 +00:00
end
2020-04-18 09:09:40 +00:00
return table.unpack ( eventData , 1 , eventData.n )
2017-05-01 13:32:39 +00:00
end
-- Install globals
2020-04-18 09:09:40 +00:00
function sleep ( nTime )
2019-05-30 18:36:28 +00:00
expect ( 1 , nTime , " number " , " nil " )
2020-04-18 09:09:40 +00:00
local timer = os.startTimer ( nTime or 0 )
2017-05-01 13:32:39 +00:00
repeat
2020-04-18 09:09:40 +00:00
local _ , param = os.pullEvent ( " timer " )
2017-05-01 13:32:39 +00:00
until param == timer
end
2020-04-18 09:09:40 +00:00
function write ( sText )
2019-05-30 18:36:28 +00:00
expect ( 1 , sText , " string " , " number " )
2017-06-18 15:48:07 +00:00
2019-12-07 10:33:47 +00:00
local w , h = term.getSize ( )
local x , y = term.getCursorPos ( )
2018-12-17 17:22:15 +00:00
2017-05-01 13:32:39 +00:00
local nLinesPrinted = 0
local function newLine ( )
if y + 1 <= h then
term.setCursorPos ( 1 , y + 1 )
else
term.setCursorPos ( 1 , h )
term.scroll ( 1 )
end
x , y = term.getCursorPos ( )
nLinesPrinted = nLinesPrinted + 1
end
2018-12-17 17:22:15 +00:00
2017-05-01 13:32:39 +00:00
-- Print the line with proper word wrapping
2020-01-01 09:01:00 +00:00
sText = tostring ( sText )
2019-12-10 18:54:43 +00:00
while # sText > 0 do
2020-04-18 09:09:40 +00:00
local whitespace = string.match ( sText , " ^[ \t ]+ " )
2017-05-01 13:32:39 +00:00
if whitespace then
-- Print whitespace
2020-04-18 09:09:40 +00:00
term.write ( whitespace )
2019-12-07 10:33:47 +00:00
x , y = term.getCursorPos ( )
2020-04-18 09:09:40 +00:00
sText = string.sub ( sText , # whitespace + 1 )
2017-05-01 13:32:39 +00:00
end
2018-12-17 17:22:15 +00:00
2020-04-18 09:09:40 +00:00
local newline = string.match ( sText , " ^ \n " )
2017-05-01 13:32:39 +00:00
if newline then
-- Print newlines
newLine ( )
2020-04-18 09:09:40 +00:00
sText = string.sub ( sText , 2 )
2017-05-01 13:32:39 +00:00
end
2018-12-17 17:22:15 +00:00
2020-04-18 09:09:40 +00:00
local text = string.match ( sText , " ^[^ \t \n ]+ " )
2017-05-01 13:32:39 +00:00
if text then
2020-04-18 09:09:40 +00:00
sText = string.sub ( sText , # text + 1 )
2019-12-10 18:54:43 +00:00
if # text > w then
2018-12-17 17:22:15 +00:00
-- Print a multiline word
2019-12-10 18:54:43 +00:00
while # text > 0 do
2017-05-01 13:32:39 +00:00
if x > w then
newLine ( )
end
2020-04-18 09:09:40 +00:00
term.write ( text )
text = string.sub ( text , w - x + 2 )
2019-12-07 10:33:47 +00:00
x , y = term.getCursorPos ( )
2017-05-01 13:32:39 +00:00
end
else
-- Print a word normally
2019-12-10 18:54:43 +00:00
if x + # text - 1 > w then
2017-05-01 13:32:39 +00:00
newLine ( )
end
2020-04-18 09:09:40 +00:00
term.write ( text )
2019-12-07 10:33:47 +00:00
x , y = term.getCursorPos ( )
2017-05-01 13:32:39 +00:00
end
end
end
2018-12-17 17:22:15 +00:00
2017-05-01 13:32:39 +00:00
return nLinesPrinted
end
2020-04-18 09:09:40 +00:00
function print ( ... )
2017-05-01 13:32:39 +00:00
local nLinesPrinted = 0
2020-04-09 21:15:06 +00:00
local nLimit = select ( " # " , ... )
2017-05-01 13:32:39 +00:00
for n = 1 , nLimit do
2020-04-18 09:09:40 +00:00
local s = tostring ( select ( n , ... ) )
2017-05-01 13:32:39 +00:00
if n < nLimit then
s = s .. " \t "
end
2020-04-18 09:09:40 +00:00
nLinesPrinted = nLinesPrinted + write ( s )
2017-05-01 13:32:39 +00:00
end
2020-04-18 09:09:40 +00:00
nLinesPrinted = nLinesPrinted + write ( " \n " )
2017-05-01 13:32:39 +00:00
return nLinesPrinted
end
2020-04-18 09:09:40 +00:00
function printError ( ... )
2017-05-01 13:32:39 +00:00
local oldColour
if term.isColour ( ) then
oldColour = term.getTextColour ( )
2020-04-18 09:09:40 +00:00
term.setTextColour ( colors.red )
2017-05-01 13:32:39 +00:00
end
2020-04-18 09:09:40 +00:00
print ( ... )
2017-05-01 13:32:39 +00:00
if term.isColour ( ) then
2020-04-18 09:09:40 +00:00
term.setTextColour ( oldColour )
2017-05-01 13:32:39 +00:00
end
end
2020-04-18 09:09:40 +00:00
function read ( _sReplaceChar , _tHistory , _fnComplete , _sDefault )
2019-05-30 18:36:28 +00:00
expect ( 1 , _sReplaceChar , " string " , " nil " )
expect ( 2 , _tHistory , " table " , " nil " )
expect ( 3 , _fnComplete , " function " , " nil " )
expect ( 4 , _sDefault , " string " , " nil " )
2020-04-18 09:09:40 +00:00
term.setCursorBlink ( true )
2017-05-01 13:32:39 +00:00
2017-06-15 12:50:47 +00:00
local sLine
2020-04-18 09:09:40 +00:00
if type ( _sDefault ) == " string " then
2017-06-15 12:50:47 +00:00
sLine = _sDefault
else
sLine = " "
end
2017-05-01 13:32:39 +00:00
local nHistoryPos
2019-11-29 20:15:58 +00:00
local nPos , nScroll = # sLine , 0
2017-05-01 13:32:39 +00:00
if _sReplaceChar then
2020-04-18 09:09:40 +00:00
_sReplaceChar = string.sub ( _sReplaceChar , 1 , 1 )
2017-05-01 13:32:39 +00:00
end
local tCompletions
local nCompletion
local function recomplete ( )
2019-12-10 18:54:43 +00:00
if _fnComplete and nPos == # sLine then
2020-04-18 09:09:40 +00:00
tCompletions = _fnComplete ( sLine )
2017-05-01 13:32:39 +00:00
if tCompletions and # tCompletions > 0 then
nCompletion = 1
else
nCompletion = nil
end
else
tCompletions = nil
nCompletion = nil
end
end
local function uncomplete ( )
tCompletions = nil
nCompletion = nil
end
local w = term.getSize ( )
local sx = term.getCursorPos ( )
2020-04-18 09:09:40 +00:00
local function redraw ( _bClear )
2019-11-29 20:15:58 +00:00
local cursor_pos = nPos - nScroll
if sx + cursor_pos >= w then
-- We've moved beyond the RHS, ensure we're on the edge.
nScroll = sx + nPos - w
elseif cursor_pos < 0 then
-- We've moved beyond the LHS, ensure we're on the edge.
nScroll = nPos
2017-05-01 13:32:39 +00:00
end
2019-11-23 13:21:07 +00:00
local _ , cy = term.getCursorPos ( )
2020-04-18 09:09:40 +00:00
term.setCursorPos ( sx , cy )
2019-12-03 23:26:13 +00:00
local sReplace = _bClear and " " or _sReplaceChar
2017-05-01 13:32:39 +00:00
if sReplace then
2020-04-18 09:09:40 +00:00
term.write ( string.rep ( sReplace , math.max ( # sLine - nScroll , 0 ) ) )
2017-05-01 13:32:39 +00:00
else
2020-04-18 09:09:40 +00:00
term.write ( string.sub ( sLine , nScroll + 1 ) )
2017-05-01 13:32:39 +00:00
end
if nCompletion then
2020-04-18 09:09:40 +00:00
local sCompletion = tCompletions [ nCompletion ]
2017-05-01 13:32:39 +00:00
local oldText , oldBg
if not _bClear then
oldText = term.getTextColor ( )
oldBg = term.getBackgroundColor ( )
2020-04-18 09:09:40 +00:00
term.setTextColor ( colors.white )
term.setBackgroundColor ( colors.gray )
2017-05-01 13:32:39 +00:00
end
if sReplace then
2020-04-18 09:09:40 +00:00
term.write ( string.rep ( sReplace , # sCompletion ) )
2017-05-01 13:32:39 +00:00
else
2020-04-18 09:09:40 +00:00
term.write ( sCompletion )
2017-05-01 13:32:39 +00:00
end
if not _bClear then
2020-04-18 09:09:40 +00:00
term.setTextColor ( oldText )
term.setBackgroundColor ( oldBg )
2017-05-01 13:32:39 +00:00
end
end
2020-04-18 09:09:40 +00:00
term.setCursorPos ( sx + nPos - nScroll , cy )
2017-05-01 13:32:39 +00:00
end
2018-12-17 17:22:15 +00:00
2017-05-01 13:32:39 +00:00
local function clear ( )
2020-04-18 09:09:40 +00:00
redraw ( true )
2017-05-01 13:32:39 +00:00
end
recomplete ( )
redraw ( )
local function acceptCompletion ( )
if nCompletion then
-- Clear
clear ( )
-- Find the common prefix of all the other suggestions which start with the same letter as the current one
2020-04-18 09:09:40 +00:00
local sCompletion = tCompletions [ nCompletion ]
2017-05-01 13:32:39 +00:00
sLine = sLine .. sCompletion
2019-11-29 20:15:58 +00:00
nPos = # sLine
2017-05-01 13:32:39 +00:00
-- Redraw
recomplete ( )
redraw ( )
end
end
while true do
2019-11-29 20:15:58 +00:00
local sEvent , param , param1 , param2 = os.pullEvent ( )
2017-05-01 13:32:39 +00:00
if sEvent == " char " then
-- Typed key
clear ( )
2020-04-18 09:09:40 +00:00
sLine = string.sub ( sLine , 1 , nPos ) .. param .. string.sub ( sLine , nPos + 1 )
2017-05-01 13:32:39 +00:00
nPos = nPos + 1
recomplete ( )
redraw ( )
elseif sEvent == " paste " then
-- Pasted text
clear ( )
2020-04-18 09:09:40 +00:00
sLine = string.sub ( sLine , 1 , nPos ) .. param .. string.sub ( sLine , nPos + 1 )
2019-11-29 20:15:58 +00:00
nPos = nPos + # param
2017-05-01 13:32:39 +00:00
recomplete ( )
redraw ( )
elseif sEvent == " key " then
if param == keys.enter then
-- Enter
if nCompletion then
clear ( )
uncomplete ( )
redraw ( )
end
break
2018-12-17 17:22:15 +00:00
2017-05-01 13:32:39 +00:00
elseif param == keys.left then
-- Left
if nPos > 0 then
clear ( )
nPos = nPos - 1
recomplete ( )
redraw ( )
end
2018-12-17 17:22:15 +00:00
2017-05-01 13:32:39 +00:00
elseif param == keys.right then
2018-12-17 17:22:15 +00:00
-- Right
2019-11-29 20:15:58 +00:00
if nPos < # sLine then
2017-05-01 13:32:39 +00:00
-- Move right
clear ( )
nPos = nPos + 1
recomplete ( )
redraw ( )
else
-- Accept autocomplete
acceptCompletion ( )
end
elseif param == keys.up or param == keys.down then
-- Up or down
if nCompletion then
-- Cycle completions
clear ( )
if param == keys.up then
nCompletion = nCompletion - 1
if nCompletion < 1 then
nCompletion = # tCompletions
end
elseif param == keys.down then
nCompletion = nCompletion + 1
if nCompletion > # tCompletions then
nCompletion = 1
end
end
redraw ( )
elseif _tHistory then
-- Cycle history
clear ( )
if param == keys.up then
-- Up
if nHistoryPos == nil then
if # _tHistory > 0 then
nHistoryPos = # _tHistory
end
elseif nHistoryPos > 1 then
nHistoryPos = nHistoryPos - 1
end
else
-- Down
if nHistoryPos == # _tHistory then
nHistoryPos = nil
elseif nHistoryPos ~= nil then
nHistoryPos = nHistoryPos + 1
2018-12-17 17:22:15 +00:00
end
2017-05-01 13:32:39 +00:00
end
if nHistoryPos then
sLine = _tHistory [ nHistoryPos ]
2019-11-29 20:15:58 +00:00
nPos , nScroll = # sLine , 0
2017-05-01 13:32:39 +00:00
else
sLine = " "
2019-11-29 20:15:58 +00:00
nPos , nScroll = 0 , 0
2017-05-01 13:32:39 +00:00
end
uncomplete ( )
redraw ( )
end
elseif param == keys.backspace then
-- Backspace
if nPos > 0 then
clear ( )
2020-04-18 09:09:40 +00:00
sLine = string.sub ( sLine , 1 , nPos - 1 ) .. string.sub ( sLine , nPos + 1 )
2017-05-01 13:32:39 +00:00
nPos = nPos - 1
2019-11-29 20:15:58 +00:00
if nScroll > 0 then nScroll = nScroll - 1 end
2017-05-01 13:32:39 +00:00
recomplete ( )
redraw ( )
end
elseif param == keys.home then
-- Home
if nPos > 0 then
clear ( )
nPos = 0
recomplete ( )
redraw ( )
end
elseif param == keys.delete then
-- Delete
2019-11-29 20:15:58 +00:00
if nPos < # sLine then
2017-05-01 13:32:39 +00:00
clear ( )
2020-04-18 09:09:40 +00:00
sLine = string.sub ( sLine , 1 , nPos ) .. string.sub ( sLine , nPos + 2 )
2017-05-01 13:32:39 +00:00
recomplete ( )
redraw ( )
end
elseif param == keys [ " end " ] then
-- End
2019-11-29 20:15:58 +00:00
if nPos < # sLine then
2017-05-01 13:32:39 +00:00
clear ( )
2019-11-29 20:15:58 +00:00
nPos = # sLine
2017-05-01 13:32:39 +00:00
recomplete ( )
redraw ( )
end
elseif param == keys.tab then
-- Tab (accept autocomplete)
acceptCompletion ( )
end
2019-11-23 13:21:07 +00:00
elseif sEvent == " mouse_click " or sEvent == " mouse_drag " and param == 1 then
local _ , cy = term.getCursorPos ( )
2019-11-29 20:15:58 +00:00
if param1 >= sx and param1 <= w and param2 == cy then
-- Ensure we don't scroll beyond the current line
nPos = math.min ( math.max ( nScroll + param1 - sx , 0 ) , # sLine )
2019-11-23 13:21:07 +00:00
redraw ( )
end
2017-05-01 13:32:39 +00:00
elseif sEvent == " term_resize " then
-- Terminal resized
w = term.getSize ( )
redraw ( )
end
end
2019-12-03 23:26:13 +00:00
local _ , cy = term.getCursorPos ( )
2020-04-18 09:09:40 +00:00
term.setCursorBlink ( false )
term.setCursorPos ( w + 1 , cy )
2017-05-01 13:32:39 +00:00
print ( )
2018-12-17 17:22:15 +00:00
2017-05-01 13:32:39 +00:00
return sLine
end
2020-04-18 09:09:40 +00:00
function loadfile ( filename , mode , env )
2019-07-12 21:04:28 +00:00
-- Support the previous `loadfile(filename, env)` form instead.
if type ( mode ) == " table " and env == nil then
mode , env = nil , mode
2017-05-01 13:32:39 +00:00
end
2019-07-12 21:04:28 +00:00
expect ( 1 , filename , " string " )
expect ( 2 , mode , " string " , " nil " )
expect ( 3 , env , " table " , " nil " )
2020-04-18 09:09:40 +00:00
local file = fs.open ( filename , " r " )
2019-07-12 21:04:28 +00:00
if not file then return nil , " File not found " end
2020-04-18 09:09:40 +00:00
local func , err = load ( file.readAll ( ) , " @ " .. fs.getName ( filename ) , mode , env )
2019-07-12 21:04:28 +00:00
file.close ( )
return func , err
2017-05-01 13:32:39 +00:00
end
2020-04-18 09:09:40 +00:00
function dofile ( _sFile )
2019-05-30 18:36:28 +00:00
expect ( 1 , _sFile , " string " )
2020-04-18 09:09:40 +00:00
local fnFile , e = loadfile ( _sFile , nil , _G )
2017-05-01 13:32:39 +00:00
if fnFile then
return fnFile ( )
else
2020-04-18 09:09:40 +00:00
error ( e , 2 )
2017-05-01 13:32:39 +00:00
end
end
-- Install the rest of the OS api
2020-04-18 09:09:40 +00:00
function os . run ( _tEnv , _sPath , ... )
2019-05-30 18:36:28 +00:00
expect ( 1 , _tEnv , " table " )
expect ( 2 , _sPath , " string " )
2017-05-01 13:32:39 +00:00
local tEnv = _tEnv
2020-04-18 09:09:40 +00:00
setmetatable ( tEnv , { __index = _G } )
2020-11-21 12:11:40 +00:00
2020-11-21 12:25:19 +00:00
if settings.get ( " bios.strict_globals " , false ) then
2020-11-21 12:11:40 +00:00
-- load will attempt to set _ENV on this environment, which
-- throws an error with this protection enabled. Thus we set it here first.
tEnv._ENV = tEnv
getmetatable ( tEnv ) . __newindex = function ( _ , name )
error ( " Attempt to create global " .. tostring ( name ) , 2 )
end
end
2020-04-18 09:09:40 +00:00
local fnFile , err = loadfile ( _sPath , nil , tEnv )
2017-05-01 13:32:39 +00:00
if fnFile then
2020-04-22 13:39:39 +00:00
local ok , err = pcall ( fnFile , ... )
2017-05-01 13:32:39 +00:00
if not ok then
if err and err ~= " " then
2020-04-18 09:09:40 +00:00
printError ( err )
2017-05-01 13:32:39 +00:00
end
return false
end
return true
end
if err and err ~= " " then
2020-04-18 09:09:40 +00:00
printError ( err )
2017-05-01 13:32:39 +00:00
end
return false
end
local tAPIsLoading = { }
2020-04-18 09:09:40 +00:00
function os . loadAPI ( _sPath )
2019-05-30 18:36:28 +00:00
expect ( 1 , _sPath , " string " )
2020-04-18 09:09:40 +00:00
local sName = fs.getName ( _sPath )
2017-05-17 21:47:13 +00:00
if sName : sub ( - 4 ) == " .lua " then
2019-12-07 10:33:47 +00:00
sName = sName : sub ( 1 , - 5 )
2017-05-17 21:47:13 +00:00
end
2017-05-01 13:32:39 +00:00
if tAPIsLoading [ sName ] == true then
2020-04-18 09:09:40 +00:00
printError ( " API " .. sName .. " is already being loaded " )
2017-05-01 13:32:39 +00:00
return false
end
tAPIsLoading [ sName ] = true
local tEnv = { }
2020-04-18 09:09:40 +00:00
setmetatable ( tEnv , { __index = _G } )
local fnAPI , err = loadfile ( _sPath , nil , tEnv )
2017-05-01 13:32:39 +00:00
if fnAPI then
2020-04-18 09:09:40 +00:00
local ok , err = pcall ( fnAPI )
2017-05-01 13:32:39 +00:00
if not ok then
tAPIsLoading [ sName ] = nil
2020-04-18 09:09:40 +00:00
return error ( " Failed to load API " .. sName .. " due to " .. err , 1 )
2017-05-01 13:32:39 +00:00
end
else
tAPIsLoading [ sName ] = nil
2020-04-18 09:09:40 +00:00
return error ( " Failed to load API " .. sName .. " due to " .. err , 1 )
2017-05-01 13:32:39 +00:00
end
2018-12-17 17:22:15 +00:00
2017-05-01 13:32:39 +00:00
local tAPI = { }
2020-04-18 09:09:40 +00:00
for k , v in pairs ( tEnv ) do
2017-05-01 13:32:39 +00:00
if k ~= " _ENV " then
tAPI [ k ] = v
end
end
2018-12-17 17:22:15 +00:00
_G [ sName ] = tAPI
2017-05-01 13:32:39 +00:00
tAPIsLoading [ sName ] = nil
return true
end
2020-04-18 09:09:40 +00:00
function os . unloadAPI ( _sName )
2019-05-30 18:36:28 +00:00
expect ( 1 , _sName , " string " )
2017-05-01 13:32:39 +00:00
if _sName ~= " _G " and type ( _G [ _sName ] ) == " table " then
_G [ _sName ] = nil
end
end
2020-04-18 09:09:40 +00:00
function os . sleep ( nTime )
sleep ( nTime )
2017-05-01 13:32:39 +00:00
end
local nativeShutdown = os.shutdown
function os . shutdown ( )
nativeShutdown ( )
while true do
coroutine.yield ( )
end
end
local nativeReboot = os.reboot
function os . reboot ( )
nativeReboot ( )
while true do
coroutine.yield ( )
end
end
-- Install the lua part of the HTTP api (if enabled)
if http then
local nativeHTTPRequest = http.request
2018-05-15 09:10:23 +00:00
local methods = {
GET = true , POST = true , HEAD = true ,
2019-12-07 10:33:47 +00:00
OPTIONS = true , PUT = true , DELETE = true ,
2019-12-08 17:10:58 +00:00
PATCH = true , TRACE = true ,
2018-05-15 09:10:23 +00:00
}
2020-04-18 09:09:40 +00:00
local function checkKey ( options , key , ty , opt )
2018-05-15 09:10:23 +00:00
local value = options [ key ]
local valueTy = type ( value )
2018-12-17 17:22:15 +00:00
2018-05-15 09:10:23 +00:00
if ( value ~= nil or not opt ) and valueTy ~= ty then
error ( ( " bad field '%s' (expected %s, got %s " ) : format ( key , ty , valueTy ) , 4 )
end
end
2020-04-18 09:09:40 +00:00
local function checkOptions ( options , body )
checkKey ( options , " url " , " string " )
2019-05-30 18:36:28 +00:00
if body == false then
2020-04-18 09:09:40 +00:00
checkKey ( options , " body " , " nil " )
2019-05-30 18:36:28 +00:00
else
2020-04-18 09:09:40 +00:00
checkKey ( options , " body " , " string " , not body )
2019-05-30 18:36:28 +00:00
end
2020-04-18 09:09:40 +00:00
checkKey ( options , " headers " , " table " , true )
checkKey ( options , " method " , " string " , true )
checkKey ( options , " redirect " , " boolean " , true )
2018-05-15 09:10:23 +00:00
if options.method and not methods [ options.method ] then
2020-04-18 09:09:40 +00:00
error ( " Unsupported HTTP method " , 3 )
2018-05-15 09:10:23 +00:00
end
end
2020-04-18 09:09:40 +00:00
local function wrapRequest ( _url , ... )
local ok , err = nativeHTTPRequest ( ... )
2017-05-01 13:32:39 +00:00
if ok then
while true do
2017-05-01 16:28:20 +00:00
local event , param1 , param2 , param3 = os.pullEvent ( )
2017-05-01 13:32:39 +00:00
if event == " http_success " and param1 == _url then
return param2
elseif event == " http_failure " and param1 == _url then
2017-05-01 16:28:20 +00:00
return nil , param2 , param3
2017-05-01 13:32:39 +00:00
end
end
end
return nil , err
end
2018-12-17 17:22:15 +00:00
2020-04-09 21:15:06 +00:00
http.get = function ( _url , _headers , _binary )
2020-04-18 09:09:40 +00:00
if type ( _url ) == " table " then
checkOptions ( _url , false )
return wrapRequest ( _url.url , _url )
2018-05-15 09:10:23 +00:00
end
2019-05-30 18:36:28 +00:00
expect ( 1 , _url , " string " )
expect ( 2 , _headers , " table " , " nil " )
expect ( 3 , _binary , " boolean " , " nil " )
2020-04-18 09:09:40 +00:00
return wrapRequest ( _url , _url , nil , _headers , _binary )
2017-05-01 13:32:39 +00:00
end
2020-04-09 21:15:06 +00:00
http.post = function ( _url , _post , _headers , _binary )
2020-04-18 09:09:40 +00:00
if type ( _url ) == " table " then
checkOptions ( _url , true )
return wrapRequest ( _url.url , _url )
2018-05-15 09:10:23 +00:00
end
2019-05-30 18:36:28 +00:00
expect ( 1 , _url , " string " )
expect ( 2 , _post , " string " )
expect ( 3 , _headers , " table " , " nil " )
expect ( 4 , _binary , " boolean " , " nil " )
2020-04-18 09:09:40 +00:00
return wrapRequest ( _url , _url , _post , _headers , _binary )
2017-05-01 13:32:39 +00:00
end
2020-04-18 09:09:40 +00:00
http.request = function ( _url , _post , _headers , _binary )
2018-05-15 09:10:23 +00:00
local url
2020-04-18 09:09:40 +00:00
if type ( _url ) == " table " then
checkOptions ( _url )
2018-05-15 09:10:23 +00:00
url = _url.url
else
2019-05-30 18:36:28 +00:00
expect ( 1 , _url , " string " )
expect ( 2 , _post , " string " , " nil " )
expect ( 3 , _headers , " table " , " nil " )
expect ( 4 , _binary , " boolean " , " nil " )
2018-05-15 09:10:23 +00:00
url = _url.url
2017-08-21 13:20:32 +00:00
end
2018-05-15 09:10:23 +00:00
2020-04-18 09:09:40 +00:00
local ok , err = nativeHTTPRequest ( _url , _post , _headers , _binary )
2017-05-01 13:32:39 +00:00
if not ok then
2020-04-18 09:09:40 +00:00
os.queueEvent ( " http_failure " , url , err )
2017-05-01 13:32:39 +00:00
end
return ok , err
end
2018-12-17 17:22:15 +00:00
2017-06-11 20:40:08 +00:00
local nativeCheckURL = http.checkURL
http.checkURLAsync = nativeCheckURL
2020-04-18 09:09:40 +00:00
http.checkURL = function ( _url )
local ok , err = nativeCheckURL ( _url )
2017-06-11 20:40:08 +00:00
if not ok then return ok , err end
2018-12-17 17:22:15 +00:00
2017-06-11 20:40:08 +00:00
while true do
2020-04-18 09:09:40 +00:00
local _ , url , ok , err = os.pullEvent ( " http_check " )
2017-06-11 20:40:08 +00:00
if url == _url then return ok , err end
end
end
2017-12-06 09:28:38 +00:00
local nativeWebsocket = http.websocket
http.websocketAsync = nativeWebsocket
2020-04-18 09:09:40 +00:00
http.websocket = function ( _url , _headers )
2019-05-30 18:36:28 +00:00
expect ( 1 , _url , " string " )
expect ( 2 , _headers , " table " , " nil " )
2020-04-18 09:09:40 +00:00
local ok , err = nativeWebsocket ( _url , _headers )
2017-12-06 09:28:38 +00:00
if not ok then return ok , err end
while true do
local event , url , param = os.pullEvent ( )
if event == " websocket_success " and url == _url then
return param
elseif event == " websocket_failure " and url == _url then
return false , param
end
end
end
2017-05-01 13:32:39 +00:00
end
-- Install the lua part of the FS api
local tEmpty = { }
2020-04-18 09:09:40 +00:00
function fs . complete ( sPath , sLocation , bIncludeFiles , bIncludeDirs )
2019-05-30 18:36:28 +00:00
expect ( 1 , sPath , " string " )
expect ( 2 , sLocation , " string " )
expect ( 3 , bIncludeFiles , " boolean " , " nil " )
expect ( 4 , bIncludeDirs , " boolean " , " nil " )
2019-12-03 23:26:13 +00:00
bIncludeFiles = bIncludeFiles ~= false
bIncludeDirs = bIncludeDirs ~= false
2017-05-01 13:32:39 +00:00
local sDir = sLocation
local nStart = 1
2020-04-18 09:09:40 +00:00
local nSlash = string.find ( sPath , " [/ \\ ] " , nStart )
2017-05-01 13:32:39 +00:00
if nSlash == 1 then
sDir = " "
nStart = 2
end
local sName
while not sName do
2020-04-18 09:09:40 +00:00
local nSlash = string.find ( sPath , " [/ \\ ] " , nStart )
2017-05-01 13:32:39 +00:00
if nSlash then
2020-04-18 09:09:40 +00:00
local sPart = string.sub ( sPath , nStart , nSlash - 1 )
sDir = fs.combine ( sDir , sPart )
2017-05-01 13:32:39 +00:00
nStart = nSlash + 1
else
2020-04-18 09:09:40 +00:00
sName = string.sub ( sPath , nStart )
2017-05-01 13:32:39 +00:00
end
end
2020-04-18 09:09:40 +00:00
if fs.isDir ( sDir ) then
2017-05-01 13:32:39 +00:00
local tResults = { }
if bIncludeDirs and sPath == " " then
2020-04-18 09:09:40 +00:00
table.insert ( tResults , " . " )
2017-05-01 13:32:39 +00:00
end
if sDir ~= " " then
if sPath == " " then
2020-04-18 09:09:40 +00:00
table.insert ( tResults , bIncludeDirs and " .. " or " ../ " )
2017-05-01 13:32:39 +00:00
elseif sPath == " . " then
2020-04-18 09:09:40 +00:00
table.insert ( tResults , bIncludeDirs and " . " or " ./ " )
2017-05-01 13:32:39 +00:00
end
end
2020-04-18 09:09:40 +00:00
local tFiles = fs.list ( sDir )
2019-12-07 10:33:47 +00:00
for n = 1 , # tFiles do
2017-05-01 13:32:39 +00:00
local sFile = tFiles [ n ]
2020-04-18 09:09:40 +00:00
if # sFile >= # sName and string.sub ( sFile , 1 , # sName ) == sName then
local bIsDir = fs.isDir ( fs.combine ( sDir , sFile ) )
local sResult = string.sub ( sFile , # sName + 1 )
2017-05-01 13:32:39 +00:00
if bIsDir then
2020-04-18 09:09:40 +00:00
table.insert ( tResults , sResult .. " / " )
2017-05-01 13:32:39 +00:00
if bIncludeDirs and # sResult > 0 then
2020-04-18 09:09:40 +00:00
table.insert ( tResults , sResult )
2017-05-01 13:32:39 +00:00
end
else
if bIncludeFiles and # sResult > 0 then
2020-04-18 09:09:40 +00:00
table.insert ( tResults , sResult )
2017-05-01 13:32:39 +00:00
end
end
end
end
return tResults
end
return tEmpty
end
2020-05-12 10:32:48 +00:00
function fs . isDriveRoot ( sPath )
expect ( 1 , sPath , " string " )
-- Force the root directory to be a mount.
return fs.getDir ( sPath ) == " .. " or fs.getDrive ( sPath ) ~= fs.getDrive ( fs.getDir ( sPath ) )
end
2017-05-01 13:32:39 +00:00
-- Load APIs
local bAPIError = false
2020-04-18 09:09:40 +00:00
local tApis = fs.list ( " rom/apis " )
for _ , sFile in ipairs ( tApis ) do
if string.sub ( sFile , 1 , 1 ) ~= " . " then
local sPath = fs.combine ( " rom/apis " , sFile )
if not fs.isDir ( sPath ) then
if not os.loadAPI ( sPath ) then
2017-05-01 13:32:39 +00:00
bAPIError = true
end
end
end
end
2020-04-18 09:09:40 +00:00
if turtle and fs.isDir ( " rom/apis/turtle " ) then
2017-05-01 13:32:39 +00:00
-- Load turtle APIs
2020-04-18 09:09:40 +00:00
local tApis = fs.list ( " rom/apis/turtle " )
for _ , sFile in ipairs ( tApis ) do
if string.sub ( sFile , 1 , 1 ) ~= " . " then
local sPath = fs.combine ( " rom/apis/turtle " , sFile )
if not fs.isDir ( sPath ) then
if not os.loadAPI ( sPath ) then
2017-05-01 13:32:39 +00:00
bAPIError = true
end
end
end
end
end
2020-04-18 09:09:40 +00:00
if pocket and fs.isDir ( " rom/apis/pocket " ) then
2017-05-01 13:32:39 +00:00
-- Load pocket APIs
2020-04-18 09:09:40 +00:00
local tApis = fs.list ( " rom/apis/pocket " )
for _ , sFile in ipairs ( tApis ) do
if string.sub ( sFile , 1 , 1 ) ~= " . " then
local sPath = fs.combine ( " rom/apis/pocket " , sFile )
if not fs.isDir ( sPath ) then
if not os.loadAPI ( sPath ) then
2017-05-01 13:32:39 +00:00
bAPIError = true
end
end
end
end
end
2020-04-18 09:09:40 +00:00
if commands and fs.isDir ( " rom/apis/command " ) then
2017-05-01 13:32:39 +00:00
-- Load command APIs
2020-04-18 09:09:40 +00:00
if os.loadAPI ( " rom/apis/command/commands.lua " ) then
2017-05-01 13:32:39 +00:00
-- Add a special case-insensitive metatable to the commands api
local tCaseInsensitiveMetatable = {
2020-04-18 09:09:40 +00:00
__index = function ( table , key )
local value = rawget ( table , key )
2017-05-01 13:32:39 +00:00
if value ~= nil then
return value
end
if type ( key ) == " string " then
2020-04-18 09:09:40 +00:00
local value = rawget ( table , string.lower ( key ) )
2017-05-01 13:32:39 +00:00
if value ~= nil then
return value
end
end
return nil
2019-12-07 10:33:47 +00:00
end ,
2017-05-01 13:32:39 +00:00
}
2020-04-18 09:09:40 +00:00
setmetatable ( commands , tCaseInsensitiveMetatable )
setmetatable ( commands.async , tCaseInsensitiveMetatable )
2017-05-01 13:32:39 +00:00
-- Add global "exec" function
exec = commands.exec
else
bAPIError = true
end
end
if bAPIError then
2020-04-18 09:09:40 +00:00
print ( " Press any key to continue " )
os.pullEvent ( " key " )
2017-05-01 13:32:39 +00:00
term.clear ( )
2020-04-18 09:09:40 +00:00
term.setCursorPos ( 1 , 1 )
2017-05-01 13:32:39 +00:00
end
-- Set default settings
Some redesigning of the settings API (#408)
- The store is now split into two sections:
- A list of possible options, with some metadata about them.
- A list of values which have been changed.
- settings.define can be used to register a new option. We have
migrated all existing options over to use it. This can be used to
define a default value, description, and a type the setting must have
(such as `string` or `boolean).
- settings.{set,unset,clear,load,store} operate using this value list.
This means that only values which have been changed are stored to
disk.
Furthermore, clearing/unsetting will reset to the /default/ value,
rather than removing entirely.
- The set program will now display descriptions.
- settings.{load,save} now default to `.settings` if no path is given.
2020-04-21 10:37:56 +00:00
settings.define ( " shell.allow_startup " , {
default = true ,
description = " Run startup files when the computer turns on. " ,
type = " boolean " ,
} )
settings.define ( " shell.allow_disk_startup " , {
default = commands == nil ,
description = " Run startup files from disk drives when the computer turns on. " ,
type = " boolean " ,
} )
settings.define ( " shell.autocomplete " , {
default = true ,
description = " Autocomplete program and arguments in the shell. " ,
type = " boolean " ,
} )
settings.define ( " edit.autocomplete " , {
default = true ,
description = " Autocomplete API and function names in the editor. " ,
type = " boolean " ,
} )
settings.define ( " lua.autocomplete " , {
default = true ,
description = " Autocomplete API and function names in the Lua REPL. " ,
type = " boolean " ,
} )
settings.define ( " edit.default_extension " , {
default = " lua " ,
description = [[The file extension the editor will use if none is given. Set to "" to disable.]] ,
type = " string " ,
} )
settings.define ( " paint.default_extension " , {
default = " nfp " ,
description = [[The file extension the paint program will use if none is given. Set to "" to disable.]] ,
type = " string " ,
} )
settings.define ( " list.show_hidden " , {
default = false ,
description = [[Show hidden files (those starting with "." in the Lua REPL)]] ,
type = " boolean " ,
} )
settings.define ( " motd.enable " , {
2020-04-22 16:44:13 +00:00
default = pocket == nil ,
Some redesigning of the settings API (#408)
- The store is now split into two sections:
- A list of possible options, with some metadata about them.
- A list of values which have been changed.
- settings.define can be used to register a new option. We have
migrated all existing options over to use it. This can be used to
define a default value, description, and a type the setting must have
(such as `string` or `boolean).
- settings.{set,unset,clear,load,store} operate using this value list.
This means that only values which have been changed are stored to
disk.
Furthermore, clearing/unsetting will reset to the /default/ value,
rather than removing entirely.
- The set program will now display descriptions.
- settings.{load,save} now default to `.settings` if no path is given.
2020-04-21 10:37:56 +00:00
description = " Display a random message when the computer starts up. " ,
type = " boolean " ,
} )
settings.define ( " motd.path " , {
default = " /rom/motd.txt:/motd.txt " ,
description = [[The path to load random messages from. Should be a colon (":") separated string of file paths.]] ,
type = " string " ,
} )
2020-05-14 18:11:57 +00:00
2020-05-08 15:07:33 +00:00
settings.define ( " lua.warn_against_use_of_local " , {
default = true ,
description = [[Print a message when input in the Lua REPL starts with the word 'local'. Local variables defined in the Lua REPL are be inaccessable on the next input.]] ,
type = " boolean " ,
} )
2020-05-14 18:11:57 +00:00
settings.define ( " lua.function_args " , {
default = true ,
description = " Show function arguments when printing functions. " ,
type = " boolean " ,
} )
settings.define ( " lua.function_source " , {
default = false ,
description = " Show where a function was defined when printing functions. " ,
type = " boolean " ,
} )
2020-11-21 12:11:40 +00:00
settings.define ( " bios.strict_globals " , {
default = false ,
description = " Prevents assigning variables into a program's environment. Make sure you use the local keyword or assign to _G explicitly. " ,
type = " boolean " ,
} )
2020-05-14 18:11:57 +00:00
2017-05-01 13:32:39 +00:00
if term.isColour ( ) then
Some redesigning of the settings API (#408)
- The store is now split into two sections:
- A list of possible options, with some metadata about them.
- A list of values which have been changed.
- settings.define can be used to register a new option. We have
migrated all existing options over to use it. This can be used to
define a default value, description, and a type the setting must have
(such as `string` or `boolean).
- settings.{set,unset,clear,load,store} operate using this value list.
This means that only values which have been changed are stored to
disk.
Furthermore, clearing/unsetting will reset to the /default/ value,
rather than removing entirely.
- The set program will now display descriptions.
- settings.{load,save} now default to `.settings` if no path is given.
2020-04-21 10:37:56 +00:00
settings.define ( " bios.use_multishell " , {
default = true ,
description = [[Allow running multiple programs at once, through the use of the "fg" and "bg" programs.]] ,
type = " boolean " ,
} )
2017-05-01 13:32:39 +00:00
end
if _CC_DEFAULT_SETTINGS then
2020-04-18 09:09:40 +00:00
for sPair in string.gmatch ( _CC_DEFAULT_SETTINGS , " [^,]+ " ) do
local sName , sValue = string.match ( sPair , " ([^=]*)=(.*) " )
2017-05-01 13:32:39 +00:00
if sName and sValue then
local value
if sValue == " true " then
value = true
elseif sValue == " false " then
value = false
elseif sValue == " nil " then
value = nil
elseif tonumber ( sValue ) then
value = tonumber ( sValue )
else
value = sValue
end
if value ~= nil then
2020-04-18 09:09:40 +00:00
settings.set ( sName , value )
2017-05-01 13:32:39 +00:00
else
2020-04-18 09:09:40 +00:00
settings.unset ( sName )
2017-05-01 13:32:39 +00:00
end
end
end
end
-- Load user settings
2020-04-18 09:09:40 +00:00
if fs.exists ( " .settings " ) then
settings.load ( " .settings " )
2017-05-01 13:32:39 +00:00
end
-- Run the shell
2020-01-15 09:29:11 +00:00
local ok , err = pcall ( parallel.waitForAny ,
function ( )
local sShell
2020-04-18 09:09:40 +00:00
if term.isColour ( ) and settings.get ( " bios.use_multishell " ) then
2020-01-15 09:29:11 +00:00
sShell = " rom/programs/advanced/multishell.lua "
else
sShell = " rom/programs/shell.lua "
end
2020-04-18 09:09:40 +00:00
os.run ( { } , sShell )
os.run ( { } , " rom/programs/shutdown.lua " )
2020-01-15 09:29:11 +00:00
end ,
rednet.run
)
2017-05-01 13:32:39 +00:00
-- If the shell errored, let the user read it.
2020-04-18 09:09:40 +00:00
term.redirect ( term.native ( ) )
2017-05-01 13:32:39 +00:00
if not ok then
2020-04-18 09:09:40 +00:00
printError ( err )
pcall ( function ( )
term.setCursorBlink ( false )
print ( " Press any key to continue " )
os.pullEvent ( " key " )
end )
2017-05-01 13:32:39 +00:00
end
-- End
os.shutdown ( )