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
|
|
|
|
local h = fs.open("rom/modules/main/craftos/expect.lua", "r")
|
|
|
|
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
|
|
|
|
|
|
|
--- Historically load/loadstring would handle the chunk name as if it has
|
|
|
|
-- 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
|
|
|
|
|
2017-05-01 13:32:39 +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")
|
|
|
|
|
2018-12-17 17:22:15 +00:00
|
|
|
local ok, p1, p2 = pcall( function()
|
2017-05-01 13:32:39 +00:00
|
|
|
if type(x) == "string" then
|
|
|
|
local result, err = nativeloadstring( x, name )
|
|
|
|
if result then
|
|
|
|
if env then
|
|
|
|
env._ENV = env
|
|
|
|
nativesetfenv( result, env )
|
|
|
|
end
|
|
|
|
return result
|
|
|
|
else
|
|
|
|
return nil, err
|
|
|
|
end
|
|
|
|
else
|
|
|
|
local result, err = nativeload( x, name )
|
|
|
|
if result then
|
|
|
|
if env then
|
|
|
|
env._ENV = env
|
|
|
|
nativesetfenv( result, env )
|
|
|
|
end
|
|
|
|
return result
|
|
|
|
else
|
|
|
|
return nil, err
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end )
|
|
|
|
if ok then
|
|
|
|
return p1, p2
|
|
|
|
else
|
|
|
|
error( p1, 2 )
|
2018-12-17 17:22:15 +00:00
|
|
|
end
|
2017-05-01 13:32:39 +00:00
|
|
|
end
|
|
|
|
table.unpack = unpack
|
|
|
|
table.pack = function( ... ) return { n = select( "#", ... ), ... } 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
|
2019-05-30 18:36:28 +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,
|
|
|
|
blogic_rshift = bit32.rshift
|
|
|
|
}
|
2017-05-01 13:32:39 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-05-31 08:10:56 +00:00
|
|
|
if _VERSION == "Lua 5.3" and not bit32 then
|
2017-05-01 13:32:39 +00:00
|
|
|
-- If we're on Lua 5.3, install the bit32 api from Lua 5.2
|
|
|
|
-- (Loaded from a string so this file will still parse on <5.3 lua)
|
|
|
|
load( [[
|
|
|
|
bit32 = {}
|
|
|
|
|
|
|
|
function bit32.arshift( n, bits )
|
|
|
|
if type(n) ~= "number" or type(bits) ~= "number" then
|
|
|
|
error( "Expected number, number", 2 )
|
|
|
|
end
|
|
|
|
return n >> bits
|
|
|
|
end
|
|
|
|
|
|
|
|
function bit32.band( m, n )
|
|
|
|
if type(m) ~= "number" or type(n) ~= "number" then
|
|
|
|
error( "Expected number, number", 2 )
|
|
|
|
end
|
|
|
|
return m & n
|
|
|
|
end
|
|
|
|
|
|
|
|
function bit32.bnot( n )
|
|
|
|
if type(n) ~= "number" then
|
|
|
|
error( "Expected number", 2 )
|
|
|
|
end
|
|
|
|
return ~n
|
|
|
|
end
|
|
|
|
|
|
|
|
function bit32.bor( m, n )
|
|
|
|
if type(m) ~= "number" or type(n) ~= "number" then
|
|
|
|
error( "Expected number, number", 2 )
|
|
|
|
end
|
|
|
|
return m | n
|
|
|
|
end
|
|
|
|
|
|
|
|
function bit32.btest( m, n )
|
|
|
|
if type(m) ~= "number" or type(n) ~= "number" then
|
|
|
|
error( "Expected number, number", 2 )
|
|
|
|
end
|
|
|
|
return (m & n) ~= 0
|
|
|
|
end
|
|
|
|
|
|
|
|
function bit32.bxor( m, n )
|
|
|
|
if type(m) ~= "number" or type(n) ~= "number" then
|
|
|
|
error( "Expected number, number", 2 )
|
|
|
|
end
|
|
|
|
return m ~ n
|
|
|
|
end
|
|
|
|
|
|
|
|
function bit32.lshift( n, bits )
|
|
|
|
if type(n) ~= "number" or type(bits) ~= "number" then
|
|
|
|
error( "Expected number, number", 2 )
|
|
|
|
end
|
|
|
|
return n << bits
|
|
|
|
end
|
|
|
|
|
|
|
|
function bit32.rshift( n, bits )
|
|
|
|
if type(n) ~= "number" or type(bits) ~= "number" then
|
|
|
|
error( "Expected number, number", 2 )
|
|
|
|
end
|
|
|
|
return n >> bits
|
|
|
|
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
|
|
|
|
|
|
|
|
function os.pullEventRaw( sFilter )
|
|
|
|
return coroutine.yield( sFilter )
|
|
|
|
end
|
|
|
|
|
|
|
|
function os.pullEvent( sFilter )
|
2017-05-04 09:49:41 +00:00
|
|
|
local eventData = table.pack( os.pullEventRaw( sFilter ) )
|
2017-05-01 13:32:39 +00:00
|
|
|
if eventData[1] == "terminate" then
|
|
|
|
error( "Terminated", 0 )
|
|
|
|
end
|
2017-05-04 09:49:41 +00:00
|
|
|
return table.unpack( eventData, 1, eventData.n )
|
2017-05-01 13:32:39 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
-- Install globals
|
|
|
|
function sleep( nTime )
|
2019-05-30 18:36:28 +00:00
|
|
|
expect(1, nTime, "number", "nil")
|
2017-05-01 13:32:39 +00:00
|
|
|
local timer = os.startTimer( nTime or 0 )
|
|
|
|
repeat
|
|
|
|
local sEvent, param = os.pullEvent( "timer" )
|
|
|
|
until param == timer
|
|
|
|
end
|
|
|
|
|
|
|
|
function write( sText )
|
2019-05-30 18:36:28 +00:00
|
|
|
expect(1, sText, "string", "number")
|
2017-06-18 15:48:07 +00:00
|
|
|
|
2018-12-17 17:22:15 +00:00
|
|
|
local w,h = term.getSize()
|
2017-05-01 13:32:39 +00:00
|
|
|
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
|
|
|
|
while string.len(sText) > 0 do
|
|
|
|
local whitespace = string.match( sText, "^[ \t]+" )
|
|
|
|
if whitespace then
|
|
|
|
-- Print whitespace
|
|
|
|
term.write( whitespace )
|
|
|
|
x,y = term.getCursorPos()
|
|
|
|
sText = string.sub( sText, string.len(whitespace) + 1 )
|
|
|
|
end
|
2018-12-17 17:22:15 +00:00
|
|
|
|
2017-05-01 13:32:39 +00:00
|
|
|
local newline = string.match( sText, "^\n" )
|
|
|
|
if newline then
|
|
|
|
-- Print newlines
|
|
|
|
newLine()
|
|
|
|
sText = string.sub( sText, 2 )
|
|
|
|
end
|
2018-12-17 17:22:15 +00:00
|
|
|
|
2017-05-01 13:32:39 +00:00
|
|
|
local text = string.match( sText, "^[^ \t\n]+" )
|
|
|
|
if text then
|
|
|
|
sText = string.sub( sText, string.len(text) + 1 )
|
|
|
|
if string.len(text) > w then
|
2018-12-17 17:22:15 +00:00
|
|
|
-- Print a multiline word
|
2017-05-01 13:32:39 +00:00
|
|
|
while string.len( text ) > 0 do
|
|
|
|
if x > w then
|
|
|
|
newLine()
|
|
|
|
end
|
|
|
|
term.write( text )
|
|
|
|
text = string.sub( text, (w-x) + 2 )
|
|
|
|
x,y = term.getCursorPos()
|
|
|
|
end
|
|
|
|
else
|
|
|
|
-- Print a word normally
|
|
|
|
if x + string.len(text) - 1 > w then
|
|
|
|
newLine()
|
|
|
|
end
|
|
|
|
term.write( text )
|
|
|
|
x,y = term.getCursorPos()
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2018-12-17 17:22:15 +00:00
|
|
|
|
2017-05-01 13:32:39 +00:00
|
|
|
return nLinesPrinted
|
|
|
|
end
|
|
|
|
|
|
|
|
function print( ... )
|
|
|
|
local nLinesPrinted = 0
|
|
|
|
local nLimit = select("#", ... )
|
|
|
|
for n = 1, nLimit do
|
|
|
|
local s = tostring( select( n, ... ) )
|
|
|
|
if n < nLimit then
|
|
|
|
s = s .. "\t"
|
|
|
|
end
|
|
|
|
nLinesPrinted = nLinesPrinted + write( s )
|
|
|
|
end
|
|
|
|
nLinesPrinted = nLinesPrinted + write( "\n" )
|
|
|
|
return nLinesPrinted
|
|
|
|
end
|
|
|
|
|
|
|
|
function printError( ... )
|
|
|
|
local oldColour
|
|
|
|
if term.isColour() then
|
|
|
|
oldColour = term.getTextColour()
|
|
|
|
term.setTextColour( colors.red )
|
|
|
|
end
|
|
|
|
print( ... )
|
|
|
|
if term.isColour() then
|
|
|
|
term.setTextColour( oldColour )
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-06-15 12:50:47 +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")
|
|
|
|
|
2017-05-01 13:32:39 +00:00
|
|
|
term.setCursorBlink( true )
|
|
|
|
|
2017-06-15 12:50:47 +00:00
|
|
|
local sLine
|
|
|
|
if type( _sDefault ) == "string" then
|
|
|
|
sLine = _sDefault
|
|
|
|
else
|
|
|
|
sLine = ""
|
|
|
|
end
|
2017-05-01 13:32:39 +00:00
|
|
|
local nHistoryPos
|
2017-06-15 12:50:47 +00:00
|
|
|
local nPos = #sLine
|
2017-05-01 13:32:39 +00:00
|
|
|
if _sReplaceChar then
|
|
|
|
_sReplaceChar = string.sub( _sReplaceChar, 1, 1 )
|
|
|
|
end
|
|
|
|
|
|
|
|
local tCompletions
|
|
|
|
local nCompletion
|
|
|
|
local function recomplete()
|
|
|
|
if _fnComplete and nPos == string.len(sLine) then
|
|
|
|
tCompletions = _fnComplete( sLine )
|
|
|
|
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()
|
|
|
|
|
|
|
|
local function redraw( _bClear )
|
|
|
|
local nScroll = 0
|
|
|
|
if sx + nPos >= w then
|
|
|
|
nScroll = (sx + nPos) - w
|
|
|
|
end
|
|
|
|
|
|
|
|
local cx,cy = term.getCursorPos()
|
|
|
|
term.setCursorPos( sx, cy )
|
|
|
|
local sReplace = (_bClear and " ") or _sReplaceChar
|
|
|
|
if sReplace then
|
|
|
|
term.write( string.rep( sReplace, math.max( string.len(sLine) - nScroll, 0 ) ) )
|
|
|
|
else
|
|
|
|
term.write( string.sub( sLine, nScroll + 1 ) )
|
|
|
|
end
|
|
|
|
|
|
|
|
if nCompletion then
|
|
|
|
local sCompletion = tCompletions[ nCompletion ]
|
|
|
|
local oldText, oldBg
|
|
|
|
if not _bClear then
|
|
|
|
oldText = term.getTextColor()
|
|
|
|
oldBg = term.getBackgroundColor()
|
|
|
|
term.setTextColor( colors.white )
|
|
|
|
term.setBackgroundColor( colors.gray )
|
|
|
|
end
|
|
|
|
if sReplace then
|
|
|
|
term.write( string.rep( sReplace, string.len( sCompletion ) ) )
|
|
|
|
else
|
|
|
|
term.write( sCompletion )
|
|
|
|
end
|
|
|
|
if not _bClear then
|
|
|
|
term.setTextColor( oldText )
|
|
|
|
term.setBackgroundColor( oldBg )
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
term.setCursorPos( sx + nPos - nScroll, cy )
|
|
|
|
end
|
2018-12-17 17:22:15 +00:00
|
|
|
|
2017-05-01 13:32:39 +00:00
|
|
|
local function clear()
|
|
|
|
redraw( true )
|
|
|
|
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
|
|
|
|
local sCompletion = tCompletions[ nCompletion ]
|
|
|
|
sLine = sLine .. sCompletion
|
|
|
|
nPos = string.len( sLine )
|
|
|
|
|
|
|
|
-- Redraw
|
|
|
|
recomplete()
|
|
|
|
redraw()
|
|
|
|
end
|
|
|
|
end
|
|
|
|
while true do
|
|
|
|
local sEvent, param = os.pullEvent()
|
|
|
|
if sEvent == "char" then
|
|
|
|
-- Typed key
|
|
|
|
clear()
|
|
|
|
sLine = string.sub( sLine, 1, nPos ) .. param .. string.sub( sLine, nPos + 1 )
|
|
|
|
nPos = nPos + 1
|
|
|
|
recomplete()
|
|
|
|
redraw()
|
|
|
|
|
|
|
|
elseif sEvent == "paste" then
|
|
|
|
-- Pasted text
|
|
|
|
clear()
|
|
|
|
sLine = string.sub( sLine, 1, nPos ) .. param .. string.sub( sLine, nPos + 1 )
|
|
|
|
nPos = nPos + string.len( param )
|
|
|
|
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
|
2017-05-01 13:32:39 +00:00
|
|
|
if nPos < string.len(sLine) then
|
|
|
|
-- 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]
|
2018-12-17 17:22:15 +00:00
|
|
|
nPos = string.len( sLine )
|
2017-05-01 13:32:39 +00:00
|
|
|
else
|
|
|
|
sLine = ""
|
|
|
|
nPos = 0
|
|
|
|
end
|
|
|
|
uncomplete()
|
|
|
|
redraw()
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
elseif param == keys.backspace then
|
|
|
|
-- Backspace
|
|
|
|
if nPos > 0 then
|
|
|
|
clear()
|
|
|
|
sLine = string.sub( sLine, 1, nPos - 1 ) .. string.sub( sLine, nPos + 1 )
|
|
|
|
nPos = nPos - 1
|
|
|
|
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
|
|
|
|
if nPos < string.len(sLine) then
|
|
|
|
clear()
|
2018-12-17 17:22:15 +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
|
|
|
|
if nPos < string.len(sLine ) then
|
|
|
|
clear()
|
|
|
|
nPos = string.len(sLine)
|
|
|
|
recomplete()
|
|
|
|
redraw()
|
|
|
|
end
|
|
|
|
|
|
|
|
elseif param == keys.tab then
|
|
|
|
-- Tab (accept autocomplete)
|
|
|
|
acceptCompletion()
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
elseif sEvent == "term_resize" then
|
|
|
|
-- Terminal resized
|
|
|
|
w = term.getSize()
|
|
|
|
redraw()
|
|
|
|
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local cx, cy = term.getCursorPos()
|
|
|
|
term.setCursorBlink( false )
|
|
|
|
term.setCursorPos( w + 1, cy )
|
|
|
|
print()
|
2018-12-17 17:22:15 +00:00
|
|
|
|
2017-05-01 13:32:39 +00:00
|
|
|
return sLine
|
|
|
|
end
|
|
|
|
|
2019-07-12 21:04:28 +00:00
|
|
|
function loadfile( filename, mode, env )
|
|
|
|
-- 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")
|
|
|
|
|
|
|
|
local file = fs.open( filename, "r" )
|
|
|
|
if not file then return nil, "File not found" end
|
|
|
|
|
|
|
|
local func, err = load( file.readAll(), "@" .. fs.getName( filename ), mode, env )
|
|
|
|
file.close()
|
|
|
|
return func, err
|
2017-05-01 13:32:39 +00:00
|
|
|
end
|
|
|
|
|
2019-05-30 18:36:28 +00:00
|
|
|
function dofile( _sFile )
|
|
|
|
expect(1, _sFile, "string")
|
|
|
|
|
2019-07-12 21:04:28 +00:00
|
|
|
local fnFile, e = loadfile( _sFile, nil, _G )
|
2017-05-01 13:32:39 +00:00
|
|
|
if fnFile then
|
|
|
|
return fnFile()
|
|
|
|
else
|
|
|
|
error( e, 2 )
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Install the rest of the OS api
|
|
|
|
function os.run( _tEnv, _sPath, ... )
|
2019-05-30 18:36:28 +00:00
|
|
|
expect(1, _tEnv, "table")
|
|
|
|
expect(2, _sPath, "string")
|
|
|
|
|
2017-05-04 09:49:41 +00:00
|
|
|
local tArgs = table.pack( ... )
|
2017-05-01 13:32:39 +00:00
|
|
|
local tEnv = _tEnv
|
|
|
|
setmetatable( tEnv, { __index = _G } )
|
2019-07-12 21:04:28 +00:00
|
|
|
local fnFile, err = loadfile( _sPath, nil, tEnv )
|
2017-05-01 13:32:39 +00:00
|
|
|
if fnFile then
|
|
|
|
local ok, err = pcall( function()
|
2017-05-04 09:49:41 +00:00
|
|
|
fnFile( table.unpack( tArgs, 1, tArgs.n ) )
|
2017-05-01 13:32:39 +00:00
|
|
|
end )
|
|
|
|
if not ok then
|
|
|
|
if err and err ~= "" then
|
|
|
|
printError( err )
|
|
|
|
end
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
if err and err ~= "" then
|
|
|
|
printError( err )
|
|
|
|
end
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
local tAPIsLoading = {}
|
|
|
|
function os.loadAPI( _sPath )
|
2019-05-30 18:36:28 +00:00
|
|
|
expect(1, _sPath, "string")
|
2017-05-01 13:32:39 +00:00
|
|
|
local sName = fs.getName( _sPath )
|
2017-05-17 21:47:13 +00:00
|
|
|
if sName:sub(-4) == ".lua" then
|
|
|
|
sName = sName:sub(1,-5)
|
|
|
|
end
|
2017-05-01 13:32:39 +00:00
|
|
|
if tAPIsLoading[sName] == true then
|
|
|
|
printError( "API "..sName.." is already being loaded" )
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
tAPIsLoading[sName] = true
|
|
|
|
|
|
|
|
local tEnv = {}
|
|
|
|
setmetatable( tEnv, { __index = _G } )
|
2019-07-12 21:04:28 +00:00
|
|
|
local fnAPI, err = loadfile( _sPath, nil, tEnv )
|
2017-05-01 13:32:39 +00:00
|
|
|
if fnAPI then
|
|
|
|
local ok, err = pcall( fnAPI )
|
|
|
|
if not ok then
|
|
|
|
tAPIsLoading[sName] = nil
|
2017-11-15 16:22:36 +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
|
2017-11-15 16:22:36 +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 = {}
|
|
|
|
for k,v in pairs( tEnv ) do
|
|
|
|
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
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
function os.sleep( nTime )
|
|
|
|
sleep( nTime )
|
|
|
|
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,
|
|
|
|
OPTIONS = true, PUT = true, DELETE = true
|
|
|
|
}
|
|
|
|
|
|
|
|
local function checkKey( options, key, ty, opt )
|
|
|
|
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
|
|
|
|
|
|
|
|
local function checkOptions( options, body )
|
|
|
|
checkKey( options, "url", "string")
|
2019-05-30 18:36:28 +00:00
|
|
|
if body == false then
|
|
|
|
checkKey( options, "body", "nil" )
|
|
|
|
else
|
|
|
|
checkKey( options, "body", "string", not body )
|
|
|
|
end
|
2018-05-15 09:10:23 +00:00
|
|
|
checkKey( options, "headers", "table", true )
|
|
|
|
checkKey( options, "method", "string", true )
|
|
|
|
checkKey( options, "redirect", "boolean", true )
|
|
|
|
|
|
|
|
if options.method and not methods[options.method] then
|
|
|
|
error( "Unsupported HTTP method", 3 )
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
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
|
|
|
|
2017-05-12 21:49:44 +00:00
|
|
|
http.get = function( _url, _headers, _binary)
|
2018-05-15 09:10:23 +00:00
|
|
|
if type( _url ) == "table" then
|
|
|
|
checkOptions( _url, false )
|
|
|
|
return wrapRequest( _url.url, _url )
|
|
|
|
end
|
|
|
|
|
2019-05-30 18:36:28 +00:00
|
|
|
expect(1, _url, "string")
|
|
|
|
expect(2, _headers, "table", "nil")
|
|
|
|
expect(3, _binary, "boolean", "nil")
|
2018-05-15 09:10:23 +00:00
|
|
|
return wrapRequest( _url, _url, nil, _headers, _binary )
|
2017-05-01 13:32:39 +00:00
|
|
|
end
|
|
|
|
|
2017-05-12 21:49:44 +00:00
|
|
|
http.post = function( _url, _post, _headers, _binary)
|
2018-05-15 09:10:23 +00:00
|
|
|
if type( _url ) == "table" then
|
|
|
|
checkOptions( _url, true )
|
|
|
|
return wrapRequest( _url.url, _url )
|
|
|
|
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")
|
2018-05-15 09:10:23 +00:00
|
|
|
return wrapRequest( _url, _url, _post, _headers, _binary )
|
2017-05-01 13:32:39 +00:00
|
|
|
end
|
|
|
|
|
2017-05-12 21:49:44 +00:00
|
|
|
http.request = function( _url, _post, _headers, _binary )
|
2018-05-15 09:10:23 +00:00
|
|
|
local url
|
|
|
|
if type( _url ) == "table" then
|
|
|
|
checkOptions( _url )
|
|
|
|
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
|
|
|
|
2017-05-12 21:49:44 +00:00
|
|
|
local ok, err = nativeHTTPRequest( _url, _post, _headers, _binary )
|
2017-05-01 13:32:39 +00:00
|
|
|
if not ok then
|
2018-05-15 09:10:23 +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
|
|
|
|
http.checkURL = function( _url )
|
|
|
|
local ok, err = nativeCheckURL( _url )
|
|
|
|
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
|
|
|
|
local event, url, ok, err = os.pullEvent( "http_check" )
|
|
|
|
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
|
|
|
|
http.websocket = function( _url, _headers )
|
2019-05-30 18:36:28 +00:00
|
|
|
expect(1, _url, "string")
|
|
|
|
expect(2, _headers, "table", "nil")
|
|
|
|
|
2017-12-06 09:28:38 +00:00
|
|
|
local ok, err = nativeWebsocket( _url, _headers )
|
|
|
|
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 = {}
|
|
|
|
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")
|
|
|
|
|
2017-05-01 13:32:39 +00:00
|
|
|
bIncludeFiles = (bIncludeFiles ~= false)
|
|
|
|
bIncludeDirs = (bIncludeDirs ~= false)
|
|
|
|
local sDir = sLocation
|
|
|
|
local nStart = 1
|
|
|
|
local nSlash = string.find( sPath, "[/\\]", nStart )
|
|
|
|
if nSlash == 1 then
|
|
|
|
sDir = ""
|
|
|
|
nStart = 2
|
|
|
|
end
|
|
|
|
local sName
|
|
|
|
while not sName do
|
|
|
|
local nSlash = string.find( sPath, "[/\\]", nStart )
|
|
|
|
if nSlash then
|
|
|
|
local sPart = string.sub( sPath, nStart, nSlash - 1 )
|
|
|
|
sDir = fs.combine( sDir, sPart )
|
|
|
|
nStart = nSlash + 1
|
|
|
|
else
|
|
|
|
sName = string.sub( sPath, nStart )
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if fs.isDir( sDir ) then
|
|
|
|
local tResults = {}
|
|
|
|
if bIncludeDirs and sPath == "" then
|
|
|
|
table.insert( tResults, "." )
|
|
|
|
end
|
|
|
|
if sDir ~= "" then
|
|
|
|
if sPath == "" then
|
|
|
|
table.insert( tResults, (bIncludeDirs and "..") or "../" )
|
|
|
|
elseif sPath == "." then
|
|
|
|
table.insert( tResults, (bIncludeDirs and ".") or "./" )
|
|
|
|
end
|
|
|
|
end
|
|
|
|
local tFiles = fs.list( sDir )
|
|
|
|
for n=1,#tFiles do
|
|
|
|
local sFile = tFiles[n]
|
|
|
|
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 )
|
|
|
|
if bIsDir then
|
|
|
|
table.insert( tResults, sResult .. "/" )
|
|
|
|
if bIncludeDirs and #sResult > 0 then
|
|
|
|
table.insert( tResults, sResult )
|
|
|
|
end
|
|
|
|
else
|
|
|
|
if bIncludeFiles and #sResult > 0 then
|
|
|
|
table.insert( tResults, sResult )
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return tResults
|
|
|
|
end
|
|
|
|
return tEmpty
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Load APIs
|
|
|
|
local bAPIError = false
|
|
|
|
local tApis = fs.list( "rom/apis" )
|
|
|
|
for n,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
|
|
|
|
bAPIError = true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-06-12 22:13:06 +00:00
|
|
|
if turtle and fs.isDir( "rom/apis/turtle" ) then
|
2017-05-01 13:32:39 +00:00
|
|
|
-- Load turtle APIs
|
|
|
|
local tApis = fs.list( "rom/apis/turtle" )
|
|
|
|
for n,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
|
|
|
|
bAPIError = true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if pocket and fs.isDir( "rom/apis/pocket" ) then
|
|
|
|
-- Load pocket APIs
|
|
|
|
local tApis = fs.list( "rom/apis/pocket" )
|
|
|
|
for n,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
|
|
|
|
bAPIError = true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if commands and fs.isDir( "rom/apis/command" ) then
|
|
|
|
-- Load command APIs
|
2017-05-17 21:47:13 +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 = {
|
|
|
|
__index = function( table, key )
|
|
|
|
local value = rawget( table, key )
|
|
|
|
if value ~= nil then
|
|
|
|
return value
|
|
|
|
end
|
|
|
|
if type(key) == "string" then
|
|
|
|
local value = rawget( table, string.lower(key) )
|
|
|
|
if value ~= nil then
|
|
|
|
return value
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
}
|
|
|
|
setmetatable( commands, tCaseInsensitiveMetatable )
|
|
|
|
setmetatable( commands.async, tCaseInsensitiveMetatable )
|
|
|
|
|
|
|
|
-- Add global "exec" function
|
|
|
|
exec = commands.exec
|
|
|
|
else
|
|
|
|
bAPIError = true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if bAPIError then
|
|
|
|
print( "Press any key to continue" )
|
|
|
|
os.pullEvent( "key" )
|
|
|
|
term.clear()
|
|
|
|
term.setCursorPos( 1,1 )
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Set default settings
|
|
|
|
settings.set( "shell.allow_startup", true )
|
|
|
|
settings.set( "shell.allow_disk_startup", (commands == nil) )
|
|
|
|
settings.set( "shell.autocomplete", true )
|
2018-12-17 17:22:15 +00:00
|
|
|
settings.set( "edit.autocomplete", true )
|
2017-05-29 12:21:27 +00:00
|
|
|
settings.set( "edit.default_extension", "lua" )
|
2017-05-29 13:05:19 +00:00
|
|
|
settings.set( "paint.default_extension", "nfp" )
|
2017-05-01 13:32:39 +00:00
|
|
|
settings.set( "lua.autocomplete", true )
|
|
|
|
settings.set( "list.show_hidden", false )
|
2019-05-13 15:54:19 +00:00
|
|
|
settings.set( "motd.enable", false )
|
|
|
|
settings.set( "motd.path", "/rom/motd.txt:/motd.txt" )
|
2017-05-01 13:32:39 +00:00
|
|
|
if term.isColour() then
|
|
|
|
settings.set( "bios.use_multishell", true )
|
|
|
|
end
|
|
|
|
if _CC_DEFAULT_SETTINGS then
|
|
|
|
for sPair in string.gmatch( _CC_DEFAULT_SETTINGS, "[^,]+" ) do
|
|
|
|
local sName, sValue = string.match( sPair, "([^=]*)=(.*)" )
|
|
|
|
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
|
|
|
|
settings.set( sName, value )
|
|
|
|
else
|
|
|
|
settings.unset( sName )
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Load user settings
|
|
|
|
if fs.exists( ".settings" ) then
|
|
|
|
settings.load( ".settings" )
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Run the shell
|
|
|
|
local ok, err = pcall( function()
|
2018-12-17 17:22:15 +00:00
|
|
|
parallel.waitForAny(
|
2017-05-01 13:32:39 +00:00
|
|
|
function()
|
|
|
|
local sShell
|
|
|
|
if term.isColour() and settings.get( "bios.use_multishell" ) then
|
2017-05-17 14:17:12 +00:00
|
|
|
sShell = "rom/programs/advanced/multishell.lua"
|
2017-05-01 13:32:39 +00:00
|
|
|
else
|
2017-05-17 14:17:12 +00:00
|
|
|
sShell = "rom/programs/shell.lua"
|
2017-05-01 13:32:39 +00:00
|
|
|
end
|
|
|
|
os.run( {}, sShell )
|
2017-05-17 14:17:12 +00:00
|
|
|
os.run( {}, "rom/programs/shutdown.lua" )
|
2017-05-01 13:32:39 +00:00
|
|
|
end,
|
|
|
|
function()
|
|
|
|
rednet.run()
|
|
|
|
end )
|
|
|
|
end )
|
|
|
|
|
|
|
|
-- If the shell errored, let the user read it.
|
|
|
|
term.redirect( term.native() )
|
|
|
|
if not ok then
|
|
|
|
printError( err )
|
|
|
|
pcall( function()
|
|
|
|
term.setCursorBlink( false )
|
|
|
|
print( "Press any key to continue" )
|
|
|
|
os.pullEvent( "key" )
|
|
|
|
end )
|
|
|
|
end
|
|
|
|
|
|
|
|
-- End
|
|
|
|
os.shutdown()
|