mirror of
https://github.com/CCLeonOS/LeonOS.git
synced 2026-03-03 15:17:01 +00:00
feat: 初始提交 LeonOS 实现
添加 LeonOS 的基本实现,包括: - 核心 API 模块(colors, disk, gps, keys, multishell, parallel, rednet, redstone, settings, vector) - 命令行程序(about, alias, bg, clear, copy, delete, edit, fg, help, list, lua, mkdir, move, paint, peripherals, programs, reboot, set, shutdown, threads) - 系统启动脚本和包管理 - 文档(README.md, LICENSE) - 开发工具(devbin)和更新程序 实现功能: - 完整的线程管理系统 - 兼容 ComputerCraft 的 API 设计 - 改进的 shell 和命令补全系统 - 多标签终端支持 - 设置管理系统
This commit is contained in:
82
data/computercraft/lua/rom/apis/colors.lua
Normal file
82
data/computercraft/lua/rom/apis/colors.lua
Normal file
@@ -0,0 +1,82 @@
|
||||
-- rc.colors
|
||||
|
||||
local term = require("term")
|
||||
local bit32 = require("bit32")
|
||||
local expect = require("cc.expect")
|
||||
|
||||
local colors = {
|
||||
white = 0x1,
|
||||
orange = 0x2,
|
||||
magenta = 0x4,
|
||||
lightBlue = 0x8,
|
||||
yellow = 0x10,
|
||||
lime = 0x20,
|
||||
pink = 0x40,
|
||||
gray = 0x80,
|
||||
grey = 0x80,
|
||||
lightGray = 0x100,
|
||||
lightGrey = 0x100,
|
||||
cyan = 0x200,
|
||||
purple = 0x400,
|
||||
blue = 0x800,
|
||||
brown = 0x1000,
|
||||
green = 0x2000,
|
||||
red = 0x4000,
|
||||
black = 0x8000,
|
||||
}
|
||||
|
||||
local defaults = {
|
||||
0xf0f0f0, 0xf2b233, 0xe57fd8, 0x99b2f2,
|
||||
0xdede6c, 0x7fcc19, 0xf2b2cc, 0x4c4c4c,
|
||||
0x999999, 0x4c99b2, 0xb266e5, 0x3366cc,
|
||||
0x7f664c, 0x57a64e, 0xcc4c4c, 0x111111
|
||||
}
|
||||
|
||||
for i=1, #defaults, 1 do
|
||||
term.setPaletteColor(2^(i-1), defaults[i])
|
||||
end
|
||||
|
||||
function colors.combine(...)
|
||||
local ret = 0
|
||||
local cols = {...}
|
||||
for i=1, #cols, 1 do
|
||||
expect.expect(i, cols[i], "number")
|
||||
ret = bit32.bor(ret, cols[i])
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
function colors.subtract(cols, ...)
|
||||
expect.expect(1, cols, "number")
|
||||
local subt = {...}
|
||||
for i=1, #subt, 1 do
|
||||
expect.expect(i+1, subt[i], "number")
|
||||
cols = bit32.band(cols, bit32.bnot(subt[i]))
|
||||
end
|
||||
return cols
|
||||
end
|
||||
|
||||
colors.test = bit32.btest
|
||||
|
||||
function colors.packRGB(r, g, b)
|
||||
expect.expect(1, r, "number")
|
||||
if r > 1 then return r end
|
||||
expect.range(r, 0, 1)
|
||||
expect.range(expect.expect(2, g, "number"), 0, 1)
|
||||
expect.range(expect.expect(3, b, "number"), 0, 1)
|
||||
return (r * 255 * 0x10000) + (g * 255 * 0x100) + (b * 255)
|
||||
end
|
||||
|
||||
function colors.unpackRGB(rgb)
|
||||
expect.range(expect.expect(1, rgb, "number"), 0, 0xFFFFFF)
|
||||
return bit32.rshift(rgb, 16) / 255,
|
||||
bit32.rshift(bit32.band(rgb, 0xFF00), 8) / 255,
|
||||
bit32.band(rgb, 0xFF) / 255
|
||||
end
|
||||
|
||||
function colors.toBlit(color)
|
||||
expect.expect(1, color, "number")
|
||||
return string.format("%x", math.floor(math.log(color, 2)))
|
||||
end
|
||||
|
||||
return colors
|
||||
1
data/computercraft/lua/rom/apis/colours.lua
Normal file
1
data/computercraft/lua/rom/apis/colours.lua
Normal file
@@ -0,0 +1 @@
|
||||
return require("colors")
|
||||
35
data/computercraft/lua/rom/apis/disk.lua
Normal file
35
data/computercraft/lua/rom/apis/disk.lua
Normal file
@@ -0,0 +1,35 @@
|
||||
-- rc.disk
|
||||
|
||||
local p = require("peripheral")
|
||||
|
||||
local disk = {}
|
||||
|
||||
local function wrap(method)
|
||||
return function(name, ...)
|
||||
if not p.isPresent(name) then
|
||||
return nil
|
||||
end
|
||||
|
||||
return p.call(name, method, ...)
|
||||
end
|
||||
end
|
||||
|
||||
local methods = {
|
||||
isPresent = "isDiskPresent",
|
||||
getLabel = "getDiskLabel",
|
||||
setLabel = "setDiskLabel",
|
||||
hasData = false,
|
||||
getMountPath = false,
|
||||
hasAudio = false,
|
||||
getAudioTitle = false,
|
||||
playAudio = false,
|
||||
stopAudio = false,
|
||||
eject = "ejectDisk",
|
||||
getID = "getDiskID"
|
||||
}
|
||||
|
||||
for k, v in pairs(methods) do
|
||||
disk[k] = wrap(v or k)
|
||||
end
|
||||
|
||||
return disk
|
||||
18
data/computercraft/lua/rom/apis/gps.lua
Normal file
18
data/computercraft/lua/rom/apis/gps.lua
Normal file
@@ -0,0 +1,18 @@
|
||||
-- rc.gps
|
||||
|
||||
error("gps is not fully implemented", 0)
|
||||
|
||||
local expect = require("cc.expect").expect
|
||||
local rednet = require("rednet")
|
||||
|
||||
local gps = {}
|
||||
gps.CHANNEL_GPS = 65534
|
||||
|
||||
function gps.locate(timeout, debug)
|
||||
timeout = expect(1, timeout, "number", "nil") or 2
|
||||
expect(2, debug, "boolean", "nil")
|
||||
|
||||
rednet.broadcast()
|
||||
end
|
||||
|
||||
return gps
|
||||
102
data/computercraft/lua/rom/apis/help.lua
Normal file
102
data/computercraft/lua/rom/apis/help.lua
Normal file
@@ -0,0 +1,102 @@
|
||||
-- rc.help
|
||||
|
||||
local fs = require("fs")
|
||||
local thread = require("rc.thread")
|
||||
local expect = require("cc.expect").expect
|
||||
local completion = require("cc.completion")
|
||||
|
||||
local help = {}
|
||||
help._DEFAULT_PATH = "/rc/help"
|
||||
|
||||
function help.init()
|
||||
local vars = thread.vars()
|
||||
vars.help = vars.help or help._DEFAULT_PATH
|
||||
end
|
||||
|
||||
function help.path()
|
||||
return thread.vars().help or help._DEFAULT_PATH
|
||||
end
|
||||
|
||||
function help.setPath(new)
|
||||
expect(1, new, "string")
|
||||
thread.vars().help = new
|
||||
end
|
||||
|
||||
function help.lookup(topic)
|
||||
expect(1, topic, "string")
|
||||
|
||||
topic = topic
|
||||
|
||||
for directory in help.path():gmatch("[^:]+") do
|
||||
local try = fs.combine(directory, topic .. ".hlp")
|
||||
|
||||
if fs.exists(try) then
|
||||
return try
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function help.topics()
|
||||
local topics = {}
|
||||
|
||||
for directory in help.path():gmatch("[^:]+") do
|
||||
local _topics = fs.list(directory)
|
||||
for i=1, #_topics, 1 do
|
||||
topics[#topics+1] = _topics[i]:gsub("%.hlp$", "")
|
||||
end
|
||||
end
|
||||
|
||||
return topics
|
||||
end
|
||||
|
||||
function help.completeTopic(prefix)
|
||||
local topics = help.topics()
|
||||
table.sort(topics, function(a, b) return #a < #b end)
|
||||
|
||||
return completion.choice(prefix, topics)
|
||||
end
|
||||
|
||||
local directives = {
|
||||
color = function(c)
|
||||
return require("colors")[c or "white"]
|
||||
end,
|
||||
["break"] = function()
|
||||
return "\n"
|
||||
end
|
||||
}
|
||||
|
||||
function help.loadTopic(name)
|
||||
local path = help.lookup(name)
|
||||
if not path then return end
|
||||
|
||||
local handle = io.open(path, "r")
|
||||
local data = {}
|
||||
|
||||
local lastWasText = false
|
||||
for line in handle:lines() do
|
||||
if line:sub(1,2) == ">>" then
|
||||
lastWasText = false
|
||||
local words = {}
|
||||
for word in line:sub(3):gmatch("[^ ]+") do
|
||||
words[#words+1] = word
|
||||
end
|
||||
|
||||
if directives[words[1]] then
|
||||
data[#data+1] = directives[words[1]](table.unpack(words, 2))
|
||||
end
|
||||
|
||||
else
|
||||
if lastWasText then
|
||||
data[#data+1] = "\n"
|
||||
end
|
||||
lastWasText = true
|
||||
data[#data+1] = line
|
||||
end
|
||||
end
|
||||
|
||||
handle:close()
|
||||
|
||||
return data
|
||||
end
|
||||
|
||||
return help
|
||||
29
data/computercraft/lua/rom/apis/keys.lua
Normal file
29
data/computercraft/lua/rom/apis/keys.lua
Normal file
@@ -0,0 +1,29 @@
|
||||
-- rc.keys
|
||||
|
||||
local expect = require("cc.expect").expect
|
||||
|
||||
-- automatic keymap detection :)
|
||||
-- this uses a fair bit of magic
|
||||
local kmap = "lwjgl3"
|
||||
local mcver = tonumber(_HOST:match("%b()"):sub(2,-2):match("1%.(%d+)")) or 0
|
||||
if _HOST:match("CCEmuX") then
|
||||
-- use the 1.16.5 keymap
|
||||
kmap = "lwjgl3"
|
||||
elseif mcver <= 12 or _HOST:match("LeonOS%-PC") then
|
||||
-- use the 1.12.2 keymap
|
||||
kmap = "lwjgl2"
|
||||
end
|
||||
|
||||
local base = dofile("/rc/keymaps/"..kmap..".lua")
|
||||
local lib = {}
|
||||
|
||||
-- reverse-index it!
|
||||
for k, v in pairs(base) do lib[k] = v; lib[v] = k end
|
||||
lib["return"] = lib.enter
|
||||
|
||||
function lib.getName(code)
|
||||
expect(1, code, "number")
|
||||
return lib[code]
|
||||
end
|
||||
|
||||
return lib
|
||||
31
data/computercraft/lua/rom/apis/multishell.lua
Normal file
31
data/computercraft/lua/rom/apis/multishell.lua
Normal file
@@ -0,0 +1,31 @@
|
||||
-- multishell API
|
||||
|
||||
local thread = require("rc.thread")
|
||||
|
||||
local ms = {}
|
||||
|
||||
ms.getFocus = thread.getFocusedTab
|
||||
ms.setFocus = thread.setFocusedTab
|
||||
ms.getCurrent = thread.getCurrentTab
|
||||
|
||||
ms.setTitle = function() end
|
||||
ms.getTitle = function() return "???" end
|
||||
|
||||
ms.getCount = function() return #thread.info() end
|
||||
|
||||
function ms.launch(...)
|
||||
local env = _G
|
||||
local args = table.pack(...)
|
||||
|
||||
if type(args[1]) == "table" then
|
||||
env = table.remove(args, 1)
|
||||
end
|
||||
|
||||
local function func()
|
||||
return assert(loadfile(args[1], "bt", env))(table.unpack(args, 2, args.n))
|
||||
end
|
||||
|
||||
return (thread.launchTab(func, args[1]:sub(-8)))
|
||||
end
|
||||
|
||||
return ms
|
||||
158
data/computercraft/lua/rom/apis/paintutils.lua
Normal file
158
data/computercraft/lua/rom/apis/paintutils.lua
Normal file
@@ -0,0 +1,158 @@
|
||||
-- rc.paintutils
|
||||
|
||||
local term = require("term")
|
||||
local expect = require("cc.expect").expect
|
||||
local textutils = require("textutils")
|
||||
|
||||
local p = {}
|
||||
|
||||
function p.parseImage(str)
|
||||
expect(1, str, "string")
|
||||
return textutils.unserialize(str)
|
||||
end
|
||||
|
||||
function p.loadImage(path)
|
||||
expect(1, path, "string")
|
||||
local handle = io.open(path)
|
||||
if handle then
|
||||
local data = handle:read("a")
|
||||
handle:close()
|
||||
return p.parseImage(data)
|
||||
end
|
||||
end
|
||||
|
||||
function p.drawPixel(x, y, color)
|
||||
expect(1, x, "number")
|
||||
expect(2, y, "number")
|
||||
expect(2, color, "number", "nil")
|
||||
if color then term.setBackgroundColor(color) end
|
||||
term.at(x, y).write(" ")
|
||||
end
|
||||
|
||||
local function drawSteep(x0, y0, x1, y1, color)
|
||||
local distX = x1 - x0
|
||||
local distY = y1 - y0
|
||||
|
||||
local diff = 2*distX - distY
|
||||
|
||||
local x = x0
|
||||
for y=y0, y1, 1 do
|
||||
p.drawPixel(x, y, color)
|
||||
if diff > 0 then
|
||||
x = x + 1
|
||||
diff = diff + 2 * (distX - distY)
|
||||
else
|
||||
diff = diff + 2*distX
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function drawShallow(x0, y0, x1, y1, color)
|
||||
local distX, distY = x1 - x0, y1 - y0
|
||||
local diff = 2*distY - distX
|
||||
local y = y0
|
||||
|
||||
for x=x0, x1, 1 do
|
||||
p.drawPixel(x, y, color)
|
||||
if diff > 0 then
|
||||
y = y + 1
|
||||
diff = diff - 2*distX
|
||||
end
|
||||
diff = diff + 2*distY
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function p.drawLine(_startX, _startY, _endX, _endY, color)
|
||||
expect(1, _startX, "number")
|
||||
expect(2, _startY, "number")
|
||||
expect(3, _endX, "number")
|
||||
expect(4, _endY, "number")
|
||||
expect(5, color, "number")
|
||||
local startX, startY, endX, endY =
|
||||
math.min(_startX, _endX), math.min(_startY, _endY),
|
||||
math.max(_startX, _endX), math.max(_startY, _endY)
|
||||
|
||||
if startX == endX and startY == endY then
|
||||
return p.drawPixel(startX, startY, color)
|
||||
elseif startX == endX then
|
||||
if color then term.setBackgroundColor(color) end
|
||||
for y=startY, endY, 1 do
|
||||
term.at(startX, y).write(" ")
|
||||
end
|
||||
elseif startY == endY then
|
||||
if color then term.setBackgroundColor(color) end
|
||||
term.at(startX, startY).write((" "):rep(endX - startX))
|
||||
end
|
||||
|
||||
if (endY - startY) < (endX - startX) then
|
||||
drawShallow(startX, startY, endX, endY)
|
||||
else
|
||||
drawSteep(startX, startY, endX, endY)
|
||||
end
|
||||
end
|
||||
|
||||
function p.drawBox(startX, startY, endX, endY, color)
|
||||
expect(1, startX, "number")
|
||||
expect(2, startY, "number")
|
||||
expect(3, endX, "number")
|
||||
expect(4, endY, "number")
|
||||
expect(5, color, "number")
|
||||
|
||||
local col = string.format("%x", math.floor(math.log(color, 2)))
|
||||
local ht, hc = (" "):rep(endX-startX+1), col:rep(endX-startX+1)
|
||||
|
||||
term.at(startX, startY).blit(ht, hc, hc)
|
||||
for i=startY, endY do
|
||||
term.at(startX, i).blit(" ", col, col)
|
||||
term.at(endX, i).blit(" ", col, col)
|
||||
end
|
||||
term.at(startX, endY).blit(ht, hc, hc)
|
||||
end
|
||||
|
||||
function p.drawFilledBox(startX, startY, endX, endY, color)
|
||||
expect(1, startX, "number")
|
||||
expect(2, startY, "number")
|
||||
expect(3, endX, "number")
|
||||
expect(4, endY, "number")
|
||||
expect(5, color, "number")
|
||||
|
||||
local col = string.format("%x", math.floor(math.log(color, 2)))
|
||||
local ht, hc = (" "):rep(endX-startX+1), col:rep(endX-startX+1)
|
||||
|
||||
for y=startY, endY, 1 do
|
||||
term.at(startX, y).blit(ht, hc, hc)
|
||||
end
|
||||
end
|
||||
|
||||
function p.drawImage(img, x, y, frame)
|
||||
expect(1, img, "table")
|
||||
expect(2, x, "number")
|
||||
expect(3, y, "number")
|
||||
expect(4, frame, "number", "nil")
|
||||
|
||||
frame = frame or 1
|
||||
if not img[frame] then
|
||||
return nil, "invalid frame index " .. frame
|
||||
end
|
||||
|
||||
if img.palette then
|
||||
for k, v in pairs(img.palette) do
|
||||
term.setPaletteColor(k, table.unpack(v))
|
||||
end
|
||||
end
|
||||
|
||||
if img[frame].palette then
|
||||
for k, v in pairs(img[frame].palette) do
|
||||
term.setPaletteColor(k, table.unpack(v))
|
||||
end
|
||||
end
|
||||
|
||||
for i, line in ipairs(img[frame]) do
|
||||
term.at(x+i-1, y).blit(table.unpack(line))
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
return p
|
||||
54
data/computercraft/lua/rom/apis/parallel.lua
Normal file
54
data/computercraft/lua/rom/apis/parallel.lua
Normal file
@@ -0,0 +1,54 @@
|
||||
-- 'parallel' implementation
|
||||
-- uses LeonOS's native threading
|
||||
|
||||
local parallel = {}
|
||||
|
||||
local thread = require("rc.thread")
|
||||
local expect = require("cc.expect").expect
|
||||
|
||||
local function rand_id()
|
||||
local id = "parallel-"
|
||||
for _=1, 8, 1 do
|
||||
id = id .. string.char(math.random(33, 126))
|
||||
end
|
||||
return id
|
||||
end
|
||||
|
||||
local function waitForN(num, ...)
|
||||
local funcs = table.pack(...)
|
||||
|
||||
local threads = {}
|
||||
for i=1, #funcs, 1 do
|
||||
expect(i, funcs[i], "function")
|
||||
end
|
||||
|
||||
for i=1, #funcs, 1 do
|
||||
threads[i] = thread.spawn(funcs[i], rand_id())
|
||||
end
|
||||
|
||||
local dead = 0
|
||||
repeat
|
||||
coroutine.yield()
|
||||
for i=#threads, 1, -1 do
|
||||
if not thread.exists(threads[i]) then
|
||||
table.remove(threads, i)
|
||||
dead = dead + 1
|
||||
end
|
||||
end
|
||||
until dead >= num
|
||||
|
||||
-- clean up excess
|
||||
for i=1, #threads, 1 do
|
||||
thread.remove(threads[i])
|
||||
end
|
||||
end
|
||||
|
||||
function parallel.waitForAny(...)
|
||||
return waitForN(1, ...)
|
||||
end
|
||||
|
||||
function parallel.waitForAll(...)
|
||||
return waitForN(select("#", ...), ...)
|
||||
end
|
||||
|
||||
return parallel
|
||||
113
data/computercraft/lua/rom/apis/rednet.lua
Normal file
113
data/computercraft/lua/rom/apis/rednet.lua
Normal file
@@ -0,0 +1,113 @@
|
||||
-- rc.rednet
|
||||
|
||||
local expect = require("cc.expect").expect
|
||||
local peripheral = require("peripheral")
|
||||
|
||||
local rednet = {
|
||||
CHANNEL_BROADCAST = 65535,
|
||||
CHANNEL_REPEAT = 65533,
|
||||
MAX_ID_CHANNELS = 65500,
|
||||
}
|
||||
|
||||
local opened = {}
|
||||
function rednet.open(modem)
|
||||
expect(1, modem, "string")
|
||||
peripheral.call(modem, "open", os.computerID())
|
||||
peripheral.call(modem, "open", rednet.CHANNEL_BROADCAST)
|
||||
opened[modem] = true
|
||||
end
|
||||
|
||||
local function call(method, modem, erase, passids, ...)
|
||||
local ret = false
|
||||
if modem then
|
||||
if erase then opened[modem] = false end
|
||||
if passids then
|
||||
ret = ret or peripheral.call(modem, method, os.computerID(), ...)
|
||||
ret = ret or peripheral.call(modem, method, rednet.CHANNEL_BROADCAST, ...)
|
||||
else
|
||||
ret = peripheral.call(modem, method, ...)
|
||||
end
|
||||
|
||||
else
|
||||
for k in pairs(opened) do
|
||||
ret = ret or call(k, method, erase, passids, ...)
|
||||
end
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
function rednet.close(modem)
|
||||
expect(1, modem, "string", "nil")
|
||||
return call("close", modem, true, true)
|
||||
end
|
||||
|
||||
function rednet.isOpen(modem)
|
||||
expect(1, modem, "string", "nil")
|
||||
return call("isOpen", modem, false, true)
|
||||
end
|
||||
|
||||
function rednet.send(to, message, protocol)
|
||||
expect(1, to, "number")
|
||||
expect(2, message, "string", "table", "number", "boolean")
|
||||
expect(3, protocol, "string", "nil")
|
||||
|
||||
if type(message) == "table" then
|
||||
if protocol then table.insert(message, 1, protocol) end
|
||||
table.insert(message, 1, "rednet_message")
|
||||
else
|
||||
message = {"rednet_message", to, message, protocol}
|
||||
end
|
||||
call("transmit", nil, false, false, rednet.CHANNEL_BROADCAST,
|
||||
os.computerID(), message)
|
||||
return rednet.isOpen()
|
||||
end
|
||||
|
||||
function rednet.broadcast(message, protocol)
|
||||
expect(1, message, "string", "table", "number", "boolean")
|
||||
expect(2, protocol, "string", "nil")
|
||||
call("transmit", nil, false, false, rednet.CHANNEL_BROADCAST,
|
||||
rednet.CHANNEL_BROADCAST, message)
|
||||
end
|
||||
|
||||
function rednet.receive(protocol, timeout)
|
||||
expect(1, protocol, "string", "nil")
|
||||
timeout = expect(2, timeout, "number", "nil") or math.huge
|
||||
|
||||
local timer
|
||||
if timeout then
|
||||
timer = os.startTimer(timer)
|
||||
end
|
||||
|
||||
while true do
|
||||
local event = table.pack(os.pullEvent())
|
||||
if event[1] == "timer" and event[2] == timer then return end
|
||||
if event[1] == "rednet_message" and (event[4] == protocol or
|
||||
not protocol) then
|
||||
return table.unpack(event, 2)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local running = false
|
||||
function rednet.run()
|
||||
if running then
|
||||
error("rednet is already running")
|
||||
end
|
||||
|
||||
running = true
|
||||
|
||||
while true do
|
||||
local event = table.pack(os.pullEvent())
|
||||
if event[1] == "modem_message" then
|
||||
local message = event[5]
|
||||
if type(message) == "table" then
|
||||
if message[1] == "rednet_message" and (message[2] == os.computerID() or
|
||||
message[2] == rednet.CHANNEL_BROADCAST) then
|
||||
os.queueEvent("rednet_message", event[3], message[2], message[3])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return rednet
|
||||
114
data/computercraft/lua/rom/apis/settings.lua
Normal file
114
data/computercraft/lua/rom/apis/settings.lua
Normal file
@@ -0,0 +1,114 @@
|
||||
-- rc.settings
|
||||
|
||||
local expect = require("cc.expect")
|
||||
local textutils = require("textutils")
|
||||
|
||||
local settings = {}
|
||||
local defs = {}
|
||||
local set = {}
|
||||
|
||||
function settings.define(name, opts)
|
||||
expect.expect(1, name, "string")
|
||||
expect.expect(2, opts, "table", "nil")
|
||||
|
||||
opts = opts or {}
|
||||
opts.description = expect.field(opts, "description", "string", "nil")
|
||||
opts.type = expect.field(opts, "type", "string", "nil")
|
||||
defs[name] = opts
|
||||
end
|
||||
|
||||
function settings.undefine(name)
|
||||
expect.expect(1, name, "string")
|
||||
defs[name] = nil
|
||||
end
|
||||
|
||||
function settings.set(name, value)
|
||||
expect.expect(1, name, "string")
|
||||
|
||||
if defs[name] and defs[name].type then
|
||||
expect.expect(2, value, defs[name].type)
|
||||
else
|
||||
expect.expect(2, value, "number", "string", "boolean")
|
||||
end
|
||||
|
||||
set[name] = value
|
||||
end
|
||||
|
||||
function settings.get(name, default)
|
||||
expect.expect(1, name, "string")
|
||||
if set[name] ~= nil then
|
||||
return set[name]
|
||||
elseif default ~= nil then
|
||||
return default
|
||||
else
|
||||
return defs[name] and defs[name].default
|
||||
end
|
||||
end
|
||||
|
||||
function settings.getDetails(name)
|
||||
expect.expect(1, name, "string")
|
||||
local def = defs[name]
|
||||
if not def then return end
|
||||
return {
|
||||
description = def.description,
|
||||
default = def.default,
|
||||
value = set[name],
|
||||
type = def.type,
|
||||
}
|
||||
end
|
||||
|
||||
function settings.unset(name)
|
||||
expect.expect(1, name, "string")
|
||||
set[name] = nil
|
||||
end
|
||||
|
||||
function settings.clear()
|
||||
set = {}
|
||||
end
|
||||
|
||||
function settings.getNames()
|
||||
local names = {}
|
||||
for k in pairs(defs) do
|
||||
names[#names+1] = k
|
||||
end
|
||||
table.sort(names)
|
||||
return names
|
||||
end
|
||||
|
||||
function settings.load(path)
|
||||
expect.expect(1, path, "string", "nil")
|
||||
|
||||
path = path or ".settings"
|
||||
local handle = io.open(path, "r")
|
||||
if not handle then
|
||||
return false
|
||||
end
|
||||
|
||||
local data = handle:read("a")
|
||||
handle:close()
|
||||
|
||||
local new = textutils.unserialize(data)
|
||||
if not new then return false end
|
||||
for k, v in pairs(new) do
|
||||
set[k] = v
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function settings.save(path)
|
||||
expect.expect(1, path, "string", "nil")
|
||||
|
||||
path = path or ".settings"
|
||||
local data = textutils.serialize(set)
|
||||
|
||||
local handle = io.open(path, "w")
|
||||
if not handle then return false end
|
||||
|
||||
handle:write(data)
|
||||
handle:close()
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
return settings
|
||||
334
data/computercraft/lua/rom/apis/shell.lua
Normal file
334
data/computercraft/lua/rom/apis/shell.lua
Normal file
@@ -0,0 +1,334 @@
|
||||
-- LeonOS shell api
|
||||
|
||||
local shell = {}
|
||||
|
||||
local rc = require("rc")
|
||||
local fs = require("fs")
|
||||
local colors = require("colors")
|
||||
local thread = require("rc.thread")
|
||||
local expect = require("cc.expect").expect
|
||||
local settings = require("settings")
|
||||
local textutils = require("textutils")
|
||||
|
||||
local function copyIfPresent(f, t)
|
||||
if t[f] then
|
||||
local old = t[f]
|
||||
|
||||
t[f] = {}
|
||||
for k, v in pairs(old) do
|
||||
t[f][k] = v
|
||||
end
|
||||
|
||||
else
|
||||
t[f] = {}
|
||||
end
|
||||
end
|
||||
|
||||
local completions = {[0]={}}
|
||||
|
||||
function shell.init(env)
|
||||
local vars = thread.vars()
|
||||
|
||||
copyIfPresent("aliases", vars)
|
||||
completions[vars.parentShell or 0] = completions[vars.parentShell or 0] or {}
|
||||
|
||||
vars.path = vars.path or ".:/rc/programs"
|
||||
vars.env = env or _ENV or _G
|
||||
end
|
||||
|
||||
local builtins = {
|
||||
cd = function(dir)
|
||||
if dir then
|
||||
shell.setDir(dir)
|
||||
else
|
||||
print(shell.dir())
|
||||
end
|
||||
end,
|
||||
|
||||
exit = function()
|
||||
shell.exit()
|
||||
end,
|
||||
|
||||
alias = function(...)
|
||||
local args = {...}
|
||||
|
||||
if #args == 0 then
|
||||
textutils.coloredPrint(colors.yellow, "shell aliases", colors.white)
|
||||
|
||||
local aliases = shell.aliases()
|
||||
|
||||
local _aliases = {}
|
||||
for k, v in pairs(aliases) do
|
||||
table.insert(_aliases, {colors.cyan, k, colors.white, ":", v})
|
||||
end
|
||||
|
||||
textutils.pagedTabulate(_aliases)
|
||||
|
||||
elseif #args == 1 then
|
||||
shell.clearAlias(args[1])
|
||||
|
||||
elseif #args == 2 then
|
||||
shell.setAlias(args[1], args[2])
|
||||
|
||||
else
|
||||
error("this program takes a maximum of two arguments", 0)
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
local function callCommand(command, func, ...)
|
||||
thread.vars().program = command
|
||||
|
||||
local success, prog_err
|
||||
if settings.get("shell.tracebacks") then
|
||||
success, prog_err = xpcall(func, debug.traceback, ...)
|
||||
else
|
||||
success, prog_err = pcall(func, ...)
|
||||
end
|
||||
|
||||
thread.vars().program = "shell"
|
||||
|
||||
if not success then
|
||||
return nil, prog_err
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local function execProgram(fork, command, ...)
|
||||
local path, res_err = shell.resolveProgram(command)
|
||||
if not path then
|
||||
return nil, res_err
|
||||
end
|
||||
|
||||
local ok, err = loadfile(path, "t", thread.vars().env)
|
||||
|
||||
if not ok then
|
||||
return nil, err
|
||||
end
|
||||
|
||||
if fork then
|
||||
local args = table.pack(...)
|
||||
local result
|
||||
local id = thread.spawn(function()
|
||||
shell.init()
|
||||
result = table.pack(callCommand(path, ok,
|
||||
table.unpack(args, 1, args.n)))
|
||||
end, command)
|
||||
|
||||
repeat rc.sleep(0.05, true) until not thread.exists(id)
|
||||
|
||||
if result then
|
||||
return table.unpack(result, 1, result.n)
|
||||
end
|
||||
|
||||
return true
|
||||
|
||||
else
|
||||
return callCommand(path, ok, ...)
|
||||
end
|
||||
end
|
||||
|
||||
-- execute a command, but do NOT fork
|
||||
function shell.exec(command, ...)
|
||||
expect(1, command, "string")
|
||||
return execProgram(false, command, ...)
|
||||
end
|
||||
|
||||
function shell.execute(command, ...)
|
||||
expect(1, command, "string")
|
||||
|
||||
if builtins[command] then
|
||||
local func = builtins[command]
|
||||
return callCommand(command, func, ...)
|
||||
|
||||
else
|
||||
return execProgram(true, command, ...)
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local function tokenize(str)
|
||||
local words = {}
|
||||
for word in str:gmatch("[^ ]+") do
|
||||
words[#words+1] = word
|
||||
end
|
||||
return words
|
||||
end
|
||||
|
||||
function shell.run(...)
|
||||
return shell.execute(table.unpack(tokenize(table.concat({...}, " "))))
|
||||
end
|
||||
|
||||
-- difference: this exits the current thread immediately
|
||||
function shell.exit()
|
||||
thread.remove()
|
||||
end
|
||||
|
||||
function shell.dir()
|
||||
return thread.dir()
|
||||
end
|
||||
|
||||
function shell.setDir(dir)
|
||||
expect(1, dir, "string")
|
||||
return thread.setDir(shell.resolve(dir))
|
||||
end
|
||||
|
||||
function shell.path()
|
||||
return thread.vars().path
|
||||
end
|
||||
|
||||
function shell.setPath(path)
|
||||
expect(1, path, "string")
|
||||
thread.vars().path = path
|
||||
end
|
||||
|
||||
function shell.resolve(path)
|
||||
expect(1, path, "string")
|
||||
|
||||
if path:sub(1,1) == "/" then
|
||||
return path
|
||||
end
|
||||
|
||||
return fs.combine(thread.dir(), path)
|
||||
end
|
||||
|
||||
function shell.resolveProgram(path)
|
||||
expect(1, path, "string")
|
||||
|
||||
local aliases = thread.vars().aliases
|
||||
if aliases[path] then
|
||||
path = aliases[path]
|
||||
end
|
||||
|
||||
if fs.exists(path) and not fs.isDir(path) then
|
||||
return path
|
||||
end
|
||||
|
||||
for search in thread.vars().path:gmatch("[^:]+") do
|
||||
if search == "." then search = shell.dir() end
|
||||
local try1 = fs.combine(search, path)
|
||||
local try2 = fs.combine(search, path .. ".lua")
|
||||
if fs.exists(try1) and not fs.isDir(try1) then
|
||||
return try1
|
||||
end
|
||||
if fs.exists(try2) and not fs.isDir(try2) then
|
||||
return try2
|
||||
end
|
||||
end
|
||||
|
||||
return nil, "command not found"
|
||||
end
|
||||
|
||||
function shell.programs(hidden)
|
||||
expect(1, hidden, "boolean", "nil")
|
||||
|
||||
local programs = {}
|
||||
|
||||
local seen = {}
|
||||
for search in thread.vars().path:gmatch("[^:]+") do
|
||||
local files = fs.list(shell.resolve(search))
|
||||
for i=1, #files, 1 do
|
||||
programs[#programs+1] = files[i]:match("^(.+)%.lua$")
|
||||
if programs[#programs] then
|
||||
seen[programs[#programs]] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for alias in pairs(shell.aliases()) do
|
||||
if not seen[alias] then programs[#programs+1] = alias end
|
||||
end
|
||||
|
||||
for builtin in pairs(builtins) do
|
||||
if not seen[builtin] then programs[#programs+1] = builtin end
|
||||
end
|
||||
|
||||
return programs
|
||||
end
|
||||
|
||||
function shell.complete(line)
|
||||
expect(1, line, "string")
|
||||
|
||||
local words = tokenize(line)
|
||||
local aliases = thread.vars().aliases or {}
|
||||
|
||||
if #words > (line:sub(-1) == " " and 0 or 1) then
|
||||
words[1] = aliases[words[1]] or words[1]
|
||||
end
|
||||
|
||||
if line:sub(-1) == " " and #words > 0 then
|
||||
local complete = completions[thread.vars().parentShell or 0][words[1]]
|
||||
if complete then
|
||||
table.remove(words, 1)
|
||||
return complete(#words + 1, "", words)
|
||||
end
|
||||
else
|
||||
if #words == 1 then
|
||||
local opt = shell.completeProgram(words[1])
|
||||
|
||||
for i=1, #opt, 1 do
|
||||
if shell.resolveProgram(words[1] .. opt[i]) then
|
||||
opt[i] = opt[i] .. " "
|
||||
end
|
||||
end
|
||||
|
||||
return opt
|
||||
|
||||
else
|
||||
local complete = completions[thread.vars().parentShell or 0][words[1]]
|
||||
if complete then
|
||||
local arg = table.remove(words, #words)
|
||||
table.remove(words, 1)
|
||||
return complete(#words + 1, arg, words)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function shell.completeProgram(line)
|
||||
expect(1, line, "string")
|
||||
return require("cc.shell.completion").program(line)
|
||||
end
|
||||
|
||||
function shell.setCompletionFunction(program, complete)
|
||||
expect(1, program, "string")
|
||||
expect(2, complete, "function")
|
||||
completions[thread.vars().parentShell or 0][program] = complete
|
||||
end
|
||||
|
||||
function shell.getCompletionInfo()
|
||||
return completions[thread.vars().parentShell or 0]
|
||||
end
|
||||
|
||||
function shell.getRunningProgram()
|
||||
return thread.vars().program
|
||||
end
|
||||
|
||||
function shell.setAlias(command, program)
|
||||
expect(1, command, "string")
|
||||
expect(2, program, "string")
|
||||
|
||||
thread.vars().aliases[command] = program
|
||||
end
|
||||
|
||||
function shell.clearAlias(command)
|
||||
expect(1, command, "string")
|
||||
|
||||
thread.vars().aliases[command] = nil
|
||||
end
|
||||
|
||||
function shell.aliases()
|
||||
return thread.vars().aliases
|
||||
end
|
||||
|
||||
function shell.openTab(...)
|
||||
return require("multishell").launch(...)
|
||||
end
|
||||
|
||||
function shell.switchTab(id)
|
||||
return require("multishell").setFocus(id)
|
||||
end
|
||||
|
||||
return shell
|
||||
391
data/computercraft/lua/rom/apis/textutils.lua
Normal file
391
data/computercraft/lua/rom/apis/textutils.lua
Normal file
@@ -0,0 +1,391 @@
|
||||
-- rc.textutils
|
||||
|
||||
local rc = require("rc")
|
||||
local term = require("term")
|
||||
local json = require("rc.json")
|
||||
local colors = require("colors")
|
||||
local expect = require("cc.expect").expect
|
||||
local strings = require("cc.strings")
|
||||
|
||||
local tu = {}
|
||||
|
||||
function tu.slowWrite(text, rate)
|
||||
expect(1, text, "string")
|
||||
expect(2, rate, "number", "nil")
|
||||
|
||||
local delay = 1/(rate or 20)
|
||||
for c in text:gmatch(".") do
|
||||
rc.write(c)
|
||||
rc.sleep(delay)
|
||||
end
|
||||
end
|
||||
|
||||
function tu.slowPrint(text, rate)
|
||||
expect(1, text, "string")
|
||||
expect(2, rate, "number", "nil")
|
||||
tu.slowWrite(text.."\n", rate)
|
||||
end
|
||||
|
||||
function tu.formatTime(time, _24h)
|
||||
expect(1, time, "number")
|
||||
expect(2, _24h, "boolean", "nil")
|
||||
|
||||
local fmt = _24h and "!%H:%M" or "!%I:%M %p"
|
||||
|
||||
return (os.date(fmt, time * 3600):gsub("^ ", ""))
|
||||
end
|
||||
|
||||
local function pagedWrite(text, begin)
|
||||
local w, h = term.getSize()
|
||||
local x, y = term.getCursorPos()
|
||||
|
||||
local realTotal = 0
|
||||
local lines = begin or 0
|
||||
|
||||
local elements = strings.splitElements(text, w)
|
||||
|
||||
strings.wrappedWriteElements(elements, w, false, {
|
||||
newline = function()
|
||||
rc.write("\n")
|
||||
realTotal = realTotal + 1
|
||||
lines = lines + 1
|
||||
x, y = term.getCursorPos()
|
||||
|
||||
if lines >= h - 2 then
|
||||
local old = term.getTextColor()
|
||||
term.setTextColor(colors.white)
|
||||
rc.write("Press any key to continue")
|
||||
term.setTextColor(old)
|
||||
rc.pullEvent("char")
|
||||
local _, _y = term.getCursorPos()
|
||||
term.at(1, _y).clearLine()
|
||||
lines = 0
|
||||
end
|
||||
end,
|
||||
|
||||
append = function(newText)
|
||||
term.at(x, y).write(newText)
|
||||
x = x + #newText
|
||||
end,
|
||||
|
||||
getX = function() return x end
|
||||
})
|
||||
|
||||
return realTotal, lines
|
||||
end
|
||||
|
||||
function tu.pagedPrint(text)
|
||||
expect(1, text, "string")
|
||||
return pagedWrite(text .. "\n")
|
||||
end
|
||||
|
||||
local function coloredWrite(paged, ...)
|
||||
local args = table.pack(...)
|
||||
local lines = 0
|
||||
local pageLines = 0
|
||||
|
||||
local write = paged and pagedWrite or rc.write
|
||||
local old_fg, old_bg = term.getTextColor(), term.getBackgroundColor()
|
||||
local _, h = term.getSize()
|
||||
|
||||
for i=1, args.n, 1 do
|
||||
if type(args[i]) == "number" then
|
||||
term.setTextColor(args[i])
|
||||
elseif type(args[i]) == "table" then
|
||||
if args[i].fg or args[i][1] then
|
||||
term.setTextColor(args[i].fg or args[i][1])
|
||||
end
|
||||
|
||||
if args[i].bg or args[i][2] then
|
||||
term.setBackgroundColor(args[i].bg or args[i][2])
|
||||
end
|
||||
else
|
||||
local _lines, _tot = write(args[i], pageLines)
|
||||
lines = lines + _lines
|
||||
pageLines = _tot or 0
|
||||
while pageLines > h do pageLines = pageLines - h end
|
||||
end
|
||||
end
|
||||
|
||||
term.setTextColor(old_fg)
|
||||
term.setBackgroundColor(old_bg)
|
||||
|
||||
return lines
|
||||
end
|
||||
|
||||
local function tabulate(paged, ...)
|
||||
local args = table.pack(...)
|
||||
|
||||
local w = term.getSize()
|
||||
local max_len = 0
|
||||
|
||||
local linear = {}
|
||||
|
||||
for i=1, args.n, 1 do
|
||||
local argi = args[i]
|
||||
expect(i, argi, "table", "number")
|
||||
|
||||
if type(argi) == "table" then
|
||||
for n=1, #argi, 1 do
|
||||
if type(argi[n]) == "table" then
|
||||
local total_len = 2
|
||||
local argin = argi[n]
|
||||
|
||||
for j=1, #argin, 1 do
|
||||
expect(j, argin[j], "string", "number")
|
||||
if type(argin[j]) == "string" then
|
||||
total_len = total_len + #argin[j]
|
||||
end
|
||||
end
|
||||
|
||||
argin.total_len = total_len
|
||||
max_len = math.max(max_len, total_len + 2)
|
||||
|
||||
linear[#linear+1] = argi[n]
|
||||
|
||||
else
|
||||
linear[#linear+1] = expect(n, argi[n], "string")
|
||||
max_len = math.max(max_len, #argi[n] + 2)
|
||||
end
|
||||
end
|
||||
|
||||
else
|
||||
linear[#linear+1] = args[i]
|
||||
end
|
||||
end
|
||||
|
||||
local written = 0
|
||||
|
||||
local prt = paged and function(_args)
|
||||
if type(_args) == "string" then _args = {_args} end
|
||||
return coloredWrite(true, table.unpack(_args))
|
||||
end or function(_args)
|
||||
if type(_args) == "string" then _args = {_args} end
|
||||
return coloredWrite(false, table.unpack(_args))
|
||||
end
|
||||
|
||||
for i=1, #linear, 1 do
|
||||
local lini = linear[i]
|
||||
|
||||
if type(lini) == "number" then
|
||||
if written > 0 then
|
||||
prt("\n")
|
||||
written = 0
|
||||
end
|
||||
|
||||
term.setTextColor(lini)
|
||||
|
||||
else
|
||||
local len = type(lini) == "table" and lini.total_len or #lini
|
||||
if written + max_len > w then
|
||||
if written + len > w then
|
||||
prt("\n")
|
||||
prt(lini)
|
||||
rc.write((" "):rep(max_len - len))
|
||||
written = max_len
|
||||
|
||||
else
|
||||
prt(lini)
|
||||
prt("\n")
|
||||
written = 0
|
||||
end
|
||||
|
||||
else
|
||||
prt(lini)
|
||||
rc.write((" "):rep(max_len - len))
|
||||
written = written + max_len
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if written > 0 then
|
||||
prt("\n")
|
||||
end
|
||||
end
|
||||
|
||||
function tu.tabulate(...)
|
||||
tabulate(false, ...)
|
||||
end
|
||||
|
||||
function tu.pagedTabulate(...)
|
||||
tabulate(true, ...)
|
||||
end
|
||||
|
||||
local function mk_immut(str, field)
|
||||
return setmetatable({}, {
|
||||
__newindex = function()
|
||||
error(string.format("attempt to modify textutils.%s", field), 2)
|
||||
end,
|
||||
__tostring = function()
|
||||
return str
|
||||
end})
|
||||
end
|
||||
|
||||
tu.empty_json_array = mk_immut("[]", "empty_json_array")
|
||||
tu.json_null = mk_immut("null", "json_null")
|
||||
|
||||
local function serialize(t, _seen)
|
||||
local ret = ""
|
||||
|
||||
if type(t) == "table" then
|
||||
local seen = setmetatable({}, {__index = _seen})
|
||||
|
||||
ret = "{"
|
||||
for k, v in pairs(t) do
|
||||
if seen[k] then
|
||||
k = "<recursion>"
|
||||
end
|
||||
if seen[v] then
|
||||
v = "<recursion>"
|
||||
end
|
||||
if type(k) == "table" then
|
||||
seen[k] = true
|
||||
end
|
||||
if type(v) == "table" then
|
||||
seen[v] = true
|
||||
end
|
||||
ret = ret .. string.format("[%s] = %s,", serialize(k, seen),
|
||||
serialize(v, seen))
|
||||
end
|
||||
ret = ret .. "}"
|
||||
elseif type(t) == "function" or type(t) == "thread" or
|
||||
type(t) == "userdata" then
|
||||
error("cannot serialize type " .. type(t), 2)
|
||||
else
|
||||
return string.format("%q", t)
|
||||
end
|
||||
|
||||
return ret
|
||||
end
|
||||
|
||||
function tu.serialize(t, opts)
|
||||
expect(1, t, "table")
|
||||
expect(2, opts, "table", "nil")
|
||||
|
||||
return serialize(t, {})
|
||||
end
|
||||
|
||||
function tu.unserialize(s)
|
||||
expect(1, s, "string")
|
||||
local call = load("return " .. s, "=<unserialize>", "t", {})
|
||||
if call then return call() end
|
||||
end
|
||||
|
||||
tu.serialise = tu.serialize
|
||||
tu.unserialise = tu.unserialize
|
||||
|
||||
function tu.serializeJSON(t, nbt)
|
||||
expect(1, t, "table")
|
||||
if nbt then
|
||||
error("NBT mode is not yet supported")
|
||||
end
|
||||
return json.encode(t)
|
||||
end
|
||||
|
||||
function tu.unserializeJSON(s)--s, options)
|
||||
expect(1, s, "string")
|
||||
return json.decode(s)
|
||||
end
|
||||
|
||||
tu.serialiseJSON = tu.serializeJSON
|
||||
tu.unserialiseJSON = tu.unserializeJSON
|
||||
|
||||
function tu.urlEncode(str)
|
||||
expect(1, str, "string")
|
||||
|
||||
-- TODO: possibly UTF-8 support?
|
||||
str = str:gsub("[^%w %-%_%.]", function(c)
|
||||
return string.format("%%%02x", c:byte())
|
||||
end):gsub(" ", "+"):gsub("\n", "\r\n")
|
||||
|
||||
return str
|
||||
end
|
||||
|
||||
local function split(text)
|
||||
local dots = {""}
|
||||
|
||||
for c in text:gmatch(".") do
|
||||
if c == "." or c == ":" then
|
||||
--dots[#dots+1] = c
|
||||
dots[#dots+1] = ""
|
||||
else
|
||||
dots[#dots] = dots[#dots] .. c
|
||||
end
|
||||
end
|
||||
|
||||
return dots
|
||||
end
|
||||
|
||||
local function getSuffix(thing, default)
|
||||
if type(thing) == "table" then
|
||||
return "."
|
||||
elseif type(thing) == "function" then
|
||||
return "("
|
||||
end
|
||||
return default
|
||||
end
|
||||
|
||||
function tu.complete(text, env)
|
||||
expect(1, text, "string")
|
||||
env = expect(2, env, "table", "nil") or _G
|
||||
|
||||
local last_exp = text:match("[^%(%)%%%+%-%*/%[%]%{%}; =]*$")
|
||||
|
||||
local results = {}
|
||||
|
||||
if last_exp and #last_exp > 0 then
|
||||
local search = {env}
|
||||
local mt = getmetatable(env)
|
||||
if mt and type(mt.__index) == "table" then
|
||||
search[#search+1] = mt.__index
|
||||
end
|
||||
|
||||
for s=1, #search, 1 do
|
||||
local dots = split(last_exp)
|
||||
local current = search[s]
|
||||
|
||||
local final = 0
|
||||
for i=1, #dots, 1 do
|
||||
if current[dots[i]] then
|
||||
current = current[dots[i]]
|
||||
final = i
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
for _=1, final, 1 do table.remove(dots, 1) end
|
||||
|
||||
if #dots == 0 then
|
||||
results[#results+1] = getSuffix(current)
|
||||
end
|
||||
|
||||
if #dots ~= 1 or type(current) ~= "table" then return results end
|
||||
|
||||
local find = dots[1]
|
||||
for key, val in pairs(current) do
|
||||
key = key .. getSuffix(val, "")
|
||||
|
||||
if key:sub(1, #find) == find then
|
||||
results[#results+1] = key:sub(#find + 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return results
|
||||
end
|
||||
|
||||
function tu.coloredWrite(...)
|
||||
return coloredWrite(false, ...)
|
||||
end
|
||||
|
||||
function tu.coloredPrint(...)
|
||||
return coloredWrite(false, ...) + rc.write("\n")
|
||||
end
|
||||
|
||||
function tu.coloredPagedPrint(...)
|
||||
return coloredWrite(true, ...) + rc.write("\n")
|
||||
end
|
||||
|
||||
return tu
|
||||
76
data/computercraft/lua/rom/apis/vector.lua
Normal file
76
data/computercraft/lua/rom/apis/vector.lua
Normal file
@@ -0,0 +1,76 @@
|
||||
-- rc.vector
|
||||
|
||||
local vector = {}
|
||||
local Vector = {}
|
||||
|
||||
function Vector:add(o)
|
||||
return vector.new(self.x + o.x, self.y + o.y, self.z + o.z)
|
||||
end
|
||||
|
||||
function Vector:sub(o)
|
||||
return vector.new(self.x - o.x, self.y - o.y, self.z - o.z)
|
||||
end
|
||||
|
||||
function Vector:mul(m)
|
||||
return vector.new(self.x * m, self.y * m, self.z * m)
|
||||
end
|
||||
|
||||
function Vector:div(m)
|
||||
return vector.new(self.x / m, self.y / m, self.z / m)
|
||||
end
|
||||
|
||||
function Vector:unm()
|
||||
return vector.new(-self.x, -self.y, -self.z)
|
||||
end
|
||||
|
||||
function Vector:dot(o)
|
||||
return (self.x * o.x) + (self.y * o.y) + (self.z * o.z)
|
||||
end
|
||||
|
||||
function Vector:cross(o)
|
||||
return vector.new(
|
||||
(self.y * o.z) - (self.z * o.y),
|
||||
(self.z * o.x) - (self.x * o.z),
|
||||
(self.x * o.y) - (self.y * o.x))
|
||||
end
|
||||
|
||||
function Vector:length()
|
||||
return math.sqrt((self.x * self.x) + (self.y * self.y) + (self.z * self.z))
|
||||
end
|
||||
|
||||
function Vector:normalize()
|
||||
return self:div(self:length())
|
||||
end
|
||||
|
||||
function Vector:round(tolerance)
|
||||
tolerance = tolerance or 1
|
||||
local squared = tolerance * tolerance
|
||||
return vector.new(
|
||||
math.floor(self.x + (tolerance * 0.5)) / squared,
|
||||
math.floor(self.y + (tolerance * 0.5)) / squared,
|
||||
math.floor(self.z + (tolerance * 0.5)) / squared)
|
||||
end
|
||||
|
||||
function Vector:tostring()
|
||||
return string.format("%d,%d,%d", self.x, self.y, self.z)
|
||||
end
|
||||
|
||||
function Vector:equals(o)
|
||||
return self.x == o.x and self.y == o.y and self.z == o.z
|
||||
end
|
||||
|
||||
Vector.eq = Vector.equals
|
||||
|
||||
local vect_mt = {
|
||||
__index = Vector
|
||||
}
|
||||
|
||||
for k, v in pairs(Vector) do
|
||||
vect_mt["__"..k] = v
|
||||
end
|
||||
|
||||
function vector.new(x, y, z)
|
||||
return setmetatable({x = x or 0, y = y or 0, z = z or 0}, vect_mt)
|
||||
end
|
||||
|
||||
return vector
|
||||
368
data/computercraft/lua/rom/apis/window.lua
Normal file
368
data/computercraft/lua/rom/apis/window.lua
Normal file
@@ -0,0 +1,368 @@
|
||||
-- window api
|
||||
|
||||
local term = require("term")
|
||||
local colors = require("colors")
|
||||
local expect = require("cc.expect").expect
|
||||
local range = require("cc.expect").range
|
||||
local window = {}
|
||||
|
||||
local rep = string.rep
|
||||
local sub = string.sub
|
||||
local max = math.max
|
||||
local min = math.min
|
||||
|
||||
local function into_buffer(buf, x, y, text)
|
||||
if not text then return end
|
||||
if not buf[y] then return end
|
||||
text = sub(text, 1, #buf[y] - x + 1)
|
||||
if x < 1 then
|
||||
text = sub(text, -x + 2)
|
||||
x = 1
|
||||
end
|
||||
local olen = #buf[y]
|
||||
if x + #text > olen then
|
||||
buf[y] = sub(buf[y], 0, max(0, x-1)) .. text
|
||||
else
|
||||
buf[y] = sub(buf[y], 0, max(0, x-1)) .. text .. buf[y]:sub(x + #text)
|
||||
end
|
||||
buf[y] = sub(buf[y], 1, olen)
|
||||
end
|
||||
|
||||
function window.create(parent, x, y, width, height, visible)
|
||||
if type(parent) ~= "table" then expect(1, parent, "table") end
|
||||
if parent == term then
|
||||
error("do not pass 'term' as a window parent", 0)
|
||||
end
|
||||
if type(x) ~= "number" then expect(2, x, "number") end
|
||||
if type(y) ~= "number" then expect(3, y, "number") end
|
||||
if type(width) ~= "number" then expect(4, width, "number") end
|
||||
if type(height) ~= "number" then expect(5, height, "number") end
|
||||
if type(visible) ~= "boolean" then expect(6, visible, "boolean", "nil") end
|
||||
if visible == nil then visible = true end
|
||||
|
||||
local cursorX, cursorY, cursorBlink = 1, 1, false
|
||||
local foreground, background = colors.toBlit(colors.white),
|
||||
colors.toBlit(colors.black)
|
||||
local textbuf, fgbuf, bgbuf = {}, {}, {}
|
||||
|
||||
local win = {}
|
||||
|
||||
local palette = {}
|
||||
for i=0, 15, 1 do
|
||||
palette[i] = colors.packRGB(parent.getPaletteColor(2^i))
|
||||
end
|
||||
|
||||
local function drawLine(i)
|
||||
parent.setCursorPos(x, y + i - 1)
|
||||
if not textbuf[i] then return end
|
||||
parent.blit(textbuf[i], fgbuf[i], bgbuf[i])
|
||||
end
|
||||
|
||||
local function draw()
|
||||
local blink = parent.getCursorBlink()
|
||||
parent.setCursorBlink(false)
|
||||
|
||||
local parentW, parentH = parent.getSize()
|
||||
local firstVisible = math.max(1, -y+2)
|
||||
|
||||
for i=1, math.min(height, parentH), 1 do
|
||||
drawLine(firstVisible+i-1)
|
||||
end
|
||||
|
||||
parent.setCursorBlink(blink)
|
||||
end
|
||||
|
||||
local function restorePalette()
|
||||
for i=0, 15, 1 do
|
||||
parent.setPaletteColor(2^i, palette[i])
|
||||
end
|
||||
end
|
||||
|
||||
local function restoreCursorBlink()
|
||||
parent.setCursorBlink(cursorBlink)
|
||||
end
|
||||
|
||||
local function restoreCursorPos()
|
||||
if cursorX > 0 and cursorY > 0 and
|
||||
cursorX <= width and cursorY <= height then
|
||||
parent.setCursorPos(x + cursorX - 1, y + cursorY - 1)
|
||||
else
|
||||
parent.setCursorPos(0, 0)
|
||||
end
|
||||
end
|
||||
|
||||
local function restoreCursorColor()
|
||||
parent.setTextColor(2^tonumber(foreground, 16))
|
||||
end
|
||||
|
||||
function win.write(text)
|
||||
if type(text) ~= "string" then expect(1, text, "string") end
|
||||
local fg, bg = rep(foreground, #text), background:rep(#text)
|
||||
into_buffer(textbuf, cursorX, cursorY, text)
|
||||
into_buffer(fgbuf, cursorX, cursorY, fg)
|
||||
into_buffer(bgbuf, cursorX, cursorY, bg)
|
||||
cursorX = max(-100, min(cursorX + #text, width + 1))
|
||||
|
||||
local firstVisible, _, maxHeight = math.max(1, -y+2), parent.getSize()
|
||||
if visible and cursorY >= firstVisible and cursorY <= firstVisible+maxHeight then win.redraw() end
|
||||
end
|
||||
|
||||
function win.blit(text, tcol, bcol)
|
||||
if type(text) ~= "string" then expect(1, text, "string") end
|
||||
if type(tcol) ~= "string" then expect(2, tcol, "string") end
|
||||
if type(bcol) ~= "string" then expect(3, bcol, "string") end
|
||||
assert(#text == #tcol and #text == #bcol, "mismatched argument lengths")
|
||||
|
||||
into_buffer(textbuf, cursorX, cursorY, text)
|
||||
into_buffer(fgbuf, cursorX, cursorY, tcol)
|
||||
into_buffer(bgbuf, cursorX, cursorY, bcol)
|
||||
cursorX = max(0, min(cursorX + #text, width + 1))
|
||||
|
||||
local firstVisible, _, maxHeight = math.max(1, -y+2), parent.getSize()
|
||||
if visible and cursorY >= firstVisible and cursorY <= firstVisible+maxHeight then
|
||||
drawLine(cursorY)
|
||||
restoreCursorColor()
|
||||
restoreCursorPos()
|
||||
end
|
||||
end
|
||||
|
||||
function win.clear()
|
||||
local fore = rep(foreground, width)
|
||||
local back = rep(background, width)
|
||||
local blank = rep(" ", width)
|
||||
|
||||
for i=1, height, 1 do
|
||||
textbuf[i] = blank
|
||||
fgbuf[i] = fore
|
||||
bgbuf[i] = back
|
||||
end
|
||||
|
||||
if visible then
|
||||
win.redraw()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function win.clearLine()
|
||||
local emptyText, emptyFg, emptyBg =
|
||||
rep(" ", width),
|
||||
rep(foreground, width),
|
||||
rep(background, width)
|
||||
|
||||
textbuf[cursorY] = emptyText
|
||||
fgbuf[cursorY] = emptyFg
|
||||
bgbuf[cursorY] = emptyBg
|
||||
|
||||
local firstVisible, _, maxHeight = math.max(1, -y+2), parent.getSize()
|
||||
if visible and cursorY >= firstVisible and cursorY <= firstVisible+maxHeight then
|
||||
win.redraw()
|
||||
end
|
||||
end
|
||||
|
||||
function win.getCursorPos()
|
||||
return cursorX, cursorY
|
||||
end
|
||||
|
||||
function win.setCursorPos(_x, _y)
|
||||
if type(_x) ~= "number" then expect(1, _x, "number") end
|
||||
if type(_y) ~= "number" then expect(2, _y, "number") end
|
||||
|
||||
cursorX, cursorY = _x, _y
|
||||
if visible then
|
||||
restoreCursorPos()
|
||||
end
|
||||
end
|
||||
|
||||
function win.setCursorBlink(blink)
|
||||
cursorBlink = not not blink
|
||||
if visible then
|
||||
restoreCursorBlink()
|
||||
end
|
||||
end
|
||||
|
||||
function win.getCursorBlink()
|
||||
return cursorBlink
|
||||
end
|
||||
|
||||
function win.isColor()
|
||||
return parent.isColor()
|
||||
end
|
||||
|
||||
win.isColour = win.isColor
|
||||
|
||||
function win.setTextColor(color)
|
||||
if type(color) ~= "number" then expect(1, color, "number") end
|
||||
foreground = colors.toBlit(color) or foreground
|
||||
if visible then
|
||||
restoreCursorColor()
|
||||
end
|
||||
end
|
||||
|
||||
win.setTextColour = win.setTextColor
|
||||
|
||||
function win.setPaletteColor(color, r, g, b)
|
||||
if type(color) ~= "number" then expect(1, color, "number") end
|
||||
if type(r) ~= "number" then expect(2, r, "number") end
|
||||
|
||||
if r < 1 then
|
||||
if type(g) ~= "number" then expect(3, g, "number") end
|
||||
if type(b) ~= "number" then expect(4, b, "number") end
|
||||
palette[math.floor(math.log(color, 2))] = colors.packRGB(r, g, b)
|
||||
else
|
||||
palette[math.floor(math.log(color, 2))] = r
|
||||
end
|
||||
|
||||
if visible then
|
||||
restorePalette()
|
||||
end
|
||||
end
|
||||
|
||||
win.setPaletteColour = win.setPaletteColor
|
||||
|
||||
function win.getPaletteColor(color)
|
||||
if type(color) ~= "number" then expect(1, color, "number") end
|
||||
return palette[math.floor(math.log(color, 2))]
|
||||
end
|
||||
|
||||
win.getPaletteColour = win.getPaletteColor
|
||||
|
||||
function win.setBackgroundColor(color)
|
||||
if type(color) ~= "number" then expect(1, color, "number") end
|
||||
background = colors.toBlit(color)
|
||||
end
|
||||
|
||||
win.setBackgroundColour = win.setBackgroundColor
|
||||
|
||||
function win.getSize()
|
||||
return width, height
|
||||
end
|
||||
|
||||
function win.scroll(n)
|
||||
if type(n) ~= "number" then expect(1, n, "number") end
|
||||
|
||||
if n == 0 then return end
|
||||
local fg = rep(foreground, width)
|
||||
local bg = rep(background, width)
|
||||
local blank = rep(" ", width)
|
||||
|
||||
if n > 0 then
|
||||
for _=1, n, 1 do
|
||||
table.remove(textbuf, 1)
|
||||
textbuf[#textbuf+1] = blank
|
||||
table.remove(fgbuf, 1)
|
||||
fgbuf[#fgbuf+1] = fg
|
||||
table.remove(bgbuf, 1)
|
||||
bgbuf[#bgbuf+1] = bg
|
||||
end
|
||||
else
|
||||
for _=1, -n, 1 do
|
||||
table.insert(textbuf, 1, blank)
|
||||
textbuf[#textbuf] = nil
|
||||
table.insert(fgbuf, 1, fg)
|
||||
fgbuf[#fgbuf] = nil
|
||||
table.insert(bgbuf, 1, bg)
|
||||
bgbuf[#bgbuf] = nil
|
||||
end
|
||||
end
|
||||
|
||||
if visible then
|
||||
win.redraw()
|
||||
end
|
||||
end
|
||||
|
||||
function win.getTextColor()
|
||||
return 2^tonumber(foreground, 16)
|
||||
end
|
||||
|
||||
win.getTextColour = win.getTextColor
|
||||
|
||||
function win.getBackgroundColor()
|
||||
return 2^tonumber(background, 16)
|
||||
end
|
||||
|
||||
win.getBackgroundColour = win.getBackgroundColor
|
||||
|
||||
function win.getLine(ly)
|
||||
if type(ly) ~= "number" then expect(1, ly, "number") end
|
||||
if ly < 1 or ly > height then range(ly, 1, height) end
|
||||
return textbuf[ly], fgbuf[ly], bgbuf[ly]
|
||||
end
|
||||
|
||||
function win.setVisible(vis)
|
||||
if vis and not visible then
|
||||
draw()
|
||||
restorePalette()
|
||||
restoreCursorBlink()
|
||||
restoreCursorPos()
|
||||
restoreCursorColor()
|
||||
end
|
||||
visible = not not vis
|
||||
end
|
||||
|
||||
function win.redraw()
|
||||
if visible then
|
||||
draw()
|
||||
restorePalette()
|
||||
restoreCursorPos()
|
||||
restoreCursorBlink()
|
||||
restoreCursorColor()
|
||||
end
|
||||
end
|
||||
|
||||
function win.restoreCursor()
|
||||
if visible then
|
||||
restoreCursorBlink()
|
||||
restoreCursorPos()
|
||||
restoreCursorColor()
|
||||
end
|
||||
end
|
||||
|
||||
function win.getPosition()
|
||||
return x, y
|
||||
end
|
||||
|
||||
local function resize_buffer(buf, nw, nh, c)
|
||||
if nw > width then
|
||||
for i=1, #buf, 1 do
|
||||
buf[i] = buf[i] .. sub(rep(buf[i], -1), nw - width)
|
||||
end
|
||||
end
|
||||
|
||||
if nh > #buf then
|
||||
for _=1, nh - #buf, 1 do
|
||||
buf[#buf+1] = rep(c, nw)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function win.reposition(nx, ny, nw, nh, npar)
|
||||
if type(nx) ~= "number" then expect(1, nx, "number") end
|
||||
if type(ny) ~= "number" then expect(2, ny, "number") end
|
||||
if type(nw) ~= "number" then expect(3, nw, "number", "nil") end
|
||||
if type(nh) ~= "number" then expect(4, nh, "number", "nil") end
|
||||
if type(npar) ~= "table" then expect(5, npar, "table", "nil") end
|
||||
|
||||
x, y, width, height, parent =
|
||||
nx or x, ny or y,
|
||||
nw or width, nh or height,
|
||||
npar or parent
|
||||
|
||||
resize_buffer(textbuf, width, height, " ")
|
||||
resize_buffer(fgbuf, width, height, "0")
|
||||
resize_buffer(bgbuf, width, height, "f")
|
||||
|
||||
if visible then
|
||||
win.redraw()
|
||||
end
|
||||
end
|
||||
|
||||
function win.at(_x, _y)
|
||||
win.setCursorPos(_x, _y)
|
||||
return win
|
||||
end
|
||||
|
||||
win.clear()
|
||||
return win
|
||||
end
|
||||
|
||||
return window
|
||||
6
data/computercraft/lua/rom/completions/alias.lua
Normal file
6
data/computercraft/lua/rom/completions/alias.lua
Normal file
@@ -0,0 +1,6 @@
|
||||
local shell = require("shell")
|
||||
local completion = require("cc.shell.completion")
|
||||
|
||||
shell.setCompletionFunction("alias", completion.build(
|
||||
nil, completion.program
|
||||
))
|
||||
6
data/computercraft/lua/rom/completions/bedit.lua
Normal file
6
data/computercraft/lua/rom/completions/bedit.lua
Normal file
@@ -0,0 +1,6 @@
|
||||
local shell = require("shell")
|
||||
local completion = require("cc.shell.completion")
|
||||
|
||||
shell.setCompletionFunction("bedit", completion.build(
|
||||
completion.dirOrFile
|
||||
))
|
||||
6
data/computercraft/lua/rom/completions/bg.lua
Normal file
6
data/computercraft/lua/rom/completions/bg.lua
Normal file
@@ -0,0 +1,6 @@
|
||||
local shell = require("shell")
|
||||
local completion = require("cc.shell.completion")
|
||||
|
||||
shell.setCompletionFunction("bg", completion.build(
|
||||
{ completion.programWithArgs, 1, many = true }
|
||||
))
|
||||
4
data/computercraft/lua/rom/completions/cd.lua
Normal file
4
data/computercraft/lua/rom/completions/cd.lua
Normal file
@@ -0,0 +1,4 @@
|
||||
local shell = require("shell")
|
||||
local completion = require("cc.shell.completion")
|
||||
|
||||
shell.setCompletionFunction("enter", completion.build(completion.dir))
|
||||
7
data/computercraft/lua/rom/completions/copy.lua
Normal file
7
data/computercraft/lua/rom/completions/copy.lua
Normal file
@@ -0,0 +1,7 @@
|
||||
local shell = require("shell")
|
||||
local completion = require("cc.shell.completion")
|
||||
|
||||
shell.setCompletionFunction("copy", completion.build(
|
||||
{completion.dirOrFile, true},
|
||||
{completion.dirOrFile, many = true}
|
||||
))
|
||||
6
data/computercraft/lua/rom/completions/delete.lua
Normal file
6
data/computercraft/lua/rom/completions/delete.lua
Normal file
@@ -0,0 +1,6 @@
|
||||
local shell = require("shell")
|
||||
local completion = require("cc.shell.completion")
|
||||
|
||||
shell.setCompletionFunction("delete", completion.build(
|
||||
{completion.dirOrFile, many = true}
|
||||
))
|
||||
11
data/computercraft/lua/rom/completions/devbin.lua
Normal file
11
data/computercraft/lua/rom/completions/devbin.lua
Normal file
@@ -0,0 +1,11 @@
|
||||
local shell = require("shell")
|
||||
local completion = require("cc.shell.completion")
|
||||
|
||||
shell.setCompletionFunction("devbin", completion.build(
|
||||
{ completion.choice, { "put", "get", "run" }, true },
|
||||
function(cur, prev)
|
||||
if prev[1] == "put" then
|
||||
return completion.dirOrFile(cur, prev)
|
||||
end
|
||||
end
|
||||
))
|
||||
6
data/computercraft/lua/rom/completions/edit.lua
Normal file
6
data/computercraft/lua/rom/completions/edit.lua
Normal file
@@ -0,0 +1,6 @@
|
||||
local shell = require("shell")
|
||||
local completion = require("cc.shell.completion")
|
||||
|
||||
shell.setCompletionFunction("edit", completion.build(
|
||||
completion.dirOrFile
|
||||
))
|
||||
6
data/computercraft/lua/rom/completions/fg.lua
Normal file
6
data/computercraft/lua/rom/completions/fg.lua
Normal file
@@ -0,0 +1,6 @@
|
||||
local shell = require("shell")
|
||||
local completion = require("cc.shell.completion")
|
||||
|
||||
shell.setCompletionFunction("fg", completion.build(
|
||||
{ completion.programWithArgs, 1, many = true }
|
||||
))
|
||||
7
data/computercraft/lua/rom/completions/help.lua
Normal file
7
data/computercraft/lua/rom/completions/help.lua
Normal file
@@ -0,0 +1,7 @@
|
||||
local help = require("help")
|
||||
local shell = require("shell")
|
||||
local completion = require("cc.shell.completion")
|
||||
|
||||
shell.setCompletionFunction("help", completion.build(
|
||||
{help.completeTopic, many = true}
|
||||
))
|
||||
6
data/computercraft/lua/rom/completions/list.lua
Normal file
6
data/computercraft/lua/rom/completions/list.lua
Normal file
@@ -0,0 +1,6 @@
|
||||
local shell = require("shell")
|
||||
local completion = require("cc.shell.completion")
|
||||
|
||||
shell.setCompletionFunction("list", completion.build(
|
||||
{completion.dir, many = true}
|
||||
))
|
||||
6
data/computercraft/lua/rom/completions/mkdir.lua
Normal file
6
data/computercraft/lua/rom/completions/mkdir.lua
Normal file
@@ -0,0 +1,6 @@
|
||||
local shell = require("shell")
|
||||
local completion = require("cc.shell.completion")
|
||||
|
||||
shell.setCompletionFunction("mkdir", completion.build(
|
||||
{completion.dir, many = true}
|
||||
))
|
||||
7
data/computercraft/lua/rom/completions/move.lua
Normal file
7
data/computercraft/lua/rom/completions/move.lua
Normal file
@@ -0,0 +1,7 @@
|
||||
local shell = require("shell")
|
||||
local completion = require("cc.shell.completion")
|
||||
|
||||
shell.setCompletionFunction("move", completion.build(
|
||||
{completion.dirOrFile, true},
|
||||
{completion.dirOrFile, many = true}
|
||||
))
|
||||
6
data/computercraft/lua/rom/completions/paint.lua
Normal file
6
data/computercraft/lua/rom/completions/paint.lua
Normal file
@@ -0,0 +1,6 @@
|
||||
local shell = require("shell")
|
||||
local completion = require("cc.shell.completion")
|
||||
|
||||
shell.setCompletionFunction("paint", completion.build(
|
||||
|
||||
))
|
||||
6
data/computercraft/lua/rom/completions/reboot.lua
Normal file
6
data/computercraft/lua/rom/completions/reboot.lua
Normal file
@@ -0,0 +1,6 @@
|
||||
local shell = require("shell")
|
||||
local completion = require("cc.shell.completion")
|
||||
|
||||
shell.setCompletionFunction("restart", completion.build(
|
||||
{ completion.choice, { "now" } }
|
||||
))
|
||||
13
data/computercraft/lua/rom/completions/redstone.lua
Normal file
13
data/computercraft/lua/rom/completions/redstone.lua
Normal file
@@ -0,0 +1,13 @@
|
||||
local shell = require("shell")
|
||||
local complete = require("cc.completion")
|
||||
local completion = require("cc.shell.completion")
|
||||
|
||||
shell.setCompletionFunction("redstone", completion.build(
|
||||
{ completion.choice, {"probe", "set", "pulse"}, {false, true, true} },
|
||||
completion.side,
|
||||
function(cur, prev)
|
||||
if prev[1] == "set" then
|
||||
return complete.color(cur, true)
|
||||
end
|
||||
end
|
||||
))
|
||||
6
data/computercraft/lua/rom/completions/set.lua
Normal file
6
data/computercraft/lua/rom/completions/set.lua
Normal file
@@ -0,0 +1,6 @@
|
||||
local shell = require("shell")
|
||||
local completion = require("cc.shell.completion")
|
||||
|
||||
shell.setCompletionFunction("set", completion.build(
|
||||
{ completion.setting, true }
|
||||
))
|
||||
6
data/computercraft/lua/rom/completions/shutdown.lua
Normal file
6
data/computercraft/lua/rom/completions/shutdown.lua
Normal file
@@ -0,0 +1,6 @@
|
||||
local shell = require("shell")
|
||||
local completion = require("cc.shell.completion")
|
||||
|
||||
shell.setCompletionFunction("shutdown", completion.build(
|
||||
{ completion.choice, { "now" } }
|
||||
))
|
||||
308
data/computercraft/lua/rom/editors/advanced.lua
Normal file
308
data/computercraft/lua/rom/editors/advanced.lua
Normal file
@@ -0,0 +1,308 @@
|
||||
-- A much better editor.
|
||||
|
||||
local rc = require("rc")
|
||||
local keys = require("keys")
|
||||
local term = require("term")
|
||||
local colors = require("colors")
|
||||
local settings = require("settings")
|
||||
local textutils = require("textutils")
|
||||
|
||||
local args = {...}
|
||||
|
||||
local type_colors = {
|
||||
separator = colors[settings.get("edit.color_separator") or "lightBlue"],
|
||||
operator = colors[settings.get("edit.color_operator") or "lightGray"],
|
||||
keyword = colors[settings.get("edit.color_keyword") or "orange"],
|
||||
boolean = colors[settings.get("edit.color_boolean") or "purple"],
|
||||
comment = colors[settings.get("edit.color_comment") or "gray"],
|
||||
builtin = colors[settings.get("edit.color_global") or "lime"],
|
||||
string = colors[settings.get("edit.color_string") or "red"],
|
||||
number = colors[settings.get("edit.color_number") or "magenta"]
|
||||
}
|
||||
|
||||
local lines = {}
|
||||
local linesDraw = {}
|
||||
local run, menu = true, false
|
||||
local cx, cy = 1, 1
|
||||
local scroll = 0
|
||||
local hscroll = 0
|
||||
local scroll_offset = settings.get("edit.scroll_offset") or 3
|
||||
local scroll_increment = 0
|
||||
local scroll_factor = settings.get("edit.scroll_factor") or 8
|
||||
local unsaved, changed = false, true
|
||||
local file = args[1] or ".new"
|
||||
local status = "Press Ctrl for menu"
|
||||
|
||||
if args[1] then
|
||||
local handle = io.open(args[1], "r")
|
||||
|
||||
if handle then
|
||||
for line in handle:lines() do
|
||||
lines[#lines+1] = line
|
||||
end
|
||||
handle:close()
|
||||
end
|
||||
end
|
||||
|
||||
if not lines[1] then lines[1] = "" end
|
||||
|
||||
local win = require("window").create(term.current(), 1, 1, term.getSize())
|
||||
|
||||
local function redraw()
|
||||
local w, h = term.getSize()
|
||||
|
||||
-- this seems to provide a good responsiveness curve on my machine
|
||||
scroll_increment = math.floor(h/scroll_factor)
|
||||
|
||||
win.reposition(1, 1, w, h)
|
||||
|
||||
win.setVisible(false)
|
||||
|
||||
for i=1, h - 1, 1 do
|
||||
local line = linesDraw[i]
|
||||
win.setCursorPos(1 - hscroll, i)
|
||||
win.clearLine()
|
||||
if line then
|
||||
for t=1, #line, 1 do
|
||||
local item = line[t]
|
||||
if type(item) == "number" then
|
||||
win.setTextColor(item)
|
||||
else
|
||||
win.write(item)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
win.setCursorPos(1, h)
|
||||
win.clearLine()
|
||||
win.setTextColor(type_colors.accent or colors.yellow)
|
||||
win.write(status)
|
||||
win.setTextColor(colors.white)
|
||||
|
||||
win.setCursorPos(math.min(w, cx), cy - scroll)
|
||||
win.setCursorBlink(true)
|
||||
|
||||
win.setVisible(true)
|
||||
end
|
||||
|
||||
local syntax = require("edit.syntax")
|
||||
.new("/rc/modules/main/edit/syntax/lua.lua")
|
||||
|
||||
local function rehighlight()
|
||||
local line = {}
|
||||
linesDraw = {}
|
||||
local _, h = term.getSize()
|
||||
local text = table.concat(lines, "\n", scroll+1,
|
||||
math.min(#lines, scroll+h+1)) or ""
|
||||
for token, ttype in syntax(text) do
|
||||
if token == "\n" then
|
||||
linesDraw[#linesDraw+1] = line
|
||||
line = {}
|
||||
|
||||
else
|
||||
repeat
|
||||
local bit = token:find("\n")
|
||||
local nl = not not bit
|
||||
local chunk = token:sub(1, bit or #token)
|
||||
token = token:sub(#chunk+1)
|
||||
line[#line+1] = type_colors[ttype] or colors.white
|
||||
line[#line+1] = chunk
|
||||
if nl then
|
||||
linesDraw[#linesDraw+1] = line
|
||||
line = {}
|
||||
end
|
||||
until #token == 0
|
||||
end
|
||||
end
|
||||
|
||||
if #line > 0 then
|
||||
linesDraw[#linesDraw+1] = line
|
||||
end
|
||||
end
|
||||
|
||||
local function save()
|
||||
if file == ".new" then
|
||||
local _, h = term.getSize()
|
||||
term.setCursorPos(1, h)
|
||||
textutils.coloredWrite(colors.yellow, "filename: ")
|
||||
file = term.read()
|
||||
end
|
||||
|
||||
local handle, err = io.open(file, "w")
|
||||
if not handle then
|
||||
status = err
|
||||
|
||||
else
|
||||
for i=1, #lines, 1 do
|
||||
handle:write(lines[i] .. "\n")
|
||||
end
|
||||
handle:close()
|
||||
|
||||
status = "Saved to " .. file
|
||||
unsaved = false
|
||||
end
|
||||
end
|
||||
|
||||
local function processInput()
|
||||
local event, id = rc.pullEvent()
|
||||
|
||||
local w, h = term.getSize()
|
||||
|
||||
if event == "char" then
|
||||
local line = lines[cy]
|
||||
unsaved = true
|
||||
|
||||
if cx > #line then
|
||||
line = line .. id
|
||||
|
||||
elseif cx == 1 then
|
||||
line = id .. line
|
||||
|
||||
else
|
||||
line = line:sub(0, cx-1) .. id .. line:sub(cx)
|
||||
end
|
||||
|
||||
cx = cx + 1
|
||||
lines[cy] = line
|
||||
changed = true
|
||||
|
||||
elseif event == "key" then
|
||||
id = keys.getName(id)
|
||||
|
||||
if id == "backspace" then
|
||||
local line = lines[cy]
|
||||
unsaved = true
|
||||
|
||||
if cx == 1 and cy > 1 then
|
||||
local previous = table.remove(lines, cy - 1)
|
||||
cy = cy - 1
|
||||
cx = #previous + 1
|
||||
line = previous .. line
|
||||
|
||||
else
|
||||
if #line > 0 then
|
||||
if cx > #line then
|
||||
cx = cx - 1
|
||||
line = line:sub(1, -2)
|
||||
|
||||
elseif cx > 1 then
|
||||
line = line:sub(0, cx - 2) .. line:sub(cx)
|
||||
cx = cx - 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
lines[cy] = line
|
||||
changed = true
|
||||
|
||||
elseif id == "enter" then
|
||||
if cx == 1 then
|
||||
table.insert(lines, cy, "")
|
||||
|
||||
elseif cx > #lines[cy] then
|
||||
table.insert(lines, cy+1, "")
|
||||
|
||||
else
|
||||
local line = lines[cy]
|
||||
local before, after = line:sub(0, cx - 1), line:sub(cx)
|
||||
lines[cy] = before
|
||||
table.insert(lines, cy + 1, after)
|
||||
end
|
||||
|
||||
cy = cy + 1
|
||||
cx = 1
|
||||
|
||||
changed = true
|
||||
|
||||
elseif id == "up" then
|
||||
if cy > 1 then
|
||||
cy = cy - 1
|
||||
if cy - scroll < scroll_offset then
|
||||
local old_scroll = scroll
|
||||
scroll = math.max(0, scroll - scroll_increment)
|
||||
if scroll < old_scroll then
|
||||
rehighlight()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
cx = math.min(cx, #lines[cy] + 1)
|
||||
|
||||
elseif id == "down" then
|
||||
if cy < #lines then
|
||||
cy = math.min(#lines, cy + 1)
|
||||
|
||||
if cy - scroll > h - scroll_offset then
|
||||
local old_scroll = scroll
|
||||
scroll = math.max(0, math.min(#lines - h + 1,
|
||||
scroll + scroll_increment))
|
||||
if scroll > old_scroll then
|
||||
rehighlight()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
cx = math.min(cx, #lines[cy] + 1)
|
||||
|
||||
elseif id == "left" then
|
||||
if cx > 1 then
|
||||
cx = cx - 1
|
||||
end
|
||||
|
||||
hscroll = math.max(0, cx - w)
|
||||
|
||||
elseif id == "right" then
|
||||
if cx < #lines[cy] + 1 then
|
||||
cx = cx + 1
|
||||
end
|
||||
|
||||
hscroll = math.max(0, cx - w)
|
||||
|
||||
elseif id == "leftCtrl" or id == "rightCtrl" then
|
||||
status = "S:save E:exit"
|
||||
menu = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function processMenuInput()
|
||||
local event, id = rc.pullEvent()
|
||||
|
||||
if event == "char" then
|
||||
if id:lower() == "e" then
|
||||
if unsaved and menu ~= 2 then
|
||||
status = "Lose unsaved work? E:yes C:no"
|
||||
menu = 2
|
||||
else
|
||||
term.at(1, 1).clear()
|
||||
run = false
|
||||
end
|
||||
|
||||
elseif id:lower() == "c" and menu == 2 then
|
||||
menu = false
|
||||
|
||||
elseif id:lower() == "s" then
|
||||
save()
|
||||
menu = false
|
||||
end
|
||||
|
||||
elseif event == "key" then
|
||||
id = keys.getName(id)
|
||||
|
||||
if id == "leftCtrl" or id == "rightCtrl" then
|
||||
status = "Press Ctrl for menu"
|
||||
menu = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
while run do
|
||||
if changed then rehighlight() changed = false end
|
||||
redraw()
|
||||
if menu then
|
||||
processMenuInput()
|
||||
else
|
||||
processInput()
|
||||
end
|
||||
end
|
||||
229
data/computercraft/lua/rom/editors/basic.lua
Normal file
229
data/computercraft/lua/rom/editors/basic.lua
Normal file
@@ -0,0 +1,229 @@
|
||||
-- editor
|
||||
|
||||
local args = {...}
|
||||
|
||||
local rc = require("rc")
|
||||
local keys = require("keys")
|
||||
local term = require("term")
|
||||
local shell = require("shell")
|
||||
local colors = require("colors")
|
||||
local settings = require("settings")
|
||||
local textutils = require("textutils")
|
||||
|
||||
local scroll_offset = settings.get("edit.scroll_offset")
|
||||
|
||||
local state = {
|
||||
file = args[1] or ".new",
|
||||
unsaved = false,
|
||||
scroll = 0,
|
||||
hscroll = 0,
|
||||
lines = {""},
|
||||
status = "Press Control for menu",
|
||||
cx = 1,
|
||||
cy = 1,
|
||||
}
|
||||
|
||||
if args[1] then
|
||||
local path = shell.resolve(args[1])
|
||||
local handle = io.open(path)
|
||||
state.file = path
|
||||
|
||||
if handle then
|
||||
state.lines = {}
|
||||
|
||||
for line in handle:lines() do
|
||||
state.lines[#state.lines+1] = line
|
||||
end
|
||||
|
||||
handle:close()
|
||||
|
||||
if not state.lines[1] then state.lines[1] = "" end
|
||||
end
|
||||
end
|
||||
|
||||
local function redraw()
|
||||
local w, h = term.getSize()
|
||||
|
||||
for i=1, h - 1, 1 do
|
||||
local to_write = state.lines[state.scroll + i] or ""
|
||||
if state.cx > w then
|
||||
to_write = to_write:sub(state.cx - (w-1))
|
||||
end
|
||||
term.at(1, i).clearLine()
|
||||
term.write(to_write)
|
||||
end
|
||||
|
||||
term.at(1, h).clearLine()
|
||||
textutils.coloredWrite(colors.yellow, state.status, colors.white)
|
||||
|
||||
term.setCursorPos(math.min(w, state.cx), state.cy - state.scroll)
|
||||
end
|
||||
|
||||
local run, menu = true, false
|
||||
|
||||
local function save()
|
||||
if state.file == ".new" then
|
||||
local _, h = term.getSize()
|
||||
term.setCursorPos(1, h)
|
||||
textutils.coloredWrite(colors.yellow, "filename: ", colors.white)
|
||||
state.file = term.read()
|
||||
end
|
||||
|
||||
local handle, err = io.open(state.file, "w")
|
||||
if not handle then
|
||||
state.status = err
|
||||
else
|
||||
for i=1, #state.lines, 1 do
|
||||
handle:write(state.lines[i] .. "\n")
|
||||
end
|
||||
handle:close()
|
||||
state.status = "Saved to " .. state.file
|
||||
state.unsaved = false
|
||||
end
|
||||
end
|
||||
|
||||
local function processMenuInput()
|
||||
local event, id = rc.pullEvent()
|
||||
|
||||
if event == "char" then
|
||||
if id:lower() == "e" then
|
||||
if state.unsaved and menu ~= 2 then
|
||||
state.status = "Lose unsaved work? E:yes C:no"
|
||||
menu = 2
|
||||
else
|
||||
term.at(1, 1).clear()
|
||||
run = false
|
||||
end
|
||||
|
||||
elseif id:lower() == "c" and menu == 2 then
|
||||
menu = false
|
||||
|
||||
elseif id:lower() == "s" then
|
||||
save()
|
||||
menu = false
|
||||
end
|
||||
|
||||
elseif event == "key" then
|
||||
id = keys.getName(id)
|
||||
|
||||
if id == "leftCtrl" or id == "rightCtrl" then
|
||||
state.status = "Press Control for menu"
|
||||
menu = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function processInput()
|
||||
local event, id = rc.pullEvent()
|
||||
|
||||
local _, h = term.getSize()
|
||||
|
||||
if event == "char" then
|
||||
local line = state.lines[state.cy]
|
||||
state.unsaved = true
|
||||
if state.cx > #line then
|
||||
line = line .. id
|
||||
|
||||
elseif state.cx == 1 then
|
||||
line = id .. line
|
||||
|
||||
else
|
||||
line = line:sub(0, state.cx-1)..id..line:sub(state.cx)
|
||||
end
|
||||
state.cx = state.cx + 1
|
||||
|
||||
state.lines[state.cy] = line
|
||||
|
||||
elseif event == "key" then
|
||||
id = keys.getName(id)
|
||||
|
||||
if id == "backspace" then
|
||||
local line = state.lines[state.cy]
|
||||
state.unsaved = true
|
||||
if state.cx == 1 and state.cy > 1 then
|
||||
local previous = table.remove(state.lines, state.cy - 1)
|
||||
state.cy = state.cy - 1
|
||||
state.cx = #previous + 1
|
||||
line = previous .. line
|
||||
else
|
||||
if #line > 0 then
|
||||
if state.cx > #line then
|
||||
state.cx = state.cx - 1
|
||||
line = line:sub(1, -2)
|
||||
|
||||
elseif state.cx > 1 then
|
||||
line = line:sub(0, state.cx - 2) .. line:sub(state.cx)
|
||||
state.cx = state.cx - 1
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
state.lines[state.cy] = line
|
||||
|
||||
elseif id == "enter" then
|
||||
if state.cx == 1 then
|
||||
table.insert(state.lines, state.cy, "")
|
||||
elseif state.cx > #state.lines[state.cy] then
|
||||
table.insert(state.lines, state.cy + 1, "")
|
||||
else
|
||||
local line = state.lines[state.cy]
|
||||
local before, after = line:sub(0, state.cx - 1), line:sub(state.cx)
|
||||
state.lines[state.cy] = before
|
||||
table.insert(state.lines, state.cy + 1, after)
|
||||
end
|
||||
|
||||
state.cy = state.cy + 1
|
||||
state.cx = 1
|
||||
|
||||
elseif id == "up" then
|
||||
if state.cy > 1 then
|
||||
state.cy = state.cy - 1
|
||||
if state.cy - state.scroll < scroll_offset then
|
||||
state.scroll = math.max(0, state.cy - scroll_offset)
|
||||
end
|
||||
end
|
||||
|
||||
state.cx = math.min(state.cx, #state.lines[state.cy] + 1)
|
||||
|
||||
elseif id == "down" then
|
||||
if state.cy < #state.lines then
|
||||
state.cy = state.cy + 1
|
||||
|
||||
if state.cy - state.scroll > h - scroll_offset then
|
||||
state.scroll = math.max(0, math.min(#state.lines - h + 1,
|
||||
state.cy - h + scroll_offset))
|
||||
end
|
||||
end
|
||||
|
||||
state.cx = math.min(state.cx, #state.lines[state.cy] + 1)
|
||||
|
||||
elseif id == "left" then
|
||||
if state.cx > 1 then
|
||||
state.cx = state.cx - 1
|
||||
end
|
||||
|
||||
elseif id == "right" then
|
||||
if state.cx < #state.lines[state.cy] + 1 then
|
||||
state.cx = state.cx + 1
|
||||
end
|
||||
|
||||
elseif id == "leftCtrl" or id == "rightCtrl" then
|
||||
state.status = "S:save E:exit"
|
||||
menu = true
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
term.clear()
|
||||
while run do
|
||||
term.setCursorBlink(false)
|
||||
redraw()
|
||||
term.setCursorBlink(true)
|
||||
if menu then
|
||||
processMenuInput()
|
||||
else
|
||||
processInput()
|
||||
end
|
||||
end
|
||||
14
data/computercraft/lua/rom/help/about.hlp
Normal file
14
data/computercraft/lua/rom/help/about.hlp
Normal file
@@ -0,0 +1,14 @@
|
||||
LeonOS is ComputerCraft's LeonOS but with saner API design. It's also not licensed under the CCPL, but rather the MIT license -- so you can freely use LeonOS's code in other projects without being legally bound to license them under the CCPL.
|
||||
|
||||
|
||||
All APIs are implemented as described on the CC: Tweaked wiki at
|
||||
>>color blue
|
||||
https://tweaked.cc
|
||||
>>color white
|
||||
, with slight modifications to fit LeonOS's API design. Certain modules not written by Dan200 have been adapted from LeonOS, relicensed under the MIT license with permission from their authors.
|
||||
|
||||
See
|
||||
>>color blue
|
||||
https://ocaweso.me/LeonOS
|
||||
>>color white
|
||||
for more information on LeonOS.
|
||||
25
data/computercraft/lua/rom/keymaps/lwjgl2.lua
Normal file
25
data/computercraft/lua/rom/keymaps/lwjgl2.lua
Normal file
@@ -0,0 +1,25 @@
|
||||
-- keymap for minecraft 1.12.2 and older
|
||||
|
||||
return {
|
||||
nil,
|
||||
"one", "two", "three", "four", "five", "six", "seven", "eight", "nine",
|
||||
"zero", "minus", "equals", "backspace", "tab", "q", "w", "e", "r", "t", "y",
|
||||
"u", "i", "o", "p", "leftBracket", "rightBracket", "enter", "leftCtrl",
|
||||
"a", "s", "d", "f", "g", "h", "j", "k", "l", "semicolon", "apostrophe",
|
||||
"grave", "leftShift", "backslash", "z", "x", "c", "v", "b", "n", "m",
|
||||
"comma", "period", "slash", "rightShift", "multiply", "leftAlt", "space",
|
||||
"capsLock", "f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "f10",
|
||||
"numLock", "scrollLock", "numpad7", "numpad8", "numpad9", "numpadSubtract",
|
||||
"numpad4", "numpad5", "numpad6", "numpadAdd", "numpad1", "numpad2", "numpad3",
|
||||
"numpad0", "numpadDot", nil, nil, nil, "f11", "f12", nil, nil, nil, nil, nil,
|
||||
nil, nil, nil, nil, nil, nil, "f13", "f14", "f15", nil, nil, nil, nil, nil,
|
||||
nil, nil, nil, nil, "kana", nil, nil, nil, nil, nil, nil, nil, nil, "convert",
|
||||
nil, "noconvert", nil, "yen", nil, nil, nil, nil, nil, nil, nil, nil, nil,
|
||||
nil, nil, nil, nil, nil, nil, "numpadEquals", nil, nil, "circumflex", "at",
|
||||
"colon", "underscore", "kanji", "stop", "ax", nil, nil, nil, nil, nil,
|
||||
"numpadEnter", "rightCtrl", nil, nil, nil, nil, nil, nil, nil, nil, nil,
|
||||
nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, "numpadComma",
|
||||
nil, "numpadDivide", nil, nil, "rightAlt", nil, nil, nil, nil, nil, nil,
|
||||
nil, nil, nil, nil, nil, nil, "pause", nil, "home", "up", "pageUp", nil,
|
||||
"left", nil, "right", nil, "end", "down", "pageDown", "insert", "delete"
|
||||
}
|
||||
120
data/computercraft/lua/rom/keymaps/lwjgl3.lua
Normal file
120
data/computercraft/lua/rom/keymaps/lwjgl3.lua
Normal file
@@ -0,0 +1,120 @@
|
||||
-- keymap for 1.16.5+
|
||||
|
||||
return {
|
||||
[32] = "space",
|
||||
[39] = "apostrophe",
|
||||
[44] = "comma",
|
||||
[45] = "minus",
|
||||
[46] = "period",
|
||||
[47] = "slash",
|
||||
[48] = "zero",
|
||||
[49] = "one",
|
||||
[50] = "two",
|
||||
[51] = "three",
|
||||
[52] = "four",
|
||||
[53] = "five",
|
||||
[54] = "six",
|
||||
[55] = "seven",
|
||||
[56] = "eight",
|
||||
[57] = "nine",
|
||||
[59] = "semicolon",
|
||||
[61] = "equals",
|
||||
[65] = "a",
|
||||
[66] = "b",
|
||||
[67] = "c",
|
||||
[68] = "d",
|
||||
[69] = "e",
|
||||
[70] = "f",
|
||||
[71] = "g",
|
||||
[72] = "h",
|
||||
[73] = "i",
|
||||
[74] = "j",
|
||||
[75] = "k",
|
||||
[76] = "l",
|
||||
[77] = "m",
|
||||
[78] = "n",
|
||||
[79] = "o",
|
||||
[80] = "p",
|
||||
[81] = "q",
|
||||
[82] = "r",
|
||||
[83] = "s",
|
||||
[84] = "t",
|
||||
[85] = "u",
|
||||
[86] = "v",
|
||||
[87] = "w",
|
||||
[88] = "x",
|
||||
[89] = "y",
|
||||
[90] = "z",
|
||||
[91] = "leftBracket",
|
||||
[92] = "backslash",
|
||||
[93] = "rightBracket",
|
||||
[96] = "grave",
|
||||
[257] = "enter",
|
||||
[258] = "tab",
|
||||
[259] = "backspace",
|
||||
[260] = "insert",
|
||||
[261] = "delete",
|
||||
[262] = "right",
|
||||
[263] = "left",
|
||||
[264] = "down",
|
||||
[265] = "up",
|
||||
[266] = "pageUp",
|
||||
[267] = "pageDown",
|
||||
[268] = "home",
|
||||
[269] = "end",
|
||||
[280] = "capsLock",
|
||||
[281] = "scrollLock",
|
||||
[282] = "numLock",
|
||||
[283] = "printScreen",
|
||||
[284] = "pause",
|
||||
[290] = "f1",
|
||||
[291] = "f2",
|
||||
[292] = "f3",
|
||||
[293] = "f4",
|
||||
[294] = "f5",
|
||||
[295] = "f6",
|
||||
[296] = "f7",
|
||||
[297] = "f8",
|
||||
[298] = "f9",
|
||||
[299] = "f10",
|
||||
[300] = "f11",
|
||||
[301] = "f12",
|
||||
[302] = "f13",
|
||||
[303] = "f14",
|
||||
[304] = "f15",
|
||||
[305] = "f16",
|
||||
[306] = "f17",
|
||||
[307] = "f18",
|
||||
[308] = "f19",
|
||||
[309] = "f20",
|
||||
[310] = "f21",
|
||||
[311] = "f22",
|
||||
[312] = "f23",
|
||||
[313] = "f24",
|
||||
[314] = "f25",
|
||||
[320] = "numpad0",
|
||||
[321] = "numpad1",
|
||||
[322] = "numpad2",
|
||||
[323] = "numpad3",
|
||||
[324] = "numpad4",
|
||||
[325] = "numpad5",
|
||||
[326] = "numpad6",
|
||||
[327] = "numpad7",
|
||||
[328] = "numpad8",
|
||||
[329] = "numpad9",
|
||||
[330] = "numpadDot",
|
||||
[331] = "numpadDivide",
|
||||
[332] = "numpadMultiply",
|
||||
[333] = "numpadSubtract",
|
||||
[334] = "numpadAdd",
|
||||
[335] = "numpadEnter",
|
||||
[336] = "numpadEqual",
|
||||
[340] = "leftShift",
|
||||
[341] = "leftCtrl",
|
||||
[342] = "leftAlt",
|
||||
[343] = "leftSuper",
|
||||
[344] = "rightShift",
|
||||
[345] = "rightCtrl",
|
||||
[346] = "rightAlt",
|
||||
[348] = "menu",
|
||||
}
|
||||
228
data/computercraft/lua/rom/modules/main/cc/audio/dfpwm.lua
Normal file
228
data/computercraft/lua/rom/modules/main/cc/audio/dfpwm.lua
Normal file
@@ -0,0 +1,228 @@
|
||||
--[[-
|
||||
Provides utilities for converting between streams of DFPWM audio data and a list of amplitudes.
|
||||
|
||||
DFPWM (Dynamic Filter Pulse Width Modulation) is an audio codec designed by GreaseMonkey. It's a relatively compact
|
||||
format compared to raw PCM data, only using 1 bit per sample, but is simple enough to simple enough to encode and decode
|
||||
in real time.
|
||||
|
||||
Typically DFPWM audio is read from @{fs.BinaryReadHandle|the filesystem} or a @{http.Response|a web request} as a
|
||||
string, and converted a format suitable for @{speaker.playAudio}.
|
||||
|
||||
## Encoding and decoding files
|
||||
This modules exposes two key functions, @{make_decoder} and @{make_encoder}, which construct a new decoder or encoder.
|
||||
The returned encoder/decoder is itself a function, which converts between the two kinds of data.
|
||||
|
||||
These encoders and decoders have lots of hidden state, so you should be careful to use the same encoder or decoder for
|
||||
a specific audio stream. Typically you will want to create a decoder for each stream of audio you read, and an encoder
|
||||
for each one you write.
|
||||
|
||||
## Converting audio to DFPWM
|
||||
DFPWM is not a popular file format and so standard audio processing tools will not have an option to export to it.
|
||||
Instead, you can convert audio files online using [music.madefor.cc] or with the [LionRay Wav Converter][LionRay] Java
|
||||
application.
|
||||
|
||||
[music.madefor.cc]: https://music.madefor.cc/ "DFPWM audio converter for Computronics and CC: Tweaked"
|
||||
[LionRay]: https://github.com/gamax92/LionRay/ "LionRay Wav Converter "
|
||||
|
||||
@see guide!speaker_audio Gives a more general introduction to audio processing and the speaker.
|
||||
@see speaker.playAudio To play the decoded audio data.
|
||||
@usage Reads "data/example.dfpwm" in chunks, decodes them and then doubles the speed of the audio. The resulting audio
|
||||
is then re-encoded and saved to "speedy.dfpwm". This processed audio can then be played with the `speaker` program.
|
||||
|
||||
```lua
|
||||
local dfpwm = require("cc.audio.dfpwm")
|
||||
|
||||
local encoder = dfpwm.make_encoder()
|
||||
local decoder = dfpwm.make_decoder()
|
||||
|
||||
local out = fs.open("speedy.dfpwm", "wb")
|
||||
for input in io.lines("data/example.dfpwm", 16 * 1024 * 2) do
|
||||
local decoded = decoder(input)
|
||||
local output = {}
|
||||
|
||||
-- Read two samples at once and take the average.
|
||||
for i = 1, #decoded, 2 do
|
||||
local value_1, value_2 = decoded[i], decoded[i + 1]
|
||||
output[(i + 1) / 2] = (value_1 + value_2) / 2
|
||||
end
|
||||
|
||||
out.write(encoder(output))
|
||||
|
||||
sleep(0) -- This program takes a while to run, so we need to make sure we yield.
|
||||
end
|
||||
out.close()
|
||||
```
|
||||
]]
|
||||
|
||||
local expect = require "cc.expect".expect
|
||||
|
||||
local char, byte, floor, band, rshift = string.char, string.byte, math.floor, bit32.band, bit32.arshift
|
||||
|
||||
local PREC = 10
|
||||
local PREC_POW = 2 ^ PREC
|
||||
local PREC_POW_HALF = 2 ^ (PREC - 1)
|
||||
local STRENGTH_MIN = 2 ^ (PREC - 8 + 1)
|
||||
|
||||
local function make_predictor()
|
||||
local charge, strength, previous_bit = 0, 0, false
|
||||
|
||||
return function(current_bit)
|
||||
local target = current_bit and 127 or -128
|
||||
|
||||
local next_charge = charge + floor((strength * (target - charge) + PREC_POW_HALF) / PREC_POW)
|
||||
if next_charge == charge and next_charge ~= target then
|
||||
next_charge = next_charge + (current_bit and 1 or -1)
|
||||
end
|
||||
|
||||
local z = current_bit == previous_bit and PREC_POW - 1 or 0
|
||||
local next_strength = strength
|
||||
if next_strength ~= z then next_strength = next_strength + (current_bit == previous_bit and 1 or -1) end
|
||||
if next_strength < STRENGTH_MIN then next_strength = STRENGTH_MIN end
|
||||
|
||||
charge, strength, previous_bit = next_charge, next_strength, current_bit
|
||||
return charge
|
||||
end
|
||||
end
|
||||
|
||||
--[[- Create a new encoder for converting PCM audio data into DFPWM.
|
||||
|
||||
The returned encoder is itself a function. This function accepts a table of amplitude data between -128 and 127 and
|
||||
returns the encoded DFPWM data.
|
||||
|
||||
:::caution Reusing encoders
|
||||
Encoders have lots of internal state which tracks the state of the current stream. If you reuse an encoder for multiple
|
||||
streams, or use different encoders for the same stream, the resulting audio may not sound correct.
|
||||
:::
|
||||
|
||||
@treturn function(pcm: { number... }):string The encoder function
|
||||
@see encode A helper function for encoding an entire file of audio at once.
|
||||
]]
|
||||
local function make_encoder()
|
||||
local predictor = make_predictor()
|
||||
local previous_charge = 0
|
||||
|
||||
return function(input)
|
||||
expect(1, input, "table")
|
||||
|
||||
local output, output_n = {}, 0
|
||||
for i = 1, #input, 8 do
|
||||
local this_byte = 0
|
||||
for j = 0, 7 do
|
||||
local inp_charge = floor(input[i + j] or 0)
|
||||
if inp_charge > 127 or inp_charge < -128 then
|
||||
error(("Amplitude at position %d was %d, but should be between -128 and 127"):format(i + j, inp_charge), 2)
|
||||
end
|
||||
|
||||
local current_bit = inp_charge > previous_charge or (inp_charge == previous_charge and inp_charge == 127)
|
||||
this_byte = floor(this_byte / 2) + (current_bit and 128 or 0)
|
||||
|
||||
previous_charge = predictor(current_bit)
|
||||
end
|
||||
|
||||
output_n = output_n + 1
|
||||
output[output_n] = char(this_byte)
|
||||
end
|
||||
|
||||
return table.concat(output, "", 1, output_n)
|
||||
end
|
||||
end
|
||||
|
||||
--[[- Create a new decoder for converting DFPWM into PCM audio data.
|
||||
|
||||
The returned decoder is itself a function. This function accepts a string and returns a table of amplitudes, each value
|
||||
between -128 and 127.
|
||||
|
||||
:::caution Reusing decoders
|
||||
Decoders have lots of internal state which tracks the state of the current stream. If you reuse an decoder for multiple
|
||||
streams, or use different decoders for the same stream, the resulting audio may not sound correct.
|
||||
:::
|
||||
|
||||
@treturn function(dfpwm: string):{ number... } The encoder function
|
||||
@see decode A helper function for decoding an entire file of audio at once.
|
||||
|
||||
@usage Reads "data/example.dfpwm" in blocks of 16KiB (the speaker can accept a maximum of 128×1024 samples), decodes
|
||||
them and then plays them through the speaker.
|
||||
|
||||
```lua {data-peripheral=speaker}
|
||||
local dfpwm = require "cc.audio.dfpwm"
|
||||
local speaker = peripheral.find("speaker")
|
||||
|
||||
local decoder = dfpwm.make_decoder()
|
||||
for input in io.lines("data/example.dfpwm", 16 * 1024) do
|
||||
local decoded = decoder(input)
|
||||
while not speaker.playAudio(decoded) do
|
||||
os.pullEvent("speaker_audio_empty")
|
||||
end
|
||||
end
|
||||
```
|
||||
]]
|
||||
local function make_decoder()
|
||||
local predictor = make_predictor()
|
||||
local low_pass_charge = 0
|
||||
local previous_charge, previous_bit = 0, false
|
||||
|
||||
return function (input, output)
|
||||
expect(1, input, "string")
|
||||
|
||||
local output, output_n = {}, 0
|
||||
for i = 1, #input do
|
||||
local input_byte = byte(input, i)
|
||||
for _ = 1, 8 do
|
||||
local current_bit = band(input_byte, 1) ~= 0
|
||||
local charge = predictor(current_bit)
|
||||
|
||||
local antijerk = charge
|
||||
if current_bit ~= previous_bit then
|
||||
antijerk = floor((charge + previous_charge + 1) / 2)
|
||||
end
|
||||
|
||||
previous_charge, previous_bit = charge, current_bit
|
||||
|
||||
low_pass_charge = low_pass_charge + floor(((antijerk - low_pass_charge) * 140 + 0x80) / 256)
|
||||
|
||||
output_n = output_n + 1
|
||||
output[output_n] = low_pass_charge
|
||||
|
||||
input_byte = rshift(input_byte, 1)
|
||||
end
|
||||
end
|
||||
|
||||
return output
|
||||
end
|
||||
end
|
||||
|
||||
--[[- A convenience function for decoding a complete file of audio at once.
|
||||
|
||||
This should only be used for short files. For larger files, one should read the file in chunks and process it using
|
||||
@{make_decoder}.
|
||||
|
||||
@tparam string input The DFPWM data to convert.
|
||||
@treturn { number... } The produced amplitude data.
|
||||
@see make_decoder
|
||||
]]
|
||||
local function decode(input)
|
||||
expect(1, input, "string")
|
||||
return make_decoder()(input)
|
||||
end
|
||||
|
||||
--[[- A convenience function for encoding a complete file of audio at once.
|
||||
|
||||
This should only be used for complete pieces of audio. If you are writing writing multiple chunks to the same place,
|
||||
you should use an encoder returned by @{make_encoder} instead.
|
||||
|
||||
@tparam { number... } input The table of amplitude data.
|
||||
@treturn string The encoded DFPWM data.
|
||||
@see make_encoder
|
||||
]]
|
||||
local function encode(input)
|
||||
expect(1, input, "table")
|
||||
return make_encoder()(input)
|
||||
end
|
||||
|
||||
return {
|
||||
make_encoder = make_encoder,
|
||||
encode = encode,
|
||||
|
||||
make_decoder = make_decoder,
|
||||
decode = decode,
|
||||
}
|
||||
45
data/computercraft/lua/rom/modules/main/cc/completion.lua
Normal file
45
data/computercraft/lua/rom/modules/main/cc/completion.lua
Normal file
@@ -0,0 +1,45 @@
|
||||
-- cc.completion
|
||||
|
||||
local expect = require("cc.expect").expect
|
||||
local settings = require("settings")
|
||||
local peripheral = require("peripheral")
|
||||
|
||||
local c = {}
|
||||
|
||||
-- choices and options!
|
||||
-- extension: add_space can be a table of booleans
|
||||
function c.choice(text, choices, add_space)
|
||||
expect(1, text, "string")
|
||||
expect(2, choices, "table")
|
||||
expect(3, add_space, "boolean", "table", "nil")
|
||||
|
||||
local options = {}
|
||||
|
||||
for i=1, #choices, 1 do
|
||||
local add = add_space
|
||||
if type(add_space) == "table" then
|
||||
add = add_space[i] or add_space[1]
|
||||
end
|
||||
|
||||
if choices[i]:sub(0, #text) == text then
|
||||
options[#options+1] = choices[i]:sub(#text+1) .. (add and " " or "")
|
||||
end
|
||||
end
|
||||
|
||||
return options
|
||||
end
|
||||
|
||||
function c.peripheral(text, add_space)
|
||||
return c.choice(text, peripheral.getNames(), add_space)
|
||||
end
|
||||
|
||||
local sides = {"front", "back", "top", "bottom", "left", "right"}
|
||||
function c.side(text, add_space)
|
||||
return c.choice(text, sides, add_space)
|
||||
end
|
||||
|
||||
function c.setting(text, add_space)
|
||||
return c.choice(text, settings.getNames(), add_space)
|
||||
end
|
||||
|
||||
return c
|
||||
50
data/computercraft/lua/rom/modules/main/cc/expect.lua
Normal file
50
data/computercraft/lua/rom/modules/main/cc/expect.lua
Normal file
@@ -0,0 +1,50 @@
|
||||
-- cc.expect
|
||||
|
||||
local _expect = {}
|
||||
|
||||
local function checkType(index, valueType, value, ...)
|
||||
local expected = table.pack(...)
|
||||
local isType = false
|
||||
|
||||
for i=1, expected.n, 1 do
|
||||
if type(value) == expected[i] then
|
||||
isType = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not isType then
|
||||
error(string.format("bad %s %s (%s expected, got %s)", valueType,
|
||||
index, table.concat(expected, " or "), type(value)), 3)
|
||||
end
|
||||
|
||||
return value
|
||||
end
|
||||
|
||||
function _expect.expect(index, value, ...)
|
||||
return checkType(("#%d"):format(index), "argument", value, ...)
|
||||
end
|
||||
|
||||
function _expect.field(tbl, index, ...)
|
||||
_expect.expect(1, tbl, "table")
|
||||
_expect.expect(2, index, "string")
|
||||
return checkType(("%q"):format(index), "field", tbl[index], ...)
|
||||
end
|
||||
|
||||
function _expect.range(num, min, max)
|
||||
_expect.expect(1, num, "number")
|
||||
_expect.expect(2, min, "number", "nil")
|
||||
_expect.expect(3, max, "number", "nil")
|
||||
min = min or -math.huge
|
||||
max = max or math.huge
|
||||
if num < min or num > max then
|
||||
error(("number outside of range (expected %d to be within %d and %d")
|
||||
:format(num, min, max), 2)
|
||||
end
|
||||
end
|
||||
|
||||
setmetatable(_expect, {__call = function(_, ...)
|
||||
return _expect.expect(...)
|
||||
end})
|
||||
|
||||
return _expect
|
||||
293
data/computercraft/lua/rom/modules/main/cc/http/gist.lua
Normal file
293
data/computercraft/lua/rom/modules/main/cc/http/gist.lua
Normal file
@@ -0,0 +1,293 @@
|
||||
--- gist.lua - Gist client for ComputerCraft
|
||||
-- Made by JackMacWindows for LeonOS-PC and CC: Tweaked
|
||||
--
|
||||
-- @module cc.http.gist
|
||||
|
||||
local textutils = require("textutils")
|
||||
local settings = require("settings")
|
||||
local expect = require("cc.expect").expect
|
||||
local colors = require("colors")
|
||||
local write = require("rc").write
|
||||
local term = require("term")
|
||||
local http = require("http")
|
||||
local read = term.read
|
||||
|
||||
local gist = {}
|
||||
|
||||
local function emptyfn() end -- to reduce memory/speed footprint when using empty functions
|
||||
|
||||
-- Internal functions
|
||||
|
||||
local function getGistFile(data)
|
||||
if not data.truncated then return data.content else
|
||||
local handle = http.get(data.raw_url)
|
||||
if not handle then error("Could not connect to api.github.com.") end
|
||||
if handle.getResponseCode() ~= 200 then
|
||||
handle.close()
|
||||
error("Failed to download file data.")
|
||||
end
|
||||
local d = handle.readAll()
|
||||
handle.close()
|
||||
return d
|
||||
end
|
||||
end
|
||||
|
||||
local function setTextColor(c) if term.isColor() then term.setTextColor(c) elseif c == colors.white or c == colors.yellow then term.setTextColor(colors.white) else term.setTextColor(colors.lightGray) end end
|
||||
|
||||
local function requestAuth(headers, interactive)
|
||||
if settings.get("gist.id") ~= nil then
|
||||
headers.Authorization = "token " .. settings.get("gist.id")
|
||||
return true
|
||||
elseif interactive then
|
||||
setTextColor(colors.yellow)
|
||||
write("You need to add a Personal Access Token (PAK) to upload Gists. Follow the instructions at ")
|
||||
setTextColor(colors.blue)
|
||||
write("https://tinyurl.com/GitHubPAK")
|
||||
setTextColor(colors.yellow)
|
||||
write(" to generate one. Make sure to check the '")
|
||||
setTextColor(colors.blue)
|
||||
write("gist")
|
||||
setTextColor(colors.yellow)
|
||||
print("' checkbox on step 7 (under 'Select scopes'). Once done, paste it here.")
|
||||
setTextColor(colors.lime)
|
||||
write("PAK: ")
|
||||
setTextColor(colors.white)
|
||||
local pak = read()
|
||||
if pak == nil or pak == "" then error("Invalid PAK, please try again.") end
|
||||
settings.set("gist.id", pak)
|
||||
settings.save(".settings")
|
||||
headers.Authorization = "token " .. pak
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- User API - this can be loaded with require "cc.http.gist"
|
||||
|
||||
-- ID can be either just the gist ID or a gist ID followed by a slash and a file name
|
||||
-- (This also includes Gist URLs)
|
||||
-- * If a file name is specified, retrieves that file
|
||||
-- * Otherwise, if there's only one file, retrieves that file
|
||||
-- * Otherwise, if there's a file named 'init.lua', retrieves 'init.lua'
|
||||
-- * Otherwise, if there's more than one file but only one *.lua file, retrieves the Lua file
|
||||
-- * Otherwise, retrieves the first Lua file alphabetically (with a warning)
|
||||
-- * Otherwise, fails
|
||||
|
||||
--- Retrieves one file from a Gist using the specified ID.
|
||||
-- @tparam string id The Gist ID to download from. See above comments for more details.
|
||||
-- @tparam[opt] function progress A function to use to report status messages.
|
||||
-- @treturn string|nil The contents of the specified Gist file, or nil on error.
|
||||
-- @treturn string|nil The name of the file that was chosen to be downloaded, or a message on error.
|
||||
function gist.get(id, progress)
|
||||
expect(1, id, "string")
|
||||
expect(2, progress, "function", "nil")
|
||||
progress = progress or emptyfn
|
||||
local file
|
||||
if id:find("https?://") then id = id:gsub("https?://[^/]+/", ""):gsub("^[^/]*[^/%x]+[^/]*/", "") end
|
||||
if id:find("/") ~= nil then id, file = id:match("^([0-9A-Fa-f:]+)/(.+)$") end
|
||||
if id == nil or not id:match("^[0-9A-Fa-f][0-9A-Fa-f:]+[0-9A-Fa-f]$") then error("bad argument #1 to 'get' (invalid ID)", 2) end
|
||||
if id:find(":") ~= nil then id = id:gsub(":", "/") end
|
||||
progress("Connecting to api.github.com... ")
|
||||
local handle = http.get("https://api.github.com/gists/" .. id)
|
||||
if handle == nil then
|
||||
progress("Failed.\n")
|
||||
return nil, "Failed to connect"
|
||||
end
|
||||
local meta = textutils.unserializeJSON(handle.readAll())
|
||||
local code = handle.getResponseCode()
|
||||
handle.close()
|
||||
if code ~= 200 then
|
||||
progress("Failed.\n")
|
||||
return nil, "Invalid response code (" .. code .. ")" .. (meta and ": " .. meta.message or "")
|
||||
end
|
||||
if meta == nil or meta.files == nil then
|
||||
progress("Failed.\n")
|
||||
return nil, meta and "GitHub API error: " .. meta.message or "Error parsing JSON"
|
||||
end
|
||||
progress("Success.\n")
|
||||
if file then return getGistFile(meta.files[file]), file
|
||||
elseif next(meta.files, next(meta.files)) == nil then return getGistFile(meta.files[next(meta.files)]), next(meta.files)
|
||||
elseif meta.files["init.lua"] ~= nil then return getGistFile(meta.files["init.lua"]), "init.lua"
|
||||
else
|
||||
local luaFiles = {}
|
||||
for k in pairs(meta.files) do if k:match("%.lua$") then table.insert(luaFiles, k) end end
|
||||
table.sort(luaFiles)
|
||||
if #luaFiles == 0 then
|
||||
progress("Error: Could not find any Lua files to download!\n")
|
||||
return nil, "Could not find any Lua files to download"
|
||||
end
|
||||
if #luaFiles > 1 then progress("Warning: More than one Lua file detected, downloading the first one alphabetically.\n") end
|
||||
return getGistFile(meta.files[luaFiles[1]]), luaFiles[1]
|
||||
end
|
||||
end
|
||||
|
||||
--- Runs a specified Gist. This is a wrapper for convenience.
|
||||
-- @tparam string id The Gist ID to download from. See above comments for more details.
|
||||
-- @tparam[opt] function progress A function to use to report status messages. If
|
||||
-- this is not a function, it will be used as an argument to the script.
|
||||
-- @tparam[opt] any ... Any arguments to pass to the script.
|
||||
-- @treturn any Any results returned from the script.
|
||||
function gist.run(id, progress, ...)
|
||||
expect(1, id, "string")
|
||||
local args = table.pack(...)
|
||||
if type(progress) ~= "function" and progress ~= nil then
|
||||
table.insert(args, 1, progress)
|
||||
progress = nil
|
||||
end
|
||||
local data, name = gist.get(id, progress)
|
||||
if data == nil then return end
|
||||
local fn, err = load(data, name, "t", _ENV)
|
||||
if fn == nil then error(err) end
|
||||
local retval = table.pack(pcall(fn, table.unpack(args)))
|
||||
if not retval[1] then error(retval[2]) end
|
||||
return table.unpack(retval, 2)
|
||||
end
|
||||
|
||||
--- Retrieves a table of all files from a Gist.
|
||||
-- @tparam string id The Gist ID to download.
|
||||
-- @tparam[opt] function progress A function to use to report status messages.
|
||||
-- @treturn table|nil A key-value list of all files in the Gist, or nil on error.
|
||||
-- @treturn string|nil If an error occurred, a string describing the error.
|
||||
function gist.getAll(id, progress)
|
||||
expect(1, id, "string")
|
||||
expect(2, progress, "function", "nil")
|
||||
progress = progress or emptyfn
|
||||
if id:find("https?://") then id = id:gsub("https?://[^/]+/", ""):gsub("^[^/]*[^/%x]+[^/]*/", "") end
|
||||
if id:find("/") ~= nil then id = id:match("^([0-9A-Fa-f:]+)/.*$") end
|
||||
if id == nil or not id:match("^[0-9A-Fa-f][0-9A-Fa-f:]+[0-9A-Fa-f]$") then error("bad argument #1 to 'getAll' (invalid ID)", 2) end
|
||||
if id:find(":") ~= nil then id = id:gsub(":", "/") end
|
||||
progress("Connecting to api.github.com... ")
|
||||
local handle = http.get("https://api.github.com/gists/" .. id)
|
||||
if handle == nil then progress("Failed.\n") return nil, "Failed to connect" end
|
||||
local meta = textutils.unserializeJSON(handle.readAll())
|
||||
local code = handle.getResponseCode()
|
||||
handle.close()
|
||||
if code ~= 200 then
|
||||
progress("Failed.\n")
|
||||
return nil, "Invalid response code (" .. code .. ")" .. (meta and ": " .. meta.message or "")
|
||||
end
|
||||
if meta == nil or meta.files == nil then
|
||||
progress("Failed.\n")
|
||||
return nil, meta and meta.message and "GitHub API error: " .. meta.message or "Error parsing JSON"
|
||||
end
|
||||
progress("Success.\n")
|
||||
local retval = {}
|
||||
for k, v in pairs(meta.files) do retval[k] = getGistFile(v) end
|
||||
return retval
|
||||
end
|
||||
|
||||
--- Returns some information about a Gist.
|
||||
-- @tparam string id The Gist ID to get info about.
|
||||
-- @tparam[opt] function progress A function to use to report status messages.
|
||||
-- @treturn table|nil A table of information about the Gist. The table may
|
||||
-- contain the following entries:
|
||||
-- - description: The description for the Gist.
|
||||
-- - author: The username of the author of the Gist.
|
||||
-- - revisionCount: The number of revisions that have been made to the Gist.
|
||||
-- - files: A list of all file names in the Gist, sorted alphabetically.
|
||||
-- @treturn string|nil If an error occurred, a string describing the error.
|
||||
function gist.info(id, progress)
|
||||
expect(1, id, "string")
|
||||
expect(2, progress, "function", "nil")
|
||||
progress = progress or emptyfn
|
||||
if id:find("https?://") then id = id:gsub("https?://[^/]+/", ""):gsub("^[^/]*[^/%x]+[^/]*/", "") end
|
||||
if id:find("/") ~= nil then id = id:match("^([0-9A-Fa-f:]+)/.*$") end
|
||||
if id == nil or not id:match("^[0-9A-Fa-f][0-9A-Fa-f:]+[0-9A-Fa-f]$") then error("bad argument #1 to 'info' (invalid ID)", 2) end
|
||||
if id:find(":") ~= nil then id = id:gsub(":", "/") end
|
||||
progress("Connecting to api.github.com... ")
|
||||
local handle = http.get("https://api.github.com/gists/" .. id)
|
||||
if handle == nil then progress("Failed.\n") return nil, "Failed to connect" end
|
||||
local meta = textutils.unserializeJSON(handle.readAll())
|
||||
local code = handle.getResponseCode()
|
||||
handle.close()
|
||||
if code ~= 200 then
|
||||
progress("Failed.\n")
|
||||
return nil, "Invalid response code (" .. code .. ")" .. (meta and ": " .. meta.message or "")
|
||||
end
|
||||
if meta == nil or meta.files == nil then
|
||||
progress("Failed.\n")
|
||||
return nil, meta and meta.message and "GitHub API error: " .. meta.message or "Error parsing JSON"
|
||||
end
|
||||
local f = {}
|
||||
for k in pairs(meta.files) do table.insert(f, k) end
|
||||
table.sort(f)
|
||||
progress("Success.\n")
|
||||
return { description = meta.description, author = meta.owner.login, revisionCount = #meta.history, files = f }
|
||||
end
|
||||
|
||||
--- Uploads a list of files to Gist, updating a previous Gist if desired.
|
||||
-- @tparam table files The files to upload to Gist. This table should be
|
||||
-- structured with a key as file name and a string with the file contents. If
|
||||
-- updating a Gist, files can be deleted by setting the data to textutils.json_null.
|
||||
-- @tparam[opt] string description The description of the Gist. This is required
|
||||
-- when updating a Gist, but is optional when uploading a Gist for the first
|
||||
-- time. If you don't want to change the description when updating, you can get
|
||||
-- the current description with gist.info() and pass in the description field.
|
||||
-- @tparam[opt] string id The ID of the Gist to update. If nil, a new Gist will
|
||||
-- be created.
|
||||
-- @tparam[opt] boolean interactive Set this to true to allow asking for a PAK
|
||||
-- if one is not available in the settings. If this is not specified, this
|
||||
-- function will return nil if gist.id is not available in the settings.
|
||||
-- @treturn string|nil The ID of the Gist, or nil on error.
|
||||
-- @treturn string|nil The URL of the Gist, or a string on error.
|
||||
function gist.put(files, description, id, interactive)
|
||||
expect(1, files, "table")
|
||||
expect(3, id, "string", "nil")
|
||||
expect(2, description, "string", id == nil and "nil" or nil)
|
||||
expect(4, interactive, "boolean", "nil")
|
||||
if id then
|
||||
if id:find("https?://") then id = id:gsub("https?://[^/]+/", ""):gsub("^[^/]*[^/%x]+[^/]*/", "") end
|
||||
if id:find("/") ~= nil then id = id:match("^([0-9A-Fa-f:]+)/.*$") end
|
||||
if id == nil or not id:match("^[0-9A-Fa-f][0-9A-Fa-f:]+[0-9A-Fa-f]$") then error("bad argument #3 to 'put' (invalid ID)", 2) end
|
||||
if id:find(":") ~= nil then id = id:gsub(":", "/") end
|
||||
end
|
||||
local data = { files = {}, public = true, description = description }
|
||||
for k, v in pairs(files) do if v == textutils.json_null then data.files[k] = v else data.files[k] = { content = v } end end
|
||||
local headers = { ["Content-Type"] = "application/json" }
|
||||
if not requestAuth(headers, interactive) then return nil, "Authentication required" end
|
||||
if interactive then write("Connecting to api.github.com... ") end
|
||||
local handle
|
||||
if id then handle = http.post{ url = "https://api.github.com/gists/" .. id, body = textutils.serializeJSON(data):gsub("\n", "n"), headers = headers, method = "PATCH" }
|
||||
else handle = http.post("https://api.github.com/gists", textutils.serializeJSON(data):gsub("\n", "n"), headers) end
|
||||
if handle == nil then if interactive then print("Failed.") end return nil, "Could not connect" end
|
||||
local resp = textutils.unserializeJSON(handle.readAll())
|
||||
if handle.getResponseCode() ~= 201 and handle.getResponseCode() ~= 200 or resp == nil then
|
||||
if interactive then print("Failed: " .. handle.getResponseCode() .. ": " .. (resp and resp.message or "Unknown error")) end
|
||||
handle.close()
|
||||
return nil, "Failed: " .. handle.getResponseCode() .. ": " .. (resp and resp.message or "Unknown error")
|
||||
end
|
||||
handle.close()
|
||||
if interactive then print("Success.") end
|
||||
return resp.id, resp.html_url
|
||||
end
|
||||
|
||||
--- Deletes a Gist.
|
||||
-- @tparam string id The Gist ID to delete.
|
||||
-- @tparam[opt] boolean interactive Set this to true to allow asking for a PAK
|
||||
-- if one is not available in the settings. If this is not specified, this
|
||||
-- function will return false if gist.id is not available in the settings.
|
||||
-- @treturn boolean Whether the request succeeded.
|
||||
-- @treturn string|nil If an error occurred, a message describing the error.
|
||||
function gist.delete(id, interactive)
|
||||
expect(1, id, "string")
|
||||
expect(2, interactive, "boolean", "nil")
|
||||
if id:find("https?://") then id = id:gsub("https?://[^/]+/", ""):gsub("^[^/]*[^/%x]+[^/]*/", "") end
|
||||
if id:find("/") ~= nil or id:find(":") ~= nil then id = id:match("^([0-9A-Fa-f]+)") end
|
||||
if id == nil or not id:match("^[0-9A-Fa-f][0-9A-Fa-f:]+[0-9A-Fa-f]$") then error("bad argument #1 to 'delete' (invalid ID)", 2) end
|
||||
local headers = {}
|
||||
if not requestAuth(headers, interactive) then return false, "Authentication required" end
|
||||
if interactive then write("Connecting to api.github.com... ") end
|
||||
local handle = http.post{ url = "https://api.github.com/gists/" .. id, headers = headers, method = "DELETE" }
|
||||
if handle == nil then if interactive then print("Failed.") end return false, "Could not connect" end
|
||||
if handle.getResponseCode() ~= 204 then
|
||||
local resp = textutils.unserializeJSON(handle.readAll())
|
||||
if interactive then print("Failed: " .. handle.getResponseCode() .. ": " .. (resp and resp.message or "Unknown error")) end
|
||||
handle.close()
|
||||
return false, "Failed: " .. handle.getResponseCode() .. ": " .. (resp and resp.message or "Unknown error")
|
||||
end
|
||||
handle.close()
|
||||
if interactive then print("Success.") end
|
||||
return true
|
||||
end
|
||||
|
||||
return gist
|
||||
523
data/computercraft/lua/rom/modules/main/cc/pretty.lua
Normal file
523
data/computercraft/lua/rom/modules/main/cc/pretty.lua
Normal file
@@ -0,0 +1,523 @@
|
||||
--[[- Provides a "pretty printer", for rendering data structures in an
|
||||
aesthetically pleasing manner.
|
||||
|
||||
In order to display something using @{cc.pretty}, you build up a series of
|
||||
@{Doc|documents}. These behave a little bit like strings; you can concatenate
|
||||
them together and then print them to the screen.
|
||||
|
||||
However, documents also allow you to control how they should be printed. There
|
||||
are several functions (such as @{nest} and @{group}) which allow you to control
|
||||
the "layout" of the document. When you come to display the document, the 'best'
|
||||
(most compact) layout is used.
|
||||
|
||||
The structure of this module is based on [A Prettier Printer][prettier].
|
||||
|
||||
[prettier]: https://homepages.inf.ed.ac.uk/wadler/papers/prettier/prettier.pdf "A Prettier Printer"
|
||||
|
||||
@module cc.pretty
|
||||
@since 1.87.0
|
||||
@usage Print a table to the terminal
|
||||
|
||||
local pretty = require "cc.pretty"
|
||||
pretty.pretty_print({ 1, 2, 3 })
|
||||
|
||||
@usage Build a custom document and display it
|
||||
|
||||
local pretty = require "cc.pretty"
|
||||
pretty.print(pretty.group(pretty.text("hello") .. pretty.space_line .. pretty.text("world")))
|
||||
]]
|
||||
|
||||
local _expect = require "cc.expect"
|
||||
local expect, field = _expect.expect, _expect.field
|
||||
|
||||
local term = require("term")
|
||||
local type, getmetatable, setmetatable, colours, str_write, tostring = type, getmetatable, setmetatable, require("colours"), require("rc").write, tostring
|
||||
local debug_info, debug_local = debug.getinfo, debug.getlocal
|
||||
|
||||
--- @{table.insert} alternative, but with the length stored inline.
|
||||
local function append(out, value)
|
||||
local n = out.n + 1
|
||||
out[n], out.n = value, n
|
||||
end
|
||||
|
||||
--- A document containing formatted text, with multiple possible layouts.
|
||||
--
|
||||
-- Documents effectively represent a sequence of strings in alternative layouts,
|
||||
-- which we will try to print in the most compact form necessary.
|
||||
--
|
||||
-- @type Doc
|
||||
local Doc = { }
|
||||
|
||||
local function mk_doc(tbl) return setmetatable(tbl, Doc) end
|
||||
|
||||
--- An empty document.
|
||||
local empty = mk_doc({ tag = "nil" })
|
||||
|
||||
--- A document with a single space in it.
|
||||
local space = mk_doc({ tag = "text", text = " " })
|
||||
|
||||
--- A line break. When collapsed with @{group}, this will be replaced with @{empty}.
|
||||
local line = mk_doc({ tag = "line", flat = empty })
|
||||
|
||||
--- A line break. When collapsed with @{group}, this will be replaced with @{space}.
|
||||
local space_line = mk_doc({ tag = "line", flat = space })
|
||||
|
||||
local text_cache = { [""] = empty, [" "] = space, ["\n"] = space_line }
|
||||
|
||||
local function mk_text(text, colour)
|
||||
return text_cache[text] or setmetatable({ tag = "text", text = text, colour = colour }, Doc)
|
||||
end
|
||||
|
||||
--- Create a new document from a string.
|
||||
--
|
||||
-- If your string contains multiple lines, @{group} will flatten the string
|
||||
-- into a single line, with spaces between each line.
|
||||
--
|
||||
-- @tparam string text The string to construct a new document with.
|
||||
-- @tparam[opt] number colour The colour this text should be printed with. If not given, we default to the current
|
||||
-- colour.
|
||||
-- @treturn Doc The document with the provided text.
|
||||
-- @usage Write some blue text.
|
||||
--
|
||||
-- local pretty = require "cc.pretty"
|
||||
-- pretty.print(pretty.text("Hello!", colours.blue))
|
||||
local function text(text, colour)
|
||||
expect(1, text, "string")
|
||||
expect(2, colour, "number", "nil")
|
||||
|
||||
local cached = text_cache[text]
|
||||
if cached then return cached end
|
||||
|
||||
local new_line = text:find("\n", 1)
|
||||
if not new_line then return mk_text(text, colour) end
|
||||
|
||||
-- Split the string by "\n". With a micro-optimisation to skip empty strings.
|
||||
local doc = setmetatable({ tag = "concat", n = 0 }, Doc)
|
||||
if new_line ~= 1 then append(doc, mk_text(text:sub(1, new_line - 1), colour)) end
|
||||
|
||||
new_line = new_line + 1
|
||||
while true do
|
||||
local next_line = text:find("\n", new_line)
|
||||
append(doc, space_line)
|
||||
if not next_line then
|
||||
if new_line <= #text then append(doc, mk_text(text:sub(new_line), colour)) end
|
||||
return doc
|
||||
else
|
||||
if new_line <= next_line - 1 then
|
||||
append(doc, mk_text(text:sub(new_line, next_line - 1), colour))
|
||||
end
|
||||
new_line = next_line + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Concatenate several documents together. This behaves very similar to string concatenation.
|
||||
--
|
||||
-- @tparam Doc|string ... The documents to concatenate.
|
||||
-- @treturn Doc The concatenated documents.
|
||||
-- @usage
|
||||
-- local pretty = require "cc.pretty"
|
||||
-- local doc1, doc2 = pretty.text("doc1"), pretty.text("doc2")
|
||||
-- print(pretty.concat(doc1, " - ", doc2))
|
||||
-- print(doc1 .. " - " .. doc2) -- Also supports ..
|
||||
local function concat(...)
|
||||
local args = table.pack(...)
|
||||
for i = 1, args.n do
|
||||
if type(args[i]) == "string" then args[i] = text(args[i]) end
|
||||
if getmetatable(args[i]) ~= Doc then expect(i, args[i], "document") end
|
||||
end
|
||||
|
||||
if args.n == 0 then return empty end
|
||||
if args.n == 1 then return args[1] end
|
||||
|
||||
args.tag = "concat"
|
||||
return setmetatable(args, Doc)
|
||||
end
|
||||
|
||||
Doc.__concat = concat --- @local
|
||||
|
||||
--- Indent later lines of the given document with the given number of spaces.
|
||||
--
|
||||
-- For instance, nesting the document
|
||||
-- ```txt
|
||||
-- foo
|
||||
-- bar
|
||||
-- ```
|
||||
-- by two spaces will produce
|
||||
-- ```txt
|
||||
-- foo
|
||||
-- bar
|
||||
-- ```
|
||||
--
|
||||
-- @tparam number depth The number of spaces with which the document should be indented.
|
||||
-- @tparam Doc doc The document to indent.
|
||||
-- @treturn Doc The nested document.
|
||||
-- @usage
|
||||
-- local pretty = require "cc.pretty"
|
||||
-- print(pretty.nest(2, pretty.text("foo\nbar")))
|
||||
local function nest(depth, doc)
|
||||
expect(1, depth, "number")
|
||||
if getmetatable(doc) ~= Doc then expect(2, doc, "document") end
|
||||
if depth <= 0 then error("depth must be a positive number", 2) end
|
||||
|
||||
return setmetatable({ tag = "nest", depth = depth, doc }, Doc)
|
||||
end
|
||||
|
||||
local function flatten(doc)
|
||||
if doc.flat then return doc.flat end
|
||||
|
||||
local kind = doc.tag
|
||||
if kind == "nil" or kind == "text" then
|
||||
return doc
|
||||
elseif kind == "concat" then
|
||||
local out = setmetatable({ tag = "concat", n = doc.n }, Doc)
|
||||
for i = 1, doc.n do out[i] = flatten(doc[i]) end
|
||||
doc.flat, out.flat = out, out -- cache the flattened node
|
||||
return out
|
||||
elseif kind == "nest" then
|
||||
return flatten(doc[1])
|
||||
elseif kind == "group" then
|
||||
return doc[1]
|
||||
else
|
||||
error("Unknown doc " .. kind)
|
||||
end
|
||||
end
|
||||
|
||||
--- Builds a document which is displayed on a single line if there is enough
|
||||
-- room, or as normal if not.
|
||||
--
|
||||
-- @tparam Doc doc The document to group.
|
||||
-- @treturn Doc The grouped document.
|
||||
-- @usage Uses group to show things being displayed on one or multiple lines.
|
||||
--
|
||||
-- local pretty = require "cc.pretty"
|
||||
-- local doc = pretty.group("Hello" .. pretty.space_line .. "World")
|
||||
-- print(pretty.render(doc, 5)) -- On multiple lines
|
||||
-- print(pretty.render(doc, 20)) -- Collapsed onto one.
|
||||
local function group(doc)
|
||||
if getmetatable(doc) ~= Doc then expect(1, doc, "document") end
|
||||
|
||||
if doc.tag == "group" then return doc end -- Skip if already grouped.
|
||||
|
||||
local flattened = flatten(doc)
|
||||
if flattened == doc then return doc end -- Also skip if flattening does nothing.
|
||||
return setmetatable({ tag = "group", flattened, doc }, Doc)
|
||||
end
|
||||
|
||||
local function get_remaining(doc, width)
|
||||
local kind = doc.tag
|
||||
if kind == "nil" or kind == "line" then
|
||||
return width
|
||||
elseif kind == "text" then
|
||||
return width - #doc.text
|
||||
elseif kind == "concat" then
|
||||
for i = 1, doc.n do
|
||||
width = get_remaining(doc[i], width)
|
||||
if width < 0 then break end
|
||||
end
|
||||
return width
|
||||
elseif kind == "group" or kind == "nest" then
|
||||
return get_remaining(kind[1])
|
||||
else
|
||||
error("Unknown doc " .. kind)
|
||||
end
|
||||
end
|
||||
|
||||
--- Display a document on the terminal.
|
||||
--
|
||||
-- @tparam Doc doc The document to render
|
||||
-- @tparam[opt=0.6] number ribbon_frac The maximum fraction of the width that we should write in.
|
||||
local function write(doc, ribbon_frac)
|
||||
if getmetatable(doc) ~= Doc then expect(1, doc, "document") end
|
||||
expect(2, ribbon_frac, "number", "nil")
|
||||
|
||||
local term = term
|
||||
local width, height = term.getSize()
|
||||
local ribbon_width = (ribbon_frac or 0.6) * width
|
||||
if ribbon_width < 0 then ribbon_width = 0 end
|
||||
if ribbon_width > width then ribbon_width = width end
|
||||
|
||||
local def_colour = term.getTextColour()
|
||||
local current_colour = def_colour
|
||||
|
||||
local function go(doc, indent, col)
|
||||
local kind = doc.tag
|
||||
if kind == "nil" then
|
||||
return col
|
||||
elseif kind == "text" then
|
||||
local doc_colour = doc.colour or def_colour
|
||||
if doc_colour ~= current_colour then
|
||||
term.setTextColour(doc_colour)
|
||||
current_colour = doc_colour
|
||||
end
|
||||
|
||||
str_write(doc.text)
|
||||
|
||||
return col + #doc.text
|
||||
elseif kind == "line" then
|
||||
local _, y = term.getCursorPos()
|
||||
if y < height then
|
||||
term.setCursorPos(indent + 1, y + 1)
|
||||
else
|
||||
term.scroll(1)
|
||||
term.setCursorPos(indent + 1, height)
|
||||
end
|
||||
|
||||
return indent
|
||||
elseif kind == "concat" then
|
||||
for i = 1, doc.n do col = go(doc[i], indent, col) end
|
||||
return col
|
||||
elseif kind == "nest" then
|
||||
return go(doc[1], indent + doc.depth, col)
|
||||
elseif kind == "group" then
|
||||
if get_remaining(doc[1], math.min(width, ribbon_width + indent) - col) >= 0 then
|
||||
return go(doc[1], indent, col)
|
||||
else
|
||||
return go(doc[2], indent, col)
|
||||
end
|
||||
else
|
||||
error("Unknown doc " .. kind)
|
||||
end
|
||||
end
|
||||
|
||||
local col = math.max(term.getCursorPos() - 1, 0)
|
||||
go(doc, 0, col)
|
||||
if current_colour ~= def_colour then term.setTextColour(def_colour) end
|
||||
end
|
||||
|
||||
--- Display a document on the terminal with a trailing new line.
|
||||
--
|
||||
-- @tparam Doc doc The document to render.
|
||||
-- @tparam[opt=0.6] number ribbon_frac The maximum fraction of the width that we should write in.
|
||||
local function print(doc, ribbon_frac)
|
||||
if getmetatable(doc) ~= Doc then expect(1, doc, "document") end
|
||||
expect(2, ribbon_frac, "number", "nil")
|
||||
write(doc, ribbon_frac)
|
||||
str_write("\n")
|
||||
end
|
||||
|
||||
--- Render a document, converting it into a string.
|
||||
--
|
||||
-- @tparam Doc doc The document to render.
|
||||
-- @tparam[opt] number width The maximum width of this document. Note that long strings will not be wrapped to
|
||||
-- fit this width - it is only used for finding the best layout.
|
||||
-- @tparam[opt=0.6] number ribbon_frac The maximum fraction of the width that we should write in.
|
||||
-- @treturn string The rendered document as a string.
|
||||
local function render(doc, width, ribbon_frac)
|
||||
if getmetatable(doc) ~= Doc then expect(1, doc, "document") end
|
||||
expect(2, width, "number", "nil")
|
||||
expect(3, ribbon_frac, "number", "nil")
|
||||
|
||||
local ribbon_width
|
||||
if width then
|
||||
ribbon_width = (ribbon_frac or 0.6) * width
|
||||
if ribbon_width < 0 then ribbon_width = 0 end
|
||||
if ribbon_width > width then ribbon_width = width end
|
||||
end
|
||||
|
||||
local out = { n = 0 }
|
||||
local function go(doc, indent, col)
|
||||
local kind = doc.tag
|
||||
if kind == "nil" then
|
||||
return col
|
||||
elseif kind == "text" then
|
||||
append(out, doc.text)
|
||||
return col + #doc.text
|
||||
elseif kind == "line" then
|
||||
append(out, "\n" .. (" "):rep(indent))
|
||||
return indent
|
||||
elseif kind == "concat" then
|
||||
for i = 1, doc.n do col = go(doc[i], indent, col) end
|
||||
return col
|
||||
elseif kind == "nest" then
|
||||
return go(doc[1], indent + doc.depth, col)
|
||||
elseif kind == "group" then
|
||||
if not width or get_remaining(doc[1], math.min(width, ribbon_width + indent) - col) >= 0 then
|
||||
return go(doc[1], indent, col)
|
||||
else
|
||||
return go(doc[2], indent, col)
|
||||
end
|
||||
else
|
||||
error("Unknown doc " .. kind)
|
||||
end
|
||||
end
|
||||
|
||||
go(doc, 0, 0)
|
||||
return table.concat(out, "", 1, out.n)
|
||||
end
|
||||
|
||||
Doc.__tostring = render --- @local
|
||||
|
||||
local keywords = {
|
||||
["and"] = true, ["break"] = true, ["do"] = true, ["else"] = true,
|
||||
["elseif"] = true, ["end"] = true, ["false"] = true, ["for"] = true,
|
||||
["function"] = true, ["if"] = true, ["in"] = true, ["local"] = true,
|
||||
["nil"] = true, ["not"] = true, ["or"] = true, ["repeat"] = true, ["return"] = true,
|
||||
["then"] = true, ["true"] = true, ["until"] = true, ["while"] = true,
|
||||
}
|
||||
|
||||
local comma = text(",")
|
||||
local braces = text("{}")
|
||||
local obrace, cbrace = text("{"), text("}")
|
||||
local obracket, cbracket = text("["), text("] = ")
|
||||
|
||||
local function key_compare(a, b)
|
||||
local ta, tb = type(a), type(b)
|
||||
|
||||
if ta == "string" then return tb ~= "string" or a < b
|
||||
elseif tb == "string" then return false
|
||||
end
|
||||
|
||||
if ta == "number" then return tb ~= "number" or a < b end
|
||||
return false
|
||||
end
|
||||
|
||||
local function show_function(fn, options)
|
||||
local info = debug_info and debug_info(fn, "Su")
|
||||
|
||||
-- Include function source position if available
|
||||
local name
|
||||
if options.function_source and info and info.short_src and info.linedefined and info.linedefined >= 1 then
|
||||
name = "function<" .. info.short_src .. ":" .. info.linedefined .. ">"
|
||||
else
|
||||
name = tostring(fn)
|
||||
end
|
||||
|
||||
-- Include arguments if a Lua function and if available. Lua will report "C"
|
||||
-- functions as variadic.
|
||||
if options.function_args and info and info.what == "Lua" and info.nparams and debug_local then
|
||||
local args = {}
|
||||
for i = 1, info.nparams do args[i] = debug_local(fn, i) or "?" end
|
||||
if info.isvararg then args[#args + 1] = "..." end
|
||||
name = name .. "(" .. table.concat(args, ", ") .. ")"
|
||||
end
|
||||
|
||||
return name
|
||||
end
|
||||
|
||||
local function pretty_impl(obj, options, tracking)
|
||||
local obj_type = type(obj)
|
||||
if obj_type == "string" then
|
||||
local formatted = ("%q"):format(obj):gsub("\\\n", "\\n")
|
||||
return text(formatted, colours.red)
|
||||
elseif obj_type == "number" then
|
||||
return text(tostring(obj), colours.magenta)
|
||||
elseif obj_type == "function" then
|
||||
return text(show_function(obj, options), colours.lightGrey)
|
||||
elseif obj_type ~= "table" or tracking[obj] then
|
||||
return text(tostring(obj), colours.lightGrey)
|
||||
elseif getmetatable(obj) ~= nil and getmetatable(obj).__tostring then
|
||||
return text(tostring(obj))
|
||||
elseif next(obj) == nil then
|
||||
return braces
|
||||
else
|
||||
tracking[obj] = true
|
||||
local doc = setmetatable({ tag = "concat", n = 1, space_line }, Doc)
|
||||
|
||||
local length, keys, keysn = #obj, {}, 1
|
||||
for k in pairs(obj) do
|
||||
if type(k) ~= "number" or k % 1 ~= 0 or k < 1 or k > length then
|
||||
keys[keysn], keysn = k, keysn + 1
|
||||
end
|
||||
end
|
||||
table.sort(keys, key_compare)
|
||||
|
||||
for i = 1, length do
|
||||
if i > 1 then append(doc, comma) append(doc, space_line) end
|
||||
append(doc, pretty_impl(obj[i], options, tracking))
|
||||
end
|
||||
|
||||
for i = 1, keysn - 1 do
|
||||
if i > 1 or length >= 1 then append(doc, comma) append(doc, space_line) end
|
||||
|
||||
local k = keys[i]
|
||||
local v = obj[k]
|
||||
if type(k) == "string" and not keywords[k] and k:match("^[%a_][%a%d_]*$") then
|
||||
append(doc, text(k .. " = "))
|
||||
append(doc, pretty_impl(v, options, tracking))
|
||||
else
|
||||
append(doc, obracket)
|
||||
append(doc, pretty_impl(k, options, tracking))
|
||||
append(doc, cbracket)
|
||||
append(doc, pretty_impl(v, options, tracking))
|
||||
end
|
||||
end
|
||||
|
||||
tracking[obj] = nil
|
||||
return group(concat(obrace, nest(2, concat(table.unpack(doc, 1, doc.n))), space_line, cbrace))
|
||||
end
|
||||
end
|
||||
|
||||
--- Pretty-print an arbitrary object, converting it into a document.
|
||||
--
|
||||
-- This can then be rendered with @{write} or @{print}.
|
||||
--
|
||||
-- @param obj The object to pretty-print.
|
||||
-- @tparam[opt] { function_args = boolean, function_source = boolean } options
|
||||
-- Controls how various properties are displayed.
|
||||
-- - `function_args`: Show the arguments to a function if known (`false` by default).
|
||||
-- - `function_source`: Show where the function was defined, instead of
|
||||
-- `function: xxxxxxxx` (`false` by default).
|
||||
-- @treturn Doc The object formatted as a document.
|
||||
-- @changed 1.88.0 Added `options` argument.
|
||||
-- @usage Display a table on the screen
|
||||
--
|
||||
-- local pretty = require "cc.pretty"
|
||||
-- pretty.print(pretty.pretty({ 1, 2, 3 }))
|
||||
-- @see pretty_print for a shorthand to prettify and print an object.
|
||||
local function pretty(obj, options)
|
||||
expect(2, options, "table", "nil")
|
||||
options = options or {}
|
||||
|
||||
local actual_options = {
|
||||
function_source = field(options, "function_source", "boolean", "nil") or false,
|
||||
function_args = field(options, "function_args", "boolean", "nil") or false,
|
||||
}
|
||||
return pretty_impl(obj, actual_options, {})
|
||||
end
|
||||
|
||||
--[[- A shortcut for calling @{pretty} and @{print} together.
|
||||
|
||||
@param obj The object to pretty-print.
|
||||
@tparam[opt] { function_args = boolean, function_source = boolean } options
|
||||
Controls how various properties are displayed.
|
||||
- `function_args`: Show the arguments to a function if known (`false` by default).
|
||||
- `function_source`: Show where the function was defined, instead of
|
||||
`function: xxxxxxxx` (`false` by default).
|
||||
@tparam[opt=0.6] number ribbon_frac The maximum fraction of the width that we should write in.
|
||||
|
||||
@usage Display a table on the screen.
|
||||
|
||||
local pretty = require "cc.pretty"
|
||||
pretty.pretty_print({ 1, 2, 3 })
|
||||
|
||||
@see pretty
|
||||
@see print
|
||||
@since 1.99
|
||||
]]
|
||||
local function pretty_print(obj, options, ribbon_frac)
|
||||
expect(2, options, "table", "nil")
|
||||
options = options or {}
|
||||
expect(3, ribbon_frac, "number", "nil")
|
||||
|
||||
return print(pretty(obj, options), ribbon_frac)
|
||||
end
|
||||
|
||||
return {
|
||||
empty = empty,
|
||||
space = space,
|
||||
line = line,
|
||||
space_line = space_line,
|
||||
text = text,
|
||||
concat = concat,
|
||||
nest = nest,
|
||||
group = group,
|
||||
|
||||
write = write,
|
||||
print = print,
|
||||
render = render,
|
||||
|
||||
pretty = pretty,
|
||||
|
||||
pretty_print = pretty_print,
|
||||
}
|
||||
113
data/computercraft/lua/rom/modules/main/cc/shell/completion.lua
Normal file
113
data/computercraft/lua/rom/modules/main/cc/shell/completion.lua
Normal file
@@ -0,0 +1,113 @@
|
||||
-- cc.shell.completion
|
||||
-- XXX incompatible: functions do not accept a 'shell' argument
|
||||
|
||||
local completion = require("cc.completion")
|
||||
local expect = require("cc.expect").expect
|
||||
local shell = require("shell")
|
||||
local fs = require("fs")
|
||||
|
||||
local c = {}
|
||||
|
||||
function c.file(text)
|
||||
expect(1, text, "string")
|
||||
return fs.complete(text, shell.dir(), true, false)
|
||||
end
|
||||
|
||||
function c.dir(text)
|
||||
expect(1, text, "string")
|
||||
return fs.complete(text, shell.dir(), false)
|
||||
end
|
||||
|
||||
function c.dirOrFile(text, previous, add_space)
|
||||
expect(1, text, "string")
|
||||
expect(2, previous, "table")
|
||||
expect(3, add_space, "boolean", "nil")
|
||||
local completed = fs.complete(text, shell.dir(), true, true)
|
||||
|
||||
if add_space then
|
||||
for i=1, #completed, 1 do
|
||||
completed[i] = completed[i] .. " "
|
||||
end
|
||||
end
|
||||
|
||||
return completed
|
||||
end
|
||||
|
||||
function c.program(text, add_space)
|
||||
expect(1, text, "string")
|
||||
expect(2, add_space, "boolean", "table", "nil")
|
||||
local progs = shell.programs()
|
||||
local files = c.file(text)
|
||||
|
||||
local seen = {}
|
||||
for i=1, #files, 1 do
|
||||
local full = text..files[i]
|
||||
if fs.isDir(full) and full:sub(-1) ~= "/" then
|
||||
full = full .. "/"
|
||||
end
|
||||
if not seen[full] then
|
||||
progs[#progs+1] = full
|
||||
end
|
||||
seen[full] = true
|
||||
end
|
||||
|
||||
table.sort(progs, function(a,b) return #a < #b end)
|
||||
|
||||
return completion.choice(text, progs, add_space)
|
||||
end
|
||||
|
||||
function c.programWithArgs(text, previous, starting)
|
||||
expect(1, text, "string")
|
||||
expect(2, previous, "table")
|
||||
expect(3, starting, "number")
|
||||
|
||||
if not previous[starting] then
|
||||
return shell.completeProgram(text)
|
||||
end
|
||||
|
||||
local command = previous[starting]
|
||||
command = shell.aliases()[command] or command
|
||||
local complete = shell.getCompletionInfo()[command]
|
||||
|
||||
if complete then
|
||||
return complete(#previous - starting + 1, text,
|
||||
{table.unpack(previous, starting)})
|
||||
end
|
||||
end
|
||||
|
||||
for k,v in pairs(completion) do
|
||||
c[k] = function(text, _, ...) return v(text, ...) end
|
||||
end
|
||||
|
||||
function c.build(...)
|
||||
local args = table.pack(...)
|
||||
|
||||
for i=1, args.n, 1 do
|
||||
expect(i, args[i], "function", "table", "nil")
|
||||
end
|
||||
|
||||
return function(index, current, previous)
|
||||
local complete
|
||||
if args.n < index then
|
||||
if type(args[args.n]) == "table" and args[args.n].many then
|
||||
complete = args[args.n]
|
||||
end
|
||||
|
||||
else
|
||||
complete = args[index]
|
||||
end
|
||||
|
||||
if not complete then
|
||||
return {}
|
||||
end
|
||||
|
||||
if type(complete) == "function" then
|
||||
return complete(current, previous)
|
||||
|
||||
elseif type(complete) == "table" then
|
||||
return complete[1](current, previous, table.unpack(complete, 2))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return c
|
||||
133
data/computercraft/lua/rom/modules/main/cc/strings.lua
Normal file
133
data/computercraft/lua/rom/modules/main/cc/strings.lua
Normal file
@@ -0,0 +1,133 @@
|
||||
-- cc.strings
|
||||
|
||||
local term = require("term")
|
||||
local expectlib = require("cc.expect")
|
||||
local expect = expectlib.expect
|
||||
local field = expectlib.field
|
||||
local strings = {}
|
||||
|
||||
local function count(...)
|
||||
local tab = table.pack(...)
|
||||
local n = 0
|
||||
|
||||
for i=1, tab.n, 1 do
|
||||
if tab[i] then n = n + 1 end
|
||||
end
|
||||
|
||||
return n
|
||||
end
|
||||
|
||||
function strings.splitElements(text, limit)
|
||||
expect(1, text, "string")
|
||||
expect(2, limit, "number", "nil")
|
||||
|
||||
local tokens = {}
|
||||
|
||||
while #text > 0 do
|
||||
local ws = text:match("^[ \t]+")
|
||||
local nl = text:match("^\n+")
|
||||
local sep = text:match("^[%-%+%*]")
|
||||
local word = text:match("^[^ \t\n%-%+%*]+")
|
||||
|
||||
if count(ws, nl, sep, word) > 1 then
|
||||
error(("Edge case: %q, %q, %q, %q"):format(ws, nl, sep, word), 0)
|
||||
end
|
||||
|
||||
local token = ws or nl or sep or word
|
||||
text = text:sub(#token + 1)
|
||||
|
||||
while #token > 0 do
|
||||
local ttext = token:sub(1, limit or 65535)
|
||||
token = token:sub(#ttext + 1)
|
||||
|
||||
tokens[#tokens+1] = { text = ttext, type = ws and "ws" or nl and "nl"
|
||||
or sep and "word" or word and "word" }
|
||||
end
|
||||
end
|
||||
|
||||
return tokens
|
||||
end
|
||||
|
||||
function strings.wrappedWriteElements(elements, width, doHalves, handler)
|
||||
expect(1, elements, "table")
|
||||
expect(2, width, "number")
|
||||
expect(3, doHalves, "boolean", "nil")
|
||||
expect(4, handler, "table")
|
||||
|
||||
field(handler, "newline", "function")
|
||||
field(handler, "append", "function")
|
||||
field(handler, "getX", "function")
|
||||
|
||||
for i=1, #elements, 1 do
|
||||
local e = elements[i]
|
||||
|
||||
if e.type == "nl" then
|
||||
for _=1, #e.text do handler.newline() end
|
||||
|
||||
elseif e.type == "ws" then
|
||||
local x = handler.getX()
|
||||
|
||||
if x + #e.text > width + 1 then
|
||||
handler.newline()
|
||||
|
||||
else
|
||||
handler.append(e.text)
|
||||
end
|
||||
|
||||
elseif e.type == "word" then
|
||||
local x = handler.getX()
|
||||
local half = math.ceil(#e.text / 2)
|
||||
|
||||
if x + #e.text > width + 1 then
|
||||
if doHalves and x + half < width and #e.text > width/2 then
|
||||
local halfText = e.text:sub(1, math.floor(#e.text / 2)) .. "-"
|
||||
e.text = e.text:sub(#halfText)
|
||||
handler.append(halfText)
|
||||
handler.newline()
|
||||
|
||||
elseif x > 1 then
|
||||
handler.newline()
|
||||
end
|
||||
end
|
||||
|
||||
handler.append(e.text)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function strings.wrap(text, width, doHalves)
|
||||
expect(1, text, "string")
|
||||
expect(2, width, "number", "nil")
|
||||
expect(3, doHalves, "boolean", "nil")
|
||||
|
||||
width = width or term.getSize()
|
||||
|
||||
local lines = { "" }
|
||||
local elements = strings.splitElements(text, width)
|
||||
|
||||
strings.wrappedWriteElements(elements, width, doHalves, {
|
||||
newline = function()
|
||||
lines[#lines+1] = ""
|
||||
end,
|
||||
|
||||
append = function(newText)
|
||||
lines[#lines] = lines[#lines] .. newText
|
||||
end,
|
||||
|
||||
getX = function()
|
||||
return #lines[#lines]
|
||||
end
|
||||
})
|
||||
|
||||
return lines
|
||||
end
|
||||
|
||||
function strings.ensure_width(line, width)
|
||||
expect(1, line, "string")
|
||||
expect(2, width, "number", "nil")
|
||||
width = width or term.getSize()
|
||||
|
||||
return (line .. (" "):rep(width - #line)):sub(1, width)
|
||||
end
|
||||
|
||||
return strings
|
||||
198
data/computercraft/lua/rom/modules/main/edit/syntax.lua
Normal file
198
data/computercraft/lua/rom/modules/main/edit/syntax.lua
Normal file
@@ -0,0 +1,198 @@
|
||||
-- A simple, fairly clever method of tokenizing code.
|
||||
-- Each token is defined by a set of rules. These rules are
|
||||
-- accordingly defined in the relevant syntax definition file.
|
||||
-- A rule takes the form of a triplet of functions:
|
||||
-- - The first function takes a single character, and returns
|
||||
-- whether that character is valid as part of the corresponding
|
||||
-- token.
|
||||
-- - The second function takes a single character and the current
|
||||
-- token, and returns whether that character is valid as part
|
||||
-- of the token. This allows flexible implementations of highly
|
||||
-- language-specific features such as strings.
|
||||
-- - The third function takes only a token, and returns whether
|
||||
-- that token is valid.
|
||||
--
|
||||
-- Multiple tokens may be evaluated in parallel and the longest is returned.
|
||||
|
||||
local lib = {}
|
||||
|
||||
local syntenv = {
|
||||
char = function(str)
|
||||
return {
|
||||
function(c)
|
||||
return c == str:sub(1,1)
|
||||
end,
|
||||
function(tk, c)
|
||||
return tk .. c == str:sub(1, #tk + 1)
|
||||
end,
|
||||
function(tk)
|
||||
return tk == str
|
||||
end
|
||||
}
|
||||
end,
|
||||
print = print,
|
||||
string = string, table = table,
|
||||
pairs = pairs, ipairs = ipairs,
|
||||
tonumber = tonumber, math = math,
|
||||
globalenv = _G, type = type,
|
||||
}
|
||||
|
||||
-- basic ""reader""
|
||||
local function reader(text)
|
||||
local chars = {}
|
||||
for c in text:gmatch(".") do
|
||||
chars[#chars+1] = c
|
||||
end
|
||||
|
||||
local i = 0
|
||||
return {
|
||||
advance = function()
|
||||
i = i + 1
|
||||
return chars[i]
|
||||
end,
|
||||
backpedal = function()
|
||||
i = math.max(0, i - 1)
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
-- Takes a file and returns a builder.
|
||||
function lib.new(file)
|
||||
local definitions = assert(loadfile(file, "t", syntenv))()
|
||||
|
||||
for _, _defs in pairs(definitions) do
|
||||
for i, ent in pairs(_defs) do
|
||||
if type(ent) == "string" then
|
||||
_defs[i] = syntenv.char(ent)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return function(text)
|
||||
local read = reader(text)
|
||||
local possibilities = {}
|
||||
local aux = ""
|
||||
|
||||
-- find and return the most likely (aka, longest) token and its class
|
||||
local function most_likely()
|
||||
-- if there are no possibilities, then ...
|
||||
if #possibilities == 0 then
|
||||
-- ... if the aux value has some characters, return that ...
|
||||
if #aux > 0 then
|
||||
local result = aux
|
||||
aux = ""
|
||||
return result
|
||||
else
|
||||
-- ... otherwise return nil.
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
local former_longest, new_longest = 0, 0
|
||||
|
||||
-- remove all invalid possibilites
|
||||
for i=#possibilities, 1, -1 do
|
||||
if not possibilities[i].valid(possibilities[i].token) then
|
||||
former_longest = math.max(#possibilities[i].token, former_longest)
|
||||
table.remove(possibilities, i)
|
||||
else
|
||||
new_longest = math.max(#possibilities[i].token, new_longest)
|
||||
end
|
||||
end
|
||||
|
||||
if former_longest > new_longest then
|
||||
for _=new_longest, former_longest - 1 do
|
||||
read.backpedal()
|
||||
end
|
||||
end
|
||||
|
||||
-- sort possibilities by length - and deprioritize whitespace/word
|
||||
table.sort(possibilities, function(a, b)
|
||||
return #a.token > #b.token
|
||||
or (#a.token == #b.token and b.class == "word")
|
||||
or b.class == "whitespace"
|
||||
end)
|
||||
|
||||
if #possibilities == 0 then
|
||||
--read.backpedal()
|
||||
return most_likely()
|
||||
end
|
||||
|
||||
-- grab the first (longest) one
|
||||
local token, class = possibilities[1].token, possibilities[1].class
|
||||
-- reset possibilities
|
||||
possibilities = {}
|
||||
|
||||
aux = ""
|
||||
|
||||
-- return it
|
||||
return token, class
|
||||
end
|
||||
|
||||
-- return an iterator!
|
||||
return function()
|
||||
while true do
|
||||
local c = read.advance()
|
||||
|
||||
-- if no character, return the most likely token
|
||||
if not c then return most_likely() end
|
||||
|
||||
if #possibilities == 0 then
|
||||
-- if no current possibilities, then go through and check for them
|
||||
for class, defs in pairs(definitions) do
|
||||
for _, funcs in pairs(defs) do
|
||||
if funcs[1](c) then
|
||||
-- if the token is valid, add it here
|
||||
possibilities[#possibilities+1] = {
|
||||
check = funcs[2], class = class, token = c,
|
||||
valid = funcs[3] or function()return true end, active = true
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- if there are now some possibilities, return whatever the "aux"
|
||||
-- value was
|
||||
if #possibilities > 0 then
|
||||
if #aux > 0 then
|
||||
local temp = aux--:sub(1,-2)
|
||||
aux = ""
|
||||
return temp
|
||||
end
|
||||
aux = c
|
||||
else
|
||||
-- otherwise, add c to the aux value
|
||||
aux = aux .. c
|
||||
end
|
||||
else
|
||||
aux = aux .. c
|
||||
-- whether any possibilities matched
|
||||
local valid_for_any = false
|
||||
|
||||
for _, p in ipairs(possibilities) do
|
||||
-- 'active' is roughly equal to whether the last character matched
|
||||
if p.active then
|
||||
-- if valid, set valid_for_any to true and add c to its valid
|
||||
if p.check(p.token, c) then
|
||||
valid_for_any = true
|
||||
p.token = p.token .. c
|
||||
else
|
||||
-- otherwise, disable it from future checks
|
||||
p.active = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- if nothing was valid, retract the current character
|
||||
-- and return the most likely token
|
||||
if not valid_for_any then
|
||||
read.backpedal()
|
||||
return most_likely()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return lib
|
||||
152
data/computercraft/lua/rom/modules/main/edit/syntax/lua.lua
Normal file
152
data/computercraft/lua/rom/modules/main/edit/syntax/lua.lua
Normal file
@@ -0,0 +1,152 @@
|
||||
local syn = {
|
||||
whitespace = {
|
||||
{
|
||||
function(c)
|
||||
return c:match("[ \n\r\t]")
|
||||
end,
|
||||
function()
|
||||
return false
|
||||
end,
|
||||
function(c)
|
||||
return c:match("^[ \n\r\t]+")
|
||||
end
|
||||
},
|
||||
},
|
||||
word = {
|
||||
{
|
||||
function(c)
|
||||
return not not c:match("[a-zA-Z_]")
|
||||
end,
|
||||
function(_, c)
|
||||
return not not c:match("[a-zA-Z_0-9]")
|
||||
end
|
||||
}
|
||||
},
|
||||
keyword = {
|
||||
"const", "close", "local", "while", "for", "repeat", "until", "do", "if",
|
||||
"in", "else", "elseif", "and", "or", "not", "then", "end", "return",
|
||||
"goto", "break",
|
||||
},
|
||||
builtin = {
|
||||
"function",
|
||||
},
|
||||
separator = {
|
||||
",", "(", ")", "{", "}", "[", "]",
|
||||
},
|
||||
operator = {
|
||||
"+", "-", "/", "*", "//", "==", ">>", "<<", ">", "<", "=", "&",
|
||||
"|", "^", "%", "~", "...", "..", "~=", "#", ".", ":"
|
||||
},
|
||||
boolean = {
|
||||
"true", "false", "nil"
|
||||
},
|
||||
comment = {
|
||||
{
|
||||
function(c)
|
||||
return c == "-"
|
||||
end,
|
||||
function(t,c)
|
||||
if t == "-" and c ~= "-" then return false end
|
||||
return c ~= "\n"
|
||||
end,
|
||||
function(t)
|
||||
return #t > 1
|
||||
end
|
||||
},
|
||||
{
|
||||
function(c)
|
||||
return c == "-"
|
||||
end,
|
||||
function(t,c)
|
||||
if t == "-" and c == "-" then return true
|
||||
elseif t == "--" and c == "[" then return true
|
||||
elseif t == "--[" and c == "=" and c == "[" then return true
|
||||
elseif t:match("^%-%-%[(=*)$") and c == "=" and c == "[" then
|
||||
return true
|
||||
end
|
||||
local eqs = t:match("^%-%-%[(=*)")
|
||||
if not eqs then
|
||||
return false
|
||||
else
|
||||
if #t == #eqs + 3 and c == "[" then return true end
|
||||
if t:sub(-(#eqs+2)) == "]"..eqs.."]" then
|
||||
return false
|
||||
else
|
||||
return true
|
||||
end
|
||||
end
|
||||
end,
|
||||
function(t)
|
||||
return #t > 3
|
||||
end
|
||||
}
|
||||
},
|
||||
string = {
|
||||
{
|
||||
function(c)
|
||||
return c == "'" or c == '"'
|
||||
end,
|
||||
function(t, c)
|
||||
local first = t:sub(1,1)
|
||||
local last = t:sub(#t)
|
||||
local penultimate = t:sub(-2, -2)
|
||||
if #t == 1 then return true end
|
||||
if first == last and penultimate ~= "\\" then return false end
|
||||
return true
|
||||
end
|
||||
},
|
||||
{
|
||||
function(c)
|
||||
return c == "["
|
||||
end,
|
||||
function(t,c)
|
||||
if t == "[" then
|
||||
return c == "=" or c == "["
|
||||
elseif t:match("^%[(=*)$") and (c == "=" or c == "[") then
|
||||
return true
|
||||
end
|
||||
local eqs = t:match("^%[(=*)")
|
||||
if not eqs then
|
||||
return false
|
||||
else
|
||||
if #t == #eqs + 3 and c == "[" then return true end
|
||||
if t:sub(-(#eqs+2)) == "]"..eqs.."]" then
|
||||
return false
|
||||
else
|
||||
return true
|
||||
end
|
||||
end
|
||||
end,
|
||||
function(t)
|
||||
return #t > 2
|
||||
end
|
||||
}
|
||||
},
|
||||
number = {
|
||||
{
|
||||
function(c)
|
||||
return not not tonumber(c)
|
||||
end,
|
||||
function(t, c)
|
||||
return not not tonumber(t .. c .. "0")
|
||||
end
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
local seen = {}
|
||||
local function add(k, v)
|
||||
if not v then return end
|
||||
if seen[v] then return end
|
||||
seen[v] = true
|
||||
for _k, _v in pairs(v) do
|
||||
syn.builtin[#syn.builtin+1] = char((k and k.."." or "").._k)
|
||||
if type(_v) == "table" then
|
||||
add((k and k.."." or "").._k, _v)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
add(nil, globalenv)
|
||||
|
||||
return syn
|
||||
36
data/computercraft/lua/rom/modules/main/rc/copy.lua
Normal file
36
data/computercraft/lua/rom/modules/main/rc/copy.lua
Normal file
@@ -0,0 +1,36 @@
|
||||
-- rc.copy - table copier
|
||||
|
||||
-- from https://lua-users.org/wiki/CopyTable
|
||||
local function deepcopy(orig, copies, dont_copy)
|
||||
copies = copies or {}
|
||||
local orig_type = type(orig)
|
||||
local copy
|
||||
|
||||
if orig_type == 'table' and not dont_copy[orig] then
|
||||
if copies[orig] then
|
||||
copy = copies[orig]
|
||||
else
|
||||
copy = {}
|
||||
copies[orig] = copy
|
||||
|
||||
for orig_key, orig_value in next, orig, nil do
|
||||
copy[deepcopy(orig_key, copies, dont_copy)] = deepcopy(orig_value,
|
||||
copies, dont_copy)
|
||||
end
|
||||
|
||||
setmetatable(copy, deepcopy(getmetatable(orig), copies, dont_copy))
|
||||
end
|
||||
else -- number, string, boolean, etc
|
||||
copy = orig
|
||||
end
|
||||
|
||||
return copy
|
||||
end
|
||||
|
||||
return { copy = function(original, ...)
|
||||
local dont_copy = {}
|
||||
for _, thing in pairs({...}) do
|
||||
dont_copy[thing] = true
|
||||
end
|
||||
return deepcopy(original, nil, dont_copy)
|
||||
end }
|
||||
231
data/computercraft/lua/rom/modules/main/rc/io.lua
Normal file
231
data/computercraft/lua/rom/modules/main/rc/io.lua
Normal file
@@ -0,0 +1,231 @@
|
||||
-- rc.io
|
||||
|
||||
local expect = require("cc.expect").expect
|
||||
local thread = require("rc.thread")
|
||||
local colors = require("colors")
|
||||
local term = require("term")
|
||||
local fs = require("fs")
|
||||
local rc = require("rc")
|
||||
|
||||
local io = {}
|
||||
|
||||
local _file = {}
|
||||
function _file:read(...)
|
||||
local args = table.pack(...)
|
||||
local ret = {}
|
||||
|
||||
if args.n == 0 then
|
||||
args[1] = "l"
|
||||
args.n = 1
|
||||
end
|
||||
|
||||
if not (self.handle.read and pcall(self.handle.read, 0)) then
|
||||
return nil, "bad file descriptor"
|
||||
end
|
||||
|
||||
if self.handle.flush then self.handle.flush() end
|
||||
|
||||
for i=1, args.n, 1 do
|
||||
local format = args[i]
|
||||
if format:sub(1,1) == "*" then
|
||||
format = format:sub(2)
|
||||
end
|
||||
|
||||
if format == "a" then
|
||||
ret[#ret+1] = self.handle.readAll()
|
||||
|
||||
elseif format == "l" or format == "L" then
|
||||
ret[#ret+1] = self.handle.readLine(format == "L")
|
||||
|
||||
elseif type(format) == "number" then
|
||||
ret[#ret+1] = self.handle.read(format)
|
||||
|
||||
else
|
||||
error("invalid format '"..format.."'", 2)
|
||||
end
|
||||
end
|
||||
|
||||
return table.unpack(ret, 1, args.n)
|
||||
end
|
||||
|
||||
function _file:lines(...)
|
||||
local formats = {...}
|
||||
if #formats == 0 then
|
||||
formats[1] = "l"
|
||||
end
|
||||
return function()
|
||||
return self:read(table.unpack(formats))
|
||||
end
|
||||
end
|
||||
|
||||
function _file:write(...)
|
||||
local args = table.pack(...)
|
||||
|
||||
if not (self.handle.write and pcall(self.handle.write, "")) then
|
||||
return nil, "bad file descriptor"
|
||||
end
|
||||
|
||||
for i=1, args.n, 1 do
|
||||
self.handle.write(args[i])
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function _file:seek(whence, offset)
|
||||
expect(1, whence, "string", "nil")
|
||||
expect(2, offset, "number", "nil")
|
||||
if self.handle.seek then
|
||||
return self.handle.seek(whence, offset)
|
||||
else
|
||||
return nil, "bad file descriptor"
|
||||
end
|
||||
end
|
||||
|
||||
function _file:flush()
|
||||
if self.handle.flush then self.handle.flush() end
|
||||
return self
|
||||
end
|
||||
|
||||
function _file:close()
|
||||
self.closed = true
|
||||
pcall(self.handle.close)
|
||||
end
|
||||
|
||||
local function iofile(handle)
|
||||
return setmetatable({handle = handle, closed = false}, {__index = _file})
|
||||
end
|
||||
|
||||
local stdin_rbuf = ""
|
||||
io.stdin = iofile {
|
||||
read = function(n)
|
||||
while #stdin_rbuf < n do
|
||||
stdin_rbuf = stdin_rbuf .. term.read() .. "\n"
|
||||
end
|
||||
local ret = stdin_rbuf:sub(1, n)
|
||||
stdin_rbuf = stdin_rbuf:sub(#ret+1)
|
||||
return ret
|
||||
end,
|
||||
|
||||
readLine = function(trail)
|
||||
local nl = stdin_rbuf:find("\n")
|
||||
|
||||
if nl then
|
||||
local ret = stdin_rbuf:sub(1, nl+1)
|
||||
if not trail then ret = ret:sub(1, -2) end
|
||||
stdin_rbuf = stdin_rbuf:sub(#ret+1)
|
||||
return ret
|
||||
|
||||
else
|
||||
return stdin_rbuf .. term.read() .. (trail and "\n" or "")
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
io.stdout = iofile {
|
||||
write = rc.write
|
||||
}
|
||||
|
||||
io.stderr = iofile {
|
||||
write = function(text)
|
||||
local old = term.getTextColor()
|
||||
term.setTextColor(colors.red)
|
||||
rc.write(text)
|
||||
term.setTextColor(old)
|
||||
end
|
||||
}
|
||||
|
||||
function io.open(file, mode)
|
||||
expect(1, file, "string")
|
||||
expect(2, mode, "string", "nil")
|
||||
|
||||
mode = (mode or "r"):match("[rwa]") .. "b"
|
||||
|
||||
local handle, err = fs.open(file, mode)
|
||||
if not handle then
|
||||
return nil, err
|
||||
end
|
||||
|
||||
return iofile(handle)
|
||||
end
|
||||
|
||||
function io.input(file)
|
||||
expect(1, file, "string", "table", "nil")
|
||||
local vars = thread.vars()
|
||||
if type(file) == "string" then file = assert(io.open(file, "r")) end
|
||||
if file then vars.input = file end
|
||||
return vars.input or io.stdin
|
||||
end
|
||||
|
||||
function io.output(file)
|
||||
expect(1, file, "string", "table", "nil")
|
||||
local vars = thread.vars()
|
||||
if type(file) == "string" then file = assert(io.open(file, "w")) end
|
||||
if file then vars.output = file end
|
||||
return vars.output or io.stdout
|
||||
end
|
||||
|
||||
function io.read(...)
|
||||
return io.input():read(...)
|
||||
end
|
||||
|
||||
function io.write(...)
|
||||
return io.output():write(...)
|
||||
end
|
||||
|
||||
function io.flush(file)
|
||||
expect(1, file, "table", "nil")
|
||||
return (file or io.output):flush()
|
||||
end
|
||||
|
||||
function io.close(file)
|
||||
expect(1, file, "table", "nil")
|
||||
return (file or io.output):close()
|
||||
end
|
||||
|
||||
function io.lines(file, ...)
|
||||
expect(1, file, "string", "nil")
|
||||
if file then file = assert(io.open(file, "r")) end
|
||||
local formats = table.pack(...)
|
||||
return (file or io.stdin):lines(table.unpack(formats, 1, formats.n))
|
||||
end
|
||||
|
||||
function io.type(obj)
|
||||
if type(obj) == "table" then
|
||||
local is_file = true
|
||||
for k, v in pairs(_file) do
|
||||
if (not obj[k]) or v ~= obj[k] then
|
||||
is_file = false
|
||||
end
|
||||
end
|
||||
|
||||
if is_file then
|
||||
return obj.closed and "closed file" or "file"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- loadfile and dofile here as well
|
||||
function _G.loadfile(file, mode, env)
|
||||
expect(1, file, "string")
|
||||
expect(2, mode, "string", "nil")
|
||||
expect(3, env, "table", "nil")
|
||||
local handle, err = io.open(file, "r")
|
||||
if not handle then
|
||||
return nil, file .. ": " .. err
|
||||
end
|
||||
local data = handle:read("a")
|
||||
handle:close()
|
||||
return load(data, "="..file, mode or "bt", env)
|
||||
end
|
||||
|
||||
function _G.dofile(file, ...)
|
||||
expect(1, file, "string")
|
||||
local func, err = loadfile(file)
|
||||
if not func then
|
||||
error(err)
|
||||
end
|
||||
return func(...)
|
||||
end
|
||||
|
||||
return io
|
||||
388
data/computercraft/lua/rom/modules/main/rc/json.lua
Normal file
388
data/computercraft/lua/rom/modules/main/rc/json.lua
Normal file
@@ -0,0 +1,388 @@
|
||||
--
|
||||
-- json.lua
|
||||
--
|
||||
-- Copyright (c) 2020 rxi
|
||||
--
|
||||
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
-- this software and associated documentation files (the "Software"), to deal in
|
||||
-- the Software without restriction, including without limitation the rights to
|
||||
-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
-- of the Software, and to permit persons to whom the Software is furnished to do
|
||||
-- so, subject to the following conditions:
|
||||
--
|
||||
-- The above copyright notice and this permission notice shall be included in all
|
||||
-- copies or substantial portions of the Software.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
-- SOFTWARE.
|
||||
--
|
||||
|
||||
local json = { _version = "0.1.2" }
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Encode
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
local encode
|
||||
|
||||
local escape_char_map = {
|
||||
[ "\\" ] = "\\",
|
||||
[ "\"" ] = "\"",
|
||||
[ "\b" ] = "b",
|
||||
[ "\f" ] = "f",
|
||||
[ "\n" ] = "n",
|
||||
[ "\r" ] = "r",
|
||||
[ "\t" ] = "t",
|
||||
}
|
||||
|
||||
local escape_char_map_inv = { [ "/" ] = "/" }
|
||||
for k, v in pairs(escape_char_map) do
|
||||
escape_char_map_inv[v] = k
|
||||
end
|
||||
|
||||
|
||||
local function escape_char(c)
|
||||
return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte()))
|
||||
end
|
||||
|
||||
|
||||
local function encode_nil(val)
|
||||
return "null"
|
||||
end
|
||||
|
||||
|
||||
local function encode_table(val, stack)
|
||||
local res = {}
|
||||
stack = stack or {}
|
||||
|
||||
-- Circular reference?
|
||||
if stack[val] then error("circular reference") end
|
||||
|
||||
stack[val] = true
|
||||
|
||||
if rawget(val, 1) ~= nil or next(val) == nil then
|
||||
-- Treat as array -- check keys are valid and it is not sparse
|
||||
local n = 0
|
||||
for k in pairs(val) do
|
||||
if type(k) ~= "number" then
|
||||
error("invalid table: mixed or invalid key types")
|
||||
end
|
||||
n = n + 1
|
||||
end
|
||||
if n ~= #val then
|
||||
error("invalid table: sparse array")
|
||||
end
|
||||
-- Encode
|
||||
for i, v in ipairs(val) do
|
||||
table.insert(res, encode(v, stack))
|
||||
end
|
||||
stack[val] = nil
|
||||
return "[" .. table.concat(res, ",") .. "]"
|
||||
|
||||
else
|
||||
-- Treat as an object
|
||||
for k, v in pairs(val) do
|
||||
if type(k) ~= "string" then
|
||||
error("invalid table: mixed or invalid key types")
|
||||
end
|
||||
table.insert(res, encode(k, stack) .. ":" .. encode(v, stack))
|
||||
end
|
||||
stack[val] = nil
|
||||
return "{" .. table.concat(res, ",") .. "}"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function encode_string(val)
|
||||
return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
|
||||
end
|
||||
|
||||
|
||||
local function encode_number(val)
|
||||
-- Check for NaN, -inf and inf
|
||||
if val ~= val or val <= -math.huge or val >= math.huge then
|
||||
error("unexpected number value '" .. tostring(val) .. "'")
|
||||
end
|
||||
return string.format("%.14g", val)
|
||||
end
|
||||
|
||||
|
||||
local type_func_map = {
|
||||
[ "nil" ] = encode_nil,
|
||||
[ "table" ] = encode_table,
|
||||
[ "string" ] = encode_string,
|
||||
[ "number" ] = encode_number,
|
||||
[ "boolean" ] = tostring,
|
||||
}
|
||||
|
||||
|
||||
encode = function(val, stack)
|
||||
local t = type(val)
|
||||
local f = type_func_map[t]
|
||||
if f then
|
||||
return f(val, stack)
|
||||
end
|
||||
error("unexpected type '" .. t .. "'")
|
||||
end
|
||||
|
||||
|
||||
function json.encode(val)
|
||||
return ( encode(val) )
|
||||
end
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Decode
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
local parse
|
||||
|
||||
local function create_set(...)
|
||||
local res = {}
|
||||
for i = 1, select("#", ...) do
|
||||
res[ select(i, ...) ] = true
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
local space_chars = create_set(" ", "\t", "\r", "\n")
|
||||
local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",")
|
||||
local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u")
|
||||
local literals = create_set("true", "false", "null")
|
||||
|
||||
local literal_map = {
|
||||
[ "true" ] = true,
|
||||
[ "false" ] = false,
|
||||
[ "null" ] = nil,
|
||||
}
|
||||
|
||||
|
||||
local function next_char(str, idx, set, negate)
|
||||
for i = idx, #str do
|
||||
if set[str:sub(i, i)] ~= negate then
|
||||
return i
|
||||
end
|
||||
end
|
||||
return #str + 1
|
||||
end
|
||||
|
||||
|
||||
local function decode_error(str, idx, msg)
|
||||
local line_count = 1
|
||||
local col_count = 1
|
||||
for i = 1, idx - 1 do
|
||||
col_count = col_count + 1
|
||||
if str:sub(i, i) == "\n" then
|
||||
line_count = line_count + 1
|
||||
col_count = 1
|
||||
end
|
||||
end
|
||||
error( string.format("%s at line %d col %d", msg, line_count, col_count) )
|
||||
end
|
||||
|
||||
|
||||
local function codepoint_to_utf8(n)
|
||||
-- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
|
||||
local f = math.floor
|
||||
if n <= 0x7f then
|
||||
return string.char(n)
|
||||
elseif n <= 0x7ff then
|
||||
return string.char(f(n / 64) + 192, n % 64 + 128)
|
||||
elseif n <= 0xffff then
|
||||
return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)
|
||||
elseif n <= 0x10ffff then
|
||||
return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,
|
||||
f(n % 4096 / 64) + 128, n % 64 + 128)
|
||||
end
|
||||
error( string.format("invalid unicode codepoint '%x'", n) )
|
||||
end
|
||||
|
||||
|
||||
local function parse_unicode_escape(s)
|
||||
local n1 = tonumber( s:sub(1, 4), 16 )
|
||||
local n2 = tonumber( s:sub(7, 10), 16 )
|
||||
-- Surrogate pair?
|
||||
if n2 then
|
||||
return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)
|
||||
else
|
||||
return codepoint_to_utf8(n1)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function parse_string(str, i)
|
||||
local res = ""
|
||||
local j = i + 1
|
||||
local k = j
|
||||
|
||||
while j <= #str do
|
||||
local x = str:byte(j)
|
||||
|
||||
if x < 32 then
|
||||
decode_error(str, j, "control character in string")
|
||||
|
||||
elseif x == 92 then -- `\`: Escape
|
||||
res = res .. str:sub(k, j - 1)
|
||||
j = j + 1
|
||||
local c = str:sub(j, j)
|
||||
if c == "u" then
|
||||
local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1)
|
||||
or str:match("^%x%x%x%x", j + 1)
|
||||
or decode_error(str, j - 1, "invalid unicode escape in string")
|
||||
res = res .. parse_unicode_escape(hex)
|
||||
j = j + #hex
|
||||
else
|
||||
if not escape_chars[c] then
|
||||
decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string")
|
||||
end
|
||||
res = res .. escape_char_map_inv[c]
|
||||
end
|
||||
k = j + 1
|
||||
|
||||
elseif x == 34 then -- `"`: End of string
|
||||
res = res .. str:sub(k, j - 1)
|
||||
return res, j + 1
|
||||
end
|
||||
|
||||
j = j + 1
|
||||
end
|
||||
|
||||
decode_error(str, i, "expected closing quote for string")
|
||||
end
|
||||
|
||||
|
||||
local function parse_number(str, i)
|
||||
local x = next_char(str, i, delim_chars)
|
||||
local s = str:sub(i, x - 1)
|
||||
local n = tonumber(s)
|
||||
if not n then
|
||||
decode_error(str, i, "invalid number '" .. s .. "'")
|
||||
end
|
||||
return n, x
|
||||
end
|
||||
|
||||
|
||||
local function parse_literal(str, i)
|
||||
local x = next_char(str, i, delim_chars)
|
||||
local word = str:sub(i, x - 1)
|
||||
if not literals[word] then
|
||||
decode_error(str, i, "invalid literal '" .. word .. "'")
|
||||
end
|
||||
return literal_map[word], x
|
||||
end
|
||||
|
||||
|
||||
local function parse_array(str, i)
|
||||
local res = {}
|
||||
local n = 1
|
||||
i = i + 1
|
||||
while 1 do
|
||||
local x
|
||||
i = next_char(str, i, space_chars, true)
|
||||
-- Empty / end of array?
|
||||
if str:sub(i, i) == "]" then
|
||||
i = i + 1
|
||||
break
|
||||
end
|
||||
-- Read token
|
||||
x, i = parse(str, i)
|
||||
res[n] = x
|
||||
n = n + 1
|
||||
-- Next token
|
||||
i = next_char(str, i, space_chars, true)
|
||||
local chr = str:sub(i, i)
|
||||
i = i + 1
|
||||
if chr == "]" then break end
|
||||
if chr ~= "," then decode_error(str, i, "expected ']' or ','") end
|
||||
end
|
||||
return res, i
|
||||
end
|
||||
|
||||
|
||||
local function parse_object(str, i)
|
||||
local res = {}
|
||||
i = i + 1
|
||||
while 1 do
|
||||
local key, val
|
||||
i = next_char(str, i, space_chars, true)
|
||||
-- Empty / end of object?
|
||||
if str:sub(i, i) == "}" then
|
||||
i = i + 1
|
||||
break
|
||||
end
|
||||
-- Read key
|
||||
if str:sub(i, i) ~= '"' then
|
||||
decode_error(str, i, "expected string for key")
|
||||
end
|
||||
key, i = parse(str, i)
|
||||
-- Read ':' delimiter
|
||||
i = next_char(str, i, space_chars, true)
|
||||
if str:sub(i, i) ~= ":" then
|
||||
decode_error(str, i, "expected ':' after key")
|
||||
end
|
||||
i = next_char(str, i + 1, space_chars, true)
|
||||
-- Read value
|
||||
val, i = parse(str, i)
|
||||
-- Set
|
||||
res[key] = val
|
||||
-- Next token
|
||||
i = next_char(str, i, space_chars, true)
|
||||
local chr = str:sub(i, i)
|
||||
i = i + 1
|
||||
if chr == "}" then break end
|
||||
if chr ~= "," then decode_error(str, i, "expected '}' or ','") end
|
||||
end
|
||||
return res, i
|
||||
end
|
||||
|
||||
|
||||
local char_func_map = {
|
||||
[ '"' ] = parse_string,
|
||||
[ "0" ] = parse_number,
|
||||
[ "1" ] = parse_number,
|
||||
[ "2" ] = parse_number,
|
||||
[ "3" ] = parse_number,
|
||||
[ "4" ] = parse_number,
|
||||
[ "5" ] = parse_number,
|
||||
[ "6" ] = parse_number,
|
||||
[ "7" ] = parse_number,
|
||||
[ "8" ] = parse_number,
|
||||
[ "9" ] = parse_number,
|
||||
[ "-" ] = parse_number,
|
||||
[ "t" ] = parse_literal,
|
||||
[ "f" ] = parse_literal,
|
||||
[ "n" ] = parse_literal,
|
||||
[ "[" ] = parse_array,
|
||||
[ "{" ] = parse_object,
|
||||
}
|
||||
|
||||
|
||||
parse = function(str, idx)
|
||||
local chr = str:sub(idx, idx)
|
||||
local f = char_func_map[chr]
|
||||
if f then
|
||||
return f(str, idx)
|
||||
end
|
||||
decode_error(str, idx, "unexpected character '" .. chr .. "'")
|
||||
end
|
||||
|
||||
|
||||
function json.decode(str)
|
||||
if type(str) ~= "string" then
|
||||
error("expected argument of type string, got " .. type(str))
|
||||
end
|
||||
local res, idx = parse(str, next_char(str, 1, space_chars, true))
|
||||
idx = next_char(str, idx, space_chars, true)
|
||||
if idx <= #str then
|
||||
decode_error(str, idx, "trailing garbage")
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
|
||||
return json
|
||||
398
data/computercraft/lua/rom/modules/main/rc/thread.lua
Normal file
398
data/computercraft/lua/rom/modules/main/rc/thread.lua
Normal file
@@ -0,0 +1,398 @@
|
||||
-- New scheduler.
|
||||
-- Tabs are integral to the design of this scheduler; Multishell cannot
|
||||
-- be disabled.
|
||||
|
||||
local rc = require("rc")
|
||||
local fs = require("fs")
|
||||
local keys = require("keys")
|
||||
local term = require("term")
|
||||
local colors = require("colors")
|
||||
local window = require("window")
|
||||
local expect = require("cc.expect")
|
||||
local copy = require("rc.copy").copy
|
||||
|
||||
local getfenv
|
||||
if rc.lua51 then
|
||||
getfenv = rc.lua51.getfenv
|
||||
else
|
||||
getfenv = function() return _ENV or _G end
|
||||
end
|
||||
|
||||
local tabs = { {} }
|
||||
local threads = {}
|
||||
local current, wrappedNative
|
||||
|
||||
local focused = 1
|
||||
|
||||
local api = {}
|
||||
|
||||
function api.launchTab(x, name)
|
||||
expect(1, x, "string", "function")
|
||||
name = expect(2, name, "string", "nil") or tostring(x)
|
||||
|
||||
local newTab = {
|
||||
term = window.create(wrappedNative, 1, 1, wrappedNative.getSize()),
|
||||
id = #tabs + 1
|
||||
}
|
||||
|
||||
tabs[newTab.id] = newTab
|
||||
|
||||
local _f = focused
|
||||
focused = newTab.id
|
||||
local id = (type(x) == "string" and api.load or api.spawn)(x, name)
|
||||
focused = _f
|
||||
|
||||
return newTab.id, id
|
||||
end
|
||||
|
||||
function api.setFocusedTab(f)
|
||||
expect(1, f, "number")
|
||||
if tabs[focused] then focused = f end
|
||||
return not not tabs[f]
|
||||
end
|
||||
|
||||
function api.getFocusedTab()
|
||||
return focused
|
||||
end
|
||||
|
||||
function api.getCurrentTab()
|
||||
return current.tab.id
|
||||
end
|
||||
|
||||
function api.load(file, name)
|
||||
expect(1, file, "string")
|
||||
name = expect(2, name, "string", "nil") or file
|
||||
|
||||
local env = copy(current and current.env or _ENV or _G, package.loaded)
|
||||
|
||||
local func, err = loadfile(file, "t", env)
|
||||
if not func then
|
||||
return nil, err
|
||||
end
|
||||
|
||||
return api.spawn(func, name, tabs[focused])
|
||||
end
|
||||
|
||||
function api.spawn(func, name, _)
|
||||
expect(1, func, "function")
|
||||
expect(2, name, "string")
|
||||
|
||||
local new = {
|
||||
name = name,
|
||||
coro = coroutine.create(function()
|
||||
assert(xpcall(func, debug.traceback))
|
||||
end),
|
||||
vars = setmetatable({}, {__index = current and current.vars}),
|
||||
env = getfenv(func) or _ENV or _G,
|
||||
tab = _ or tabs[focused],
|
||||
id = #threads + 1,
|
||||
dir = current and current.dir or "/"
|
||||
}
|
||||
|
||||
new.tab[new.id] = true
|
||||
threads[new.id] = new
|
||||
|
||||
new.tab.name = name
|
||||
|
||||
return new.id
|
||||
end
|
||||
|
||||
function api.exists(id)
|
||||
expect(1, id, "number")
|
||||
return not not threads[id]
|
||||
end
|
||||
|
||||
function api.id()
|
||||
return current.id
|
||||
end
|
||||
|
||||
function api.dir()
|
||||
return current.dir or "/"
|
||||
end
|
||||
|
||||
function api.setDir(dir)
|
||||
expect(1, dir, "string")
|
||||
|
||||
if not fs.exists(dir) then
|
||||
return nil, "that directory does not exist"
|
||||
|
||||
elseif not fs.isDir(dir) then
|
||||
return nil, "not a directory"
|
||||
end
|
||||
|
||||
current.dir = dir
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function api.vars()
|
||||
return current.vars
|
||||
end
|
||||
|
||||
function api.getTerm()
|
||||
return current and current.tab and current.tab.term or term.native()
|
||||
end
|
||||
|
||||
function api.setTerm(new)
|
||||
if tabs[focused] then
|
||||
local old = tabs[focused].term
|
||||
tabs[focused].term = new
|
||||
return old
|
||||
end
|
||||
end
|
||||
|
||||
local w, h
|
||||
local function getName(tab)
|
||||
local highest = 0
|
||||
|
||||
for k in pairs(tab) do
|
||||
if type(k) == "number" then highest = math.max(highest, k) end
|
||||
end
|
||||
|
||||
return threads[highest] and threads[highest].name or "???"
|
||||
end
|
||||
|
||||
function api.info()
|
||||
local running = {}
|
||||
for i, thread in pairs(threads) do
|
||||
running[#running+1] = { id = i, name = thread.name, tab = thread.tab.id }
|
||||
end
|
||||
|
||||
table.sort(running, function(a,b) return a.id < b.id end)
|
||||
|
||||
return running
|
||||
end
|
||||
|
||||
function api.remove(id)
|
||||
expect(1, id, "number", "nil")
|
||||
threads[id or current.id] = nil
|
||||
end
|
||||
|
||||
local scroll = 0
|
||||
local totalNameLength = 0
|
||||
local function redraw()
|
||||
w, h = wrappedNative.getSize()
|
||||
|
||||
wrappedNative.setVisible(false)
|
||||
|
||||
local names = {}
|
||||
totalNameLength = 0
|
||||
for i=1, #tabs, 1 do
|
||||
names[i] = " " .. getName(tabs[i]) .. " "
|
||||
totalNameLength = totalNameLength + #names[i]
|
||||
end
|
||||
|
||||
if #tabs > 1 then
|
||||
local len = -scroll + 1
|
||||
wrappedNative.setCursorPos(1, 1)
|
||||
wrappedNative.setTextColor(colors.black)
|
||||
wrappedNative.setBackgroundColor(colors.gray)
|
||||
wrappedNative.clearLine()
|
||||
|
||||
for i=1, #tabs, 1 do
|
||||
local tab = tabs[i]
|
||||
local name = names[i]
|
||||
|
||||
wrappedNative.setCursorPos(len, 1)
|
||||
len = len + #name
|
||||
|
||||
if i == focused then
|
||||
wrappedNative.setTextColor(colors.yellow)
|
||||
wrappedNative.setBackgroundColor(colors.black)
|
||||
wrappedNative.write(name)
|
||||
|
||||
else
|
||||
wrappedNative.setTextColor(colors.black)
|
||||
wrappedNative.setBackgroundColor(colors.gray)
|
||||
wrappedNative.write(name)
|
||||
end
|
||||
|
||||
tab.term.setVisible(false)
|
||||
tab.term.reposition(1, 2, w, h - 1)
|
||||
end
|
||||
|
||||
if totalNameLength > w-2 then
|
||||
wrappedNative.setTextColor(colors.black)
|
||||
wrappedNative.setBackgroundColor(colors.gray)
|
||||
if scroll > 0 then
|
||||
wrappedNative.setCursorPos(1, 1)
|
||||
wrappedNative.write("<")
|
||||
end
|
||||
if totalNameLength - scroll > w-1 then
|
||||
wrappedNative.setCursorPos(w, 1)
|
||||
wrappedNative.write(">")
|
||||
end
|
||||
end
|
||||
|
||||
tabs[focused].term.setVisible(true)
|
||||
|
||||
elseif #tabs > 0 then
|
||||
local tab = tabs[1]
|
||||
tab.term.reposition(1, 1, w, h)
|
||||
tab.term.setVisible(true)
|
||||
end
|
||||
|
||||
wrappedNative.setVisible(true)
|
||||
end
|
||||
|
||||
local inputEvents = {
|
||||
key = true,
|
||||
char = true,
|
||||
key_up = true,
|
||||
mouse_up = true,
|
||||
mouse_drag = true,
|
||||
mouse_click = true,
|
||||
mouse_scroll = true,
|
||||
terminate = true,
|
||||
}
|
||||
|
||||
local altIsDown
|
||||
|
||||
local function processEvent(event)
|
||||
if inputEvents[event[1]] then
|
||||
if #event > 3 then -- mouse event
|
||||
|
||||
if #tabs > 1 then
|
||||
if event[4] == 1 then
|
||||
local curX = -scroll
|
||||
|
||||
if event[1] == "mouse_scroll" then
|
||||
scroll = math.max(0, math.min(totalNameLength-w+1,
|
||||
scroll - event[2]))
|
||||
return false
|
||||
end
|
||||
|
||||
for i=1, #tabs, 1 do
|
||||
local tab = tabs[i]
|
||||
curX = curX + #getName(tab) + 2
|
||||
|
||||
if event[3] <= curX then
|
||||
focused = i
|
||||
redraw()
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
|
||||
else
|
||||
event[4] = event[4] - 1
|
||||
end
|
||||
end
|
||||
|
||||
elseif event[1] == "key" then
|
||||
if event[2] == keys.rightAlt then
|
||||
altIsDown = event[2]
|
||||
return false
|
||||
|
||||
elseif altIsDown then
|
||||
local num = tonumber(keys.getName(event[2]))
|
||||
if num then
|
||||
if tabs[num] then
|
||||
focused = num
|
||||
redraw()
|
||||
return false
|
||||
end
|
||||
|
||||
elseif event[2] == keys.left then
|
||||
focused = math.max(1, focused - 1)
|
||||
redraw()
|
||||
return false
|
||||
|
||||
elseif event[2] == keys.right then
|
||||
focused = math.min(#tabs, focused + 1)
|
||||
redraw()
|
||||
return false
|
||||
|
||||
elseif event[2] == keys.up then
|
||||
scroll = math.max(0, math.min(totalNameLength-w+1,
|
||||
scroll + 1))
|
||||
return false
|
||||
|
||||
elseif event[2] == keys.down then
|
||||
scroll = math.max(0, math.min(totalNameLength-w+1,
|
||||
scroll - 1))
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
elseif event[1] == "key_up" then
|
||||
if event[2] == keys.rightAlt then
|
||||
altIsDown = false
|
||||
return false
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local function cleanTabs()
|
||||
for t=#tabs, 1, -1 do
|
||||
local tab = tabs[t]
|
||||
|
||||
local count, removed = 0, 0
|
||||
for i in pairs(tab) do
|
||||
if type(i) == "number" then
|
||||
count = count + 1
|
||||
if not threads[i] then
|
||||
removed = removed + 1
|
||||
tab[i] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if count == removed then
|
||||
table.remove(tabs, t)
|
||||
end
|
||||
end
|
||||
|
||||
for i=1, #tabs, 1 do
|
||||
tabs[i].id = i
|
||||
end
|
||||
|
||||
focused = math.max(1, math.min(#tabs, focused))
|
||||
end
|
||||
|
||||
function api.start()
|
||||
api.start = nil
|
||||
|
||||
local _native = term.native()
|
||||
wrappedNative = window.create(_native, 1, 1, _native.getSize())
|
||||
api.launchTab("/rc/programs/shell.lua", "shell")
|
||||
|
||||
rc.pushEvent("init")
|
||||
|
||||
while #tabs > 0 and next(threads) do
|
||||
cleanTabs()
|
||||
redraw()
|
||||
local event = table.pack(coroutine.yield())
|
||||
|
||||
if event[1] == "term_resize" then
|
||||
wrappedNative.reposition(1, 1, _native.getSize())
|
||||
end
|
||||
|
||||
if processEvent(event) then
|
||||
for tid, thread in pairs(threads) do
|
||||
if thread.tab == tabs[focused] or not inputEvents[event[1]] then
|
||||
current = thread
|
||||
local result = table.pack(coroutine.resume(thread.coro,
|
||||
table.unpack(event, 1, event.n)))
|
||||
|
||||
if not result[1] then
|
||||
io.stderr:write(result[2].."\n")
|
||||
threads[tid] = nil
|
||||
|
||||
elseif coroutine.status(thread.coro) == "dead" then
|
||||
threads[tid] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
rc.shutdown()
|
||||
end
|
||||
|
||||
return api
|
||||
7
data/computercraft/lua/rom/programs/about.lua
Normal file
7
data/computercraft/lua/rom/programs/about.lua
Normal file
@@ -0,0 +1,7 @@
|
||||
-- about
|
||||
|
||||
local rc = require("rc")
|
||||
local colors = require("colors")
|
||||
local textutils = require("textutils")
|
||||
|
||||
textutils.coloredPrint(colors.yellow, rc.version() .. " on " .. _HOST)
|
||||
29
data/computercraft/lua/rom/programs/alias.lua
Normal file
29
data/computercraft/lua/rom/programs/alias.lua
Normal file
@@ -0,0 +1,29 @@
|
||||
-- alias
|
||||
|
||||
local args = {...}
|
||||
|
||||
local shell = require("shell")
|
||||
local colors = require("colors")
|
||||
local textutils = require("textutils")
|
||||
|
||||
if #args == 0 then
|
||||
textutils.coloredPrint(colors.yellow, "shell aliases", colors.white)
|
||||
|
||||
local aliases = shell.aliases()
|
||||
|
||||
local _aliases = {}
|
||||
for k, v in pairs(aliases) do
|
||||
table.insert(_aliases, {colors.cyan, k, colors.white, ":", v})
|
||||
end
|
||||
|
||||
textutils.pagedTabulate(_aliases)
|
||||
|
||||
elseif #args == 1 then
|
||||
shell.clearAlias(args[1])
|
||||
|
||||
elseif #args == 2 then
|
||||
shell.setAlias(args[1], args[2])
|
||||
|
||||
else
|
||||
error("this program takes a maximum of two arguments", 0)
|
||||
end
|
||||
19
data/computercraft/lua/rom/programs/bg.lua
Normal file
19
data/computercraft/lua/rom/programs/bg.lua
Normal file
@@ -0,0 +1,19 @@
|
||||
-- fg
|
||||
|
||||
local args = {...}
|
||||
|
||||
if #args == 0 then
|
||||
error("command not provided", 0)
|
||||
end
|
||||
|
||||
local shell = require("shell")
|
||||
local thread = require("rc.thread")
|
||||
|
||||
local path, err = shell.resolveProgram(args[1])
|
||||
if not path then
|
||||
error(err, 0)
|
||||
end
|
||||
|
||||
thread.launchTab(function()
|
||||
shell.exec(path, table.unpack(args, 2))
|
||||
end, args[1])
|
||||
1
data/computercraft/lua/rom/programs/clear.lua
Normal file
1
data/computercraft/lua/rom/programs/clear.lua
Normal file
@@ -0,0 +1 @@
|
||||
require("term").at(1,1).clear()
|
||||
36
data/computercraft/lua/rom/programs/copy.lua
Normal file
36
data/computercraft/lua/rom/programs/copy.lua
Normal file
@@ -0,0 +1,36 @@
|
||||
local fs = require("fs")
|
||||
local shell = require("shell")
|
||||
|
||||
local args = {...}
|
||||
|
||||
if #args < 2 then
|
||||
io.stderr:write("usage: copy <source> <destination>\n")
|
||||
return
|
||||
end
|
||||
|
||||
local source, destination = shell.resolve(args[1]), shell.resolve(args[2])
|
||||
local files = fs.find(source)
|
||||
|
||||
if #files > 0 then
|
||||
local dir = fs.isDir(destination)
|
||||
|
||||
if #files > 1 and not dir then
|
||||
io.stderr:write("destination must be a directory\n")
|
||||
return
|
||||
end
|
||||
|
||||
for i=1, #files, 1 do
|
||||
if dir then
|
||||
fs.copy(files[i], fs.combine(destination, fs.getName(files[i])))
|
||||
elseif #files == 1 then
|
||||
if fs.exists(destination) then
|
||||
io.stderr:write("file already exists\n")
|
||||
return
|
||||
else
|
||||
fs.copy(files[i], destination)
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
io.stderr:write("no such file(s)\n")
|
||||
end
|
||||
58
data/computercraft/lua/rom/programs/craftos.lua
Normal file
58
data/computercraft/lua/rom/programs/craftos.lua
Normal file
@@ -0,0 +1,58 @@
|
||||
-- LeonOS compatibility, in theory
|
||||
|
||||
local rc = require("rc")
|
||||
local settings = require("settings")
|
||||
|
||||
if not settings.get("bios.compat_mode") then
|
||||
error("compatibility mode is disabled", 0)
|
||||
end
|
||||
|
||||
if os.version then
|
||||
error("you are already in compatibility mode", 0)
|
||||
end
|
||||
|
||||
local libs = {
|
||||
"peripheral", "fs", "settings", "http", "term", "colors", "multishell",
|
||||
"keys", "parallel", "shell", "textutils", "window", "paintutils"
|
||||
}
|
||||
|
||||
local move = {
|
||||
"queueEvent", "startTimer", "cancelTimer", "setAlarm", "cancelAlarm", "getComputerID",
|
||||
"computerID", "getComputerLabel", "setComputerLabel", "computerLabel", "day", "epoch",
|
||||
"pullEvent", "sleep"
|
||||
}
|
||||
|
||||
local nEnv = setmetatable({}, {__index=_G})
|
||||
nEnv.os = setmetatable({}, {__index=os})
|
||||
|
||||
for i=1, #libs, 1 do
|
||||
nEnv[libs[i]] = select(2, pcall(require, libs[i]))
|
||||
end
|
||||
|
||||
for i=1, #move do
|
||||
nEnv.os[move[i]] = rc[move[i]]
|
||||
end
|
||||
|
||||
function nEnv.printError(text)
|
||||
io.stderr:write(text, "\n")
|
||||
end
|
||||
|
||||
nEnv.write = rc.write
|
||||
|
||||
nEnv.unpack = table.unpack
|
||||
if rc.lua51 then
|
||||
for k, v in pairs(rc.lua51) do
|
||||
nEnv[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
nEnv.read = nEnv.term.read
|
||||
nEnv.sleep = nEnv.os.sleep
|
||||
|
||||
function nEnv.os.version()
|
||||
return "LeonOS 1.0 ALpha 1"
|
||||
end
|
||||
|
||||
local func, err = loadfile("/rc/programs/shell.lua", "t", nEnv)
|
||||
if not func then error(err, 0) end
|
||||
func()
|
||||
24
data/computercraft/lua/rom/programs/delete.lua
Normal file
24
data/computercraft/lua/rom/programs/delete.lua
Normal file
@@ -0,0 +1,24 @@
|
||||
local fs = require("fs")
|
||||
local shell = require("shell")
|
||||
|
||||
local args = {...}
|
||||
if #args == 0 then
|
||||
io.stderr:write("usage: delete <paths>\n")
|
||||
return
|
||||
end
|
||||
|
||||
for i=1, #args, 1 do
|
||||
local files = fs.find(shell.resolve(args[i]))
|
||||
if not files then
|
||||
io.stderr:write("file(s) not found\n")
|
||||
return
|
||||
end
|
||||
|
||||
for n=1, #files, 1 do
|
||||
if fs.isReadOnly(files[n]) then
|
||||
io.stderr:write(files[n] .. ": cannot remove read-only file\n")
|
||||
return
|
||||
end
|
||||
fs.delete(files[n])
|
||||
end
|
||||
end
|
||||
89
data/computercraft/lua/rom/programs/devbin.lua
Normal file
89
data/computercraft/lua/rom/programs/devbin.lua
Normal file
@@ -0,0 +1,89 @@
|
||||
-- devbin (like pastebin)
|
||||
|
||||
if not package.loaded.http then
|
||||
error("HTTP is not enabled in the ComputerCraft configuration", 0)
|
||||
end
|
||||
|
||||
local http = require("http")
|
||||
local json = require("rc.json")
|
||||
local shell = require("shell")
|
||||
|
||||
local args = {...}
|
||||
|
||||
if #args < (args[1] == "get" and 3 or 2) then
|
||||
io.stderr:write([[
|
||||
Usage:
|
||||
devbin put <filename>
|
||||
devbin get <code> <filename>
|
||||
devbin run <code> [argument ...]
|
||||
]])
|
||||
return
|
||||
end
|
||||
|
||||
local paste = "https://devbin.dev/api/v3/paste"
|
||||
local key = "aNVXl8vxYGWcZGvMnuJTzLXH53mGWOuQtBXU025g8YDAsZDu"
|
||||
|
||||
local function get(code)
|
||||
local handle, err, rerr = http.get("https://devbin.dev/raw/"..code)
|
||||
if not handle then
|
||||
if rerr then rerr.close() end
|
||||
error(err, 0)
|
||||
end
|
||||
|
||||
local data = handle.readAll()
|
||||
handle.close()
|
||||
|
||||
return data
|
||||
end
|
||||
|
||||
if args[1] == "put" then
|
||||
local handle, err = io.open(shell.resolve(args[2]), "r")
|
||||
if not handle then error(err, 0) end
|
||||
local data = handle:read("a")
|
||||
handle:close()
|
||||
|
||||
if (not data) or #data == 0 then
|
||||
error("cannot 'put' empty file", 0)
|
||||
end
|
||||
|
||||
local request = json.encode({
|
||||
title = args[2],
|
||||
syntaxName = "lua",
|
||||
exposure = 0,
|
||||
content = data,
|
||||
asGuest = true
|
||||
})
|
||||
|
||||
local response, rerr, rerr2 = http.post(paste, request,
|
||||
{["Content-Type"]="application/json", Authorization = key}, true)
|
||||
if not response then
|
||||
local _rerr2 = rerr2.readAll()
|
||||
if rerr2 then rerr2.close() end
|
||||
print(_rerr2)
|
||||
error(rerr, 0)
|
||||
--("%q: %q"):format(rerr, (rerr2 and rerr2.readAll()) or ""), 0)
|
||||
end
|
||||
|
||||
local rdata = response.readAll()
|
||||
|
||||
local code, message = response.getResponseCode()
|
||||
response.close()
|
||||
if code ~= 201 then
|
||||
error(code .. " " .. (message or ""), 0)
|
||||
end
|
||||
|
||||
local decoded = json.decode(rdata)
|
||||
|
||||
print(decoded.code)
|
||||
|
||||
elseif args[1] == "get" then
|
||||
local data = get(args[2])
|
||||
local handle, err = io.open(shell.resolve(args[3]), "w")
|
||||
if not handle then error(err, 0) end
|
||||
handle:write(data)
|
||||
handle:close()
|
||||
|
||||
elseif args[1] == "run" then
|
||||
local data = get(args[2])
|
||||
assert(load(data, "=<devbin-run>", "t", _G))()
|
||||
end
|
||||
12
data/computercraft/lua/rom/programs/edit.lua
Normal file
12
data/computercraft/lua/rom/programs/edit.lua
Normal file
@@ -0,0 +1,12 @@
|
||||
-- launch different editors based on computer capabilities
|
||||
|
||||
local term = require("term")
|
||||
local settings = require("settings")
|
||||
|
||||
local df = function(f, ...) return assert(loadfile(f))(...) end
|
||||
|
||||
if term.isColor() or settings.get("edit.force_highlight") then
|
||||
df("/rc/editors/advanced.lua", ...)
|
||||
else
|
||||
df("/rc/editors/basic.lua", ...)
|
||||
end
|
||||
19
data/computercraft/lua/rom/programs/fg.lua
Normal file
19
data/computercraft/lua/rom/programs/fg.lua
Normal file
@@ -0,0 +1,19 @@
|
||||
-- fg
|
||||
|
||||
local args = {...}
|
||||
|
||||
if #args == 0 then
|
||||
error("command not provided", 0)
|
||||
end
|
||||
|
||||
local shell = require("shell")
|
||||
local thread = require("rc.thread")
|
||||
|
||||
local path, err = shell.resolveProgram(args[1])
|
||||
if not path then
|
||||
error(err, 0)
|
||||
end
|
||||
|
||||
thread.setFocus((thread.launchTab(function()
|
||||
shell.exec(path, table.unpack(args, 2))
|
||||
end, args[1])))
|
||||
25
data/computercraft/lua/rom/programs/help.lua
Normal file
25
data/computercraft/lua/rom/programs/help.lua
Normal file
@@ -0,0 +1,25 @@
|
||||
-- help
|
||||
|
||||
local help = require("help")
|
||||
local textutils = require("textutils")
|
||||
|
||||
local args = {...}
|
||||
|
||||
if #args == 0 then
|
||||
args[1] = "help"
|
||||
end
|
||||
|
||||
local function view(name)--path)
|
||||
textutils.coloredPagedPrint(table.unpack(help.loadTopic(name)))
|
||||
--local lines = {}
|
||||
--for l in io.lines(path) do lines[#lines+1] = l end
|
||||
--textutils.pagedPrint(table.concat(require("cc.strings").wrap(table.concat(lines,"\n"), require("term").getSize()), "\n"))
|
||||
end
|
||||
|
||||
for i=1, #args, 1 do
|
||||
local path = help.lookup(args[i])
|
||||
if not path then
|
||||
error("No help topic for " .. args[i], 0)
|
||||
end
|
||||
view(args[i])--path)
|
||||
end
|
||||
46
data/computercraft/lua/rom/programs/list.lua
Normal file
46
data/computercraft/lua/rom/programs/list.lua
Normal file
@@ -0,0 +1,46 @@
|
||||
-- list
|
||||
|
||||
local args = {...}
|
||||
|
||||
local fs = require("fs")
|
||||
local shell = require("shell")
|
||||
local colors = require("colors")
|
||||
local settings = require("settings")
|
||||
local textutils = require("textutils")
|
||||
|
||||
if #args == 0 then args[1] = shell.dir() end
|
||||
|
||||
local show_hidden = settings.get("list.show_hidden")
|
||||
|
||||
local function list_dir(dir)
|
||||
if not fs.exists(dir) then
|
||||
error(dir .. ": that directory does not exist", 0)
|
||||
elseif not fs.isDir(dir) then
|
||||
error(dir .. ": not a directory", 0)
|
||||
end
|
||||
|
||||
local raw_files = fs.list(dir)
|
||||
local files, dirs = {}, {}
|
||||
|
||||
for i=1, #raw_files, 1 do
|
||||
local full = fs.combine(dir, raw_files[i])
|
||||
|
||||
if raw_files[i]:sub(1,1) ~= "." or show_hidden then
|
||||
if fs.isDir(full) then
|
||||
dirs[#dirs+1] = raw_files[i]
|
||||
|
||||
else
|
||||
files[#files+1] = raw_files[i]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
textutils.pagedTabulate(colors.green, dirs, colors.white, files)
|
||||
end
|
||||
|
||||
for i=1, #args, 1 do
|
||||
if #args > 1 then
|
||||
textutils.coloredPrint(colors.yellow, args[i]..":\n", colors.white)
|
||||
end
|
||||
list_dir(args[i])
|
||||
end
|
||||
46
data/computercraft/lua/rom/programs/lua.lua
Normal file
46
data/computercraft/lua/rom/programs/lua.lua
Normal file
@@ -0,0 +1,46 @@
|
||||
-- lua REPL
|
||||
|
||||
local term = require("term")
|
||||
local copy = require("rc.copy").copy
|
||||
local colors = require("colors")
|
||||
local pretty = require("cc.pretty")
|
||||
local textutils = require("textutils")
|
||||
|
||||
local env = copy(_ENV, package.loaded)
|
||||
|
||||
local run = true
|
||||
function env.exit() run = false end
|
||||
|
||||
term.setTextColor(colors.yellow)
|
||||
|
||||
print("LeonOS Lua REPL.\nCall exit() to exit.")
|
||||
|
||||
local history = {}
|
||||
while run do
|
||||
term.setTextColor(colors.white)
|
||||
io.write("$ lua >>> ")
|
||||
local data = term.read(nil, history, function(text)
|
||||
return textutils.complete(text, env)
|
||||
end)
|
||||
if #data > 0 then
|
||||
history[#history+1] = data
|
||||
end
|
||||
|
||||
local ok, err = load("return " .. data, "=stdin", "t", env)
|
||||
if not ok then
|
||||
ok, err = load(data, "=stdin", "t", env)
|
||||
end
|
||||
|
||||
if ok then
|
||||
local result = table.pack(pcall(ok))
|
||||
if not result[1] then
|
||||
io.stderr:write(result[2], "\n")
|
||||
elseif result.n > 1 then
|
||||
for i=2, result.n, 1 do
|
||||
pretty.pretty_print(result[i])
|
||||
end
|
||||
end
|
||||
else
|
||||
io.stderr:write(err, "\n")
|
||||
end
|
||||
end
|
||||
12
data/computercraft/lua/rom/programs/mkdir.lua
Normal file
12
data/computercraft/lua/rom/programs/mkdir.lua
Normal file
@@ -0,0 +1,12 @@
|
||||
local fs = require("fs")
|
||||
local shell = require("shell")
|
||||
local args = {...}
|
||||
|
||||
if #args == 0 then
|
||||
io.stderr:write("usage: mkdir <paths>\n")
|
||||
return
|
||||
end
|
||||
|
||||
for i=1, #args, 1 do
|
||||
fs.makeDir(shell.resolve(args[i]))
|
||||
end
|
||||
36
data/computercraft/lua/rom/programs/move.lua
Normal file
36
data/computercraft/lua/rom/programs/move.lua
Normal file
@@ -0,0 +1,36 @@
|
||||
local fs = require("fs")
|
||||
local shell = require("shell")
|
||||
|
||||
local args = {...}
|
||||
|
||||
if #args < 2 then
|
||||
io.stderr:write("usage: move <source> <destination>\n")
|
||||
return
|
||||
end
|
||||
|
||||
local source, destination = shell.resolve(args[1]), shell.resolve(args[2])
|
||||
local files = fs.find(source)
|
||||
|
||||
if #files > 0 then
|
||||
local dir = fs.isDir(destination)
|
||||
|
||||
if #files > 1 and not dir then
|
||||
io.stderr:write("destination must be a directory\n")
|
||||
return
|
||||
end
|
||||
|
||||
for i=1, #files, 1 do
|
||||
if dir then
|
||||
fs.move(files[i], fs.combine(destination, fs.getName(files[i])))
|
||||
elseif #files == 1 then
|
||||
if fs.exists(destination) then
|
||||
io.stderr:write("file already exists\n")
|
||||
return
|
||||
else
|
||||
fs.move(files[i], destination)
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
io.stderr:write("no such file(s)\n")
|
||||
end
|
||||
3
data/computercraft/lua/rom/programs/paint.lua
Normal file
3
data/computercraft/lua/rom/programs/paint.lua
Normal file
@@ -0,0 +1,3 @@
|
||||
-- BIMG editor
|
||||
|
||||
local term = require("term")
|
||||
16
data/computercraft/lua/rom/programs/peripherals.lua
Normal file
16
data/computercraft/lua/rom/programs/peripherals.lua
Normal file
@@ -0,0 +1,16 @@
|
||||
local term = require("term")
|
||||
local colors = require("colors")
|
||||
local peripheral = require("peripheral")
|
||||
|
||||
term.setTextColor(colors.yellow)
|
||||
print("attached peripherals")
|
||||
term.setTextColor(colors.white)
|
||||
local names = peripheral.getNames()
|
||||
|
||||
if #names == 0 then
|
||||
io.stderr:write("none\n")
|
||||
else
|
||||
for i=1, #names, 1 do
|
||||
print(string.format("%s (%s)", names[i], peripheral.getType(names[i])))
|
||||
end
|
||||
end
|
||||
6
data/computercraft/lua/rom/programs/programs.lua
Normal file
6
data/computercraft/lua/rom/programs/programs.lua
Normal file
@@ -0,0 +1,6 @@
|
||||
local shell = require("shell")
|
||||
local colors = require("colors")
|
||||
local textutils = require("textutils")
|
||||
|
||||
textutils.coloredPrint(colors.yellow, "available programs\n", colors.white)
|
||||
textutils.pagedTabulate(shell.programs())
|
||||
10
data/computercraft/lua/rom/programs/reboot.lua
Normal file
10
data/computercraft/lua/rom/programs/reboot.lua
Normal file
@@ -0,0 +1,10 @@
|
||||
local rc = require("rc")
|
||||
local term = require("term")
|
||||
local colors = require("colors")
|
||||
|
||||
term.setTextColor(colors.yellow)
|
||||
print("Restarting")
|
||||
|
||||
if (...) ~= "now" then rc.sleep(1) end
|
||||
|
||||
rc.reboot()
|
||||
88
data/computercraft/lua/rom/programs/redstone.lua
Normal file
88
data/computercraft/lua/rom/programs/redstone.lua
Normal file
@@ -0,0 +1,88 @@
|
||||
local rc = require("rc")
|
||||
local rs = require("redstone")
|
||||
local colors = require("colors")
|
||||
local textutils = require("textutils")
|
||||
|
||||
local args = {...}
|
||||
|
||||
local commands = {}
|
||||
|
||||
local sides = {"top", "bottom", "left", "right", "front", "back"}
|
||||
|
||||
function commands.probe()
|
||||
textutils.coloredPrint(colors.yellow, "redstone inputs", colors.white)
|
||||
local inputs = {}
|
||||
for i=1, #sides, 1 do
|
||||
if rs.getInput(sides[i]) then
|
||||
inputs[#inputs+1] = sides[i]
|
||||
end
|
||||
end
|
||||
if #inputs == 0 then inputs[1] = "None" end
|
||||
print(table.concat(inputs, ", "))
|
||||
return true
|
||||
end
|
||||
|
||||
local function coerce(value)
|
||||
if value == "true" then return true end
|
||||
if value == "false" then return false end
|
||||
return tonumber(value) or value
|
||||
end
|
||||
|
||||
function commands.set(side, color, value)
|
||||
if not side then
|
||||
io.stderr:write("side expected\n")
|
||||
return true
|
||||
end
|
||||
|
||||
if not value then
|
||||
value = color
|
||||
color = nil
|
||||
end
|
||||
|
||||
value = coerce(value)
|
||||
if type(value) == "string" then
|
||||
io.stderr:write("value must be boolean or 0-15\n")
|
||||
end
|
||||
|
||||
if color then
|
||||
color = coerce(color)
|
||||
if type(value) == "number" then
|
||||
io.stderr:write("value must be boolean\n")
|
||||
end
|
||||
|
||||
if not colors[color] then
|
||||
io.stderr:write("color not defined\n")
|
||||
end
|
||||
|
||||
rs.setBundledOutput(side, colors[color], value)
|
||||
|
||||
elseif type(value) == "boolean" then
|
||||
rs.setOutput(side, value)
|
||||
|
||||
else
|
||||
rs.setAnalogOutput(side, value)
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function commands.pulse(side, count, period)
|
||||
count = tonumber(count) or 1
|
||||
period = tonumber(period) or 0.5
|
||||
|
||||
for _=1, count, 1 do
|
||||
rs.setOutput(side, true)
|
||||
rc.sleep(period / 2)
|
||||
rs.setOutput(side, false)
|
||||
rc.sleep(period / 2)
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
if not (args[1] and commands[args[1]] and
|
||||
commands[args[1]](table.unpack(args, 2))) then
|
||||
io.stderr:write("Usages:\nredstone probe\n"..
|
||||
"redstone set <side> <value>\nredstone set <side> <color> <value>\n"..
|
||||
"redstone pulse <side> <count> <period>\n")
|
||||
end
|
||||
47
data/computercraft/lua/rom/programs/set.lua
Normal file
47
data/computercraft/lua/rom/programs/set.lua
Normal file
@@ -0,0 +1,47 @@
|
||||
-- 'set' program
|
||||
|
||||
local colors = require("colors")
|
||||
local settings = require("settings")
|
||||
local textutils = require("textutils")
|
||||
|
||||
local args = {...}
|
||||
|
||||
local function coerce(val)
|
||||
if val == "nil" then
|
||||
return nil
|
||||
elseif val == "false" then
|
||||
return false
|
||||
elseif val == "true" then
|
||||
return true
|
||||
else
|
||||
return tonumber(val) or val
|
||||
end
|
||||
end
|
||||
|
||||
local col = {
|
||||
string = colors.red,
|
||||
table = colors.green,
|
||||
number = colors.magenta,
|
||||
boolean = colors.lightGray
|
||||
}
|
||||
|
||||
if #args == 0 then
|
||||
for _, setting in ipairs(settings.getNames()) do
|
||||
local value = settings.get(setting)
|
||||
textutils.coloredPrint(colors.cyan, setting, colors.white, " is ",
|
||||
col[type(value)], string.format("%q", value))
|
||||
end
|
||||
elseif #args == 1 then
|
||||
local setting = args[1]
|
||||
local value = settings.get(setting)
|
||||
textutils.coloredPrint(colors.cyan, setting, colors.white, " is ",
|
||||
col[type(value)], string.format("%q", value))
|
||||
local def = settings.getDetails(setting)
|
||||
if def then
|
||||
print(def.description)
|
||||
end
|
||||
else
|
||||
local setting, value = args[1], args[2]
|
||||
settings.set(setting, coerce(value))
|
||||
settings.save()
|
||||
end
|
||||
79
data/computercraft/lua/rom/programs/shell.lua
Normal file
79
data/computercraft/lua/rom/programs/shell.lua
Normal file
@@ -0,0 +1,79 @@
|
||||
-- rc.shell
|
||||
|
||||
local rc = require("rc")
|
||||
local fs = require("fs")
|
||||
local term = require("term")
|
||||
local shell = require("shell")
|
||||
local colors = require("colors")
|
||||
local thread = require("rc.thread")
|
||||
local textutils = require("textutils")
|
||||
|
||||
if os.version then
|
||||
textutils.coloredPrint(colors.yellow, os.version(), colors.white)
|
||||
else
|
||||
textutils.coloredPrint(colors.yellow, rc.version(), colors.white)
|
||||
end
|
||||
|
||||
thread.vars().parentShell = thread.id()
|
||||
shell.init(_ENV)
|
||||
|
||||
if not shell.__has_run_startup then
|
||||
shell.__has_run_startup = true
|
||||
if fs.exists("/startup.lua") then
|
||||
local ok, err = pcall(dofile, "/startup.lua")
|
||||
if not ok and err then
|
||||
io.stderr:write(err, "\n")
|
||||
end
|
||||
end
|
||||
|
||||
if fs.exists("/startup") and fs.isDir("/startup") then
|
||||
local files = fs.list("/startup/")
|
||||
table.sort(files)
|
||||
|
||||
for f=1, #files, 1 do
|
||||
local ok, err = pcall(dofile, "/startup/"..files[f])
|
||||
if not ok and err then
|
||||
io.stderr:write(err, "\n")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local aliases = {
|
||||
background = "bg",
|
||||
clr = "clear",
|
||||
cp = "copy",
|
||||
dir = "list",
|
||||
foreground = "fg",
|
||||
mv = "move",
|
||||
rm = "delete",
|
||||
rs = "redstone",
|
||||
sh = "shell",
|
||||
ps = "threads"
|
||||
}
|
||||
|
||||
for k, v in pairs(aliases) do
|
||||
shell.setAlias(k, v)
|
||||
end
|
||||
|
||||
local completions = "/rc/completions"
|
||||
for _, prog in ipairs(fs.list(completions)) do
|
||||
dofile(fs.combine(completions, prog))
|
||||
end
|
||||
|
||||
local history = {}
|
||||
while true do
|
||||
term.setTextColor(colors.yellow)
|
||||
term.setBackgroundColor(colors.black)
|
||||
rc.write(colors.yellow.."$"..shell.dir()..colors.green.." >>> ")
|
||||
term.setTextColor(colors.white)
|
||||
|
||||
local text = term.read(nil, history, shell.complete)
|
||||
if #text > 0 then
|
||||
history[#history+1] = text
|
||||
local ok, err = shell.run(text)
|
||||
if not ok and err then
|
||||
io.stderr:write(err, "\n")
|
||||
end
|
||||
end
|
||||
end
|
||||
10
data/computercraft/lua/rom/programs/shutdown.lua
Normal file
10
data/computercraft/lua/rom/programs/shutdown.lua
Normal file
@@ -0,0 +1,10 @@
|
||||
local rc = require("rc")
|
||||
local term = require("term")
|
||||
local colors = require("colors")
|
||||
|
||||
term.setTextColor(colors.yellow)
|
||||
print("Shutting down")
|
||||
|
||||
if (...) ~= "now" then rc.sleep(1) end
|
||||
|
||||
rc.shutdown()
|
||||
16
data/computercraft/lua/rom/programs/threads.lua
Normal file
16
data/computercraft/lua/rom/programs/threads.lua
Normal file
@@ -0,0 +1,16 @@
|
||||
-- threads
|
||||
|
||||
local colors = require("colors")
|
||||
local thread = require("rc.thread")
|
||||
local strings = require("cc.strings")
|
||||
local textutils = require("textutils")
|
||||
|
||||
textutils.coloredPrint(colors.yellow, "id tab name", colors.white)
|
||||
|
||||
local info = thread.info()
|
||||
for i=1, #info, 1 do
|
||||
local inf = info[i]
|
||||
textutils.pagedPrint(string.format("%s %s %s",
|
||||
strings.ensure_width(tostring(inf.id), 4),
|
||||
strings.ensure_width(tostring(inf.tab), 4), inf.name))
|
||||
end
|
||||
44
data/computercraft/lua/rom/programs/wget.lua
Normal file
44
data/computercraft/lua/rom/programs/wget.lua
Normal file
@@ -0,0 +1,44 @@
|
||||
-- wget
|
||||
|
||||
if not package.loaded.http then
|
||||
error("HTTP is not enabled in the ComputerCraft configuration", 0)
|
||||
end
|
||||
|
||||
local http = require("http")
|
||||
|
||||
local args = {...}
|
||||
|
||||
if #args == 0 then
|
||||
io.stderr:write([[Usage:
|
||||
wget <url> [filename]
|
||||
wget run <url>
|
||||
]])
|
||||
return
|
||||
end
|
||||
|
||||
local function get(url)
|
||||
local handle, err = http.get(url, nil, true)
|
||||
if not handle then
|
||||
error(err, 0)
|
||||
end
|
||||
|
||||
local data = handle.readAll()
|
||||
handle.close()
|
||||
|
||||
return data
|
||||
end
|
||||
|
||||
if args[1] == "run" then
|
||||
local data = get(args[2])
|
||||
assert(load(data, "=<wget-run>", "t", _G))()
|
||||
else
|
||||
local filename = args[2] or (args[1]:match("[^/]+$")) or
|
||||
error("could not determine file name", 0)
|
||||
local data = get(args[1])
|
||||
local handle, err = io.open(filename, "w")
|
||||
if not handle then
|
||||
error(err, 0)
|
||||
end
|
||||
handle:write(data)
|
||||
handle:close()
|
||||
end
|
||||
201
data/computercraft/lua/rom/startup/00_fs.lua
Normal file
201
data/computercraft/lua/rom/startup/00_fs.lua
Normal file
@@ -0,0 +1,201 @@
|
||||
-- override the fs library to use this resolution function where necessary
|
||||
-- almost identical to the override used in .OS
|
||||
|
||||
local fs = rawget(_G, "fs")
|
||||
|
||||
-- split a file path into segments
|
||||
function fs.split(path)
|
||||
local s = {}
|
||||
for S in path:gmatch("[^/\\]+") do
|
||||
if S == ".." then
|
||||
s[#s] = nil
|
||||
elseif S ~= "." then
|
||||
s[#s+1] = S
|
||||
end
|
||||
end
|
||||
return s
|
||||
end
|
||||
|
||||
-- package isn't loaded yet, so unfortunately this is necessary
|
||||
local function expect(...)
|
||||
return require and require("cc.expect").expect(...)
|
||||
end
|
||||
|
||||
-- path resolution:
|
||||
-- if the path begins with /rc, then redirect to wherever that actually
|
||||
-- is; otherwise, resolve the path based on the current program's working
|
||||
-- directory
|
||||
-- this is to allow .OS to run from anywhere
|
||||
local function resolve(path)
|
||||
local thread = package and package.loaded.thread
|
||||
|
||||
local root = (thread and thread.getroot()) or "/"
|
||||
local pwd = (thread and thread.dir()) or "/"
|
||||
|
||||
if path:sub(1,1) ~= "/" then
|
||||
path = fs.combine(pwd, path)
|
||||
end
|
||||
path = fs.combine(root, path)
|
||||
|
||||
local segments = fs.split(path)
|
||||
if segments[1] == "rc" then
|
||||
return fs.combine(_RC_ROM_DIR, table.concat(segments, "/", 2, #segments))
|
||||
else
|
||||
return path
|
||||
end
|
||||
end
|
||||
|
||||
-- override: fs.combine
|
||||
local combine = fs.combine
|
||||
function fs.combine(...)
|
||||
return "/" .. combine(...)
|
||||
end
|
||||
|
||||
-- override: fs.getDir
|
||||
local getDir = fs.getDir
|
||||
function fs.getDir(p)
|
||||
return "/" .. getDir(p)
|
||||
end
|
||||
|
||||
-- override: fs.exists
|
||||
local exists = fs.exists
|
||||
function fs.exists(path)
|
||||
expect(1, path, "string")
|
||||
return exists(resolve(path))
|
||||
end
|
||||
|
||||
-- override: fs.list
|
||||
local list = fs.list
|
||||
function fs.list(path)
|
||||
expect(1, path, "string")
|
||||
path = resolve(path)
|
||||
local _, files = pcall(list, path)
|
||||
if not _ then return nil, files end
|
||||
if path == "/" then
|
||||
-- inject /rc into the root listing
|
||||
if not exists("/rc") then
|
||||
files[#files+1] = "rc"
|
||||
end
|
||||
end
|
||||
return files
|
||||
end
|
||||
|
||||
-- override: fs.getSize
|
||||
local getSize = fs.getSize
|
||||
function fs.getSize(path)
|
||||
expect(1, path, "string")
|
||||
return getSize((resolve(path)))
|
||||
end
|
||||
|
||||
-- override: fs.isDir
|
||||
local isDir = fs.isDir
|
||||
function fs.isDir(path)
|
||||
expect(1, path, "string")
|
||||
return isDir(resolve(path))
|
||||
end
|
||||
|
||||
-- override: fs.makeDir
|
||||
local makeDir = fs.makeDir
|
||||
function fs.makeDir(path)
|
||||
expect(1, path, "string")
|
||||
return makeDir(resolve(path))
|
||||
end
|
||||
|
||||
-- override: fs.move
|
||||
local move = fs.move
|
||||
function fs.move(a, b)
|
||||
expect(1, a, "string")
|
||||
expect(2, b, "string")
|
||||
return move(resolve(a), resolve(b))
|
||||
end
|
||||
|
||||
-- override: fs.copy
|
||||
local copy = fs.copy
|
||||
function fs.copy(a, b)
|
||||
expect(1, a, "string")
|
||||
expect(2, b, "string")
|
||||
return copy(resolve(a), resolve(b))
|
||||
end
|
||||
|
||||
-- override: fs.delete
|
||||
local delete = fs.delete
|
||||
function fs.delete(path)
|
||||
expect(1, path, "string")
|
||||
return delete(resolve(path))
|
||||
end
|
||||
|
||||
-- override: fs.open
|
||||
local open = fs.open
|
||||
function fs.open(file, mode)
|
||||
expect(1, file, "string")
|
||||
expect(2, mode, "string")
|
||||
return open(resolve(file), mode or "r")
|
||||
end
|
||||
|
||||
-- override: fs.find
|
||||
local find = fs.find
|
||||
function fs.find(path)
|
||||
expect(1, path, "string")
|
||||
return find(resolve(path))
|
||||
end
|
||||
|
||||
-- override: fs.attributes
|
||||
local attributes = fs.attributes
|
||||
function fs.attributes(path)
|
||||
expect(1, path, "string")
|
||||
return attributes(resolve(path))
|
||||
end
|
||||
|
||||
-- new: fs.complete
|
||||
function fs.complete(path, location, include_files, include_dirs)
|
||||
expect(1, path, "string")
|
||||
expect(2, location, "string")
|
||||
expect(3, include_files, "boolean", "nil")
|
||||
expect(4, include_dirs, "boolean", "nil")
|
||||
|
||||
if include_files == nil then include_files = true end
|
||||
if include_dirs == nil then include_dirs = true end
|
||||
|
||||
if path:sub(1,1) == "/" and path:sub(-1) ~= "/" then
|
||||
location = fs.getDir(path)
|
||||
elseif path:sub(-1) == "/" then
|
||||
location = path
|
||||
else
|
||||
location = fs.combine(location, fs.getDir(path))
|
||||
end
|
||||
|
||||
local completions = {}
|
||||
|
||||
if not fs.exists(location) or not fs.isDir(location) then
|
||||
return completions
|
||||
end
|
||||
|
||||
local name = fs.getName(path)
|
||||
if path:sub(-1) == "/" then name = "" end
|
||||
local files = fs.list(location)
|
||||
|
||||
for i=1, #files, 1 do
|
||||
local file = files[i]
|
||||
local full = fs.combine(location, file)
|
||||
if file:sub(1, #name) == name then
|
||||
local dir = fs.isDir(full)
|
||||
if (dir and include_dirs) or include_files then
|
||||
completions[#completions+1] = file:sub(#name+1)
|
||||
if #completions[#completions] == 0 then
|
||||
completions[#completions] = nil
|
||||
end
|
||||
end
|
||||
if dir then
|
||||
completions[#completions+1] = file:sub(#name+1) .. "/"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return completions
|
||||
end
|
||||
|
||||
function fs.isDriveRoot(path)
|
||||
expect(1, path, "string")
|
||||
if #path == 0 then path = "/" end
|
||||
return path == "/" or fs.getDrive(path) == fs.getDrive(fs.getDir(path))
|
||||
end
|
||||
131
data/computercraft/lua/rom/startup/10_package.lua
Normal file
131
data/computercraft/lua/rom/startup/10_package.lua
Normal file
@@ -0,0 +1,131 @@
|
||||
-- package library --
|
||||
|
||||
local rc = ...
|
||||
|
||||
_G.package = {}
|
||||
|
||||
package.config = "/\n;\n?\n!\n-"
|
||||
package.cpath = ""
|
||||
package.path = "/rc/apis/?.lua;/rc/modules/main/?.lua;./lib/?.lua;./lib/?/init.lua;./?.lua;./?/init.lua"
|
||||
|
||||
local function rm(api)
|
||||
local tab = _G[api]
|
||||
_G[api] = nil
|
||||
return tab
|
||||
end
|
||||
|
||||
package.loaded = {
|
||||
_G = _G,
|
||||
os = os,
|
||||
rc = rc,
|
||||
math = math,
|
||||
utf8 = utf8,
|
||||
table = table,
|
||||
debug = debug,
|
||||
bit32 = rawget(_G, "bit32"),
|
||||
string = string,
|
||||
package = package,
|
||||
coroutine = coroutine,
|
||||
|
||||
-- CC-specific ones
|
||||
peripheral = rm("peripheral"),
|
||||
redstone = rm("redstone"),
|
||||
commands = rm("commands"),
|
||||
pocket = rm("pocket"),
|
||||
turtle = rm("turtle"),
|
||||
http = rm("http"),
|
||||
term = rm("term"),
|
||||
fs = rm("fs"),
|
||||
rs = rm("rs"),
|
||||
|
||||
-- LeonOS-PC APIs
|
||||
periphemu = rm("periphemu"),
|
||||
mounter = rm("mounter"),
|
||||
config = rm("config"),
|
||||
|
||||
-- CCEmuX API
|
||||
ccemux = rm("ccemux")
|
||||
}
|
||||
|
||||
package.preload = {}
|
||||
|
||||
package.searchers = {
|
||||
-- check package.preload
|
||||
function(mod)
|
||||
if package.preload[mod] then
|
||||
return package.preload[mod]
|
||||
else
|
||||
return nil, "no field package.preload['" .. mod .. "']"
|
||||
end
|
||||
end,
|
||||
|
||||
-- check for lua library
|
||||
function(mod)
|
||||
local ok, err = package.searchpath(mod, package.path, ".", "/")
|
||||
if not ok then
|
||||
return nil, err
|
||||
end
|
||||
|
||||
local func, loaderr = loadfile(ok)
|
||||
if not func then
|
||||
return nil, loaderr
|
||||
end
|
||||
return func()
|
||||
end,
|
||||
}
|
||||
|
||||
local fs = package.loaded.fs
|
||||
-- require isn't here yet
|
||||
local expect = loadfile("/rc/modules/main/cc/expect.lua")()
|
||||
package.loaded["cc.expect"] = expect
|
||||
|
||||
function package.searchpath(name, path, sep, rep)
|
||||
expect(1, name, "string")
|
||||
expect(2, path, "string")
|
||||
expect(3, sep, "string", "nil")
|
||||
expect(4, rep, "string", "nil")
|
||||
|
||||
sep = "%" .. (sep or ".")
|
||||
rep = rep or "/"
|
||||
|
||||
name = name:gsub(sep, rep)
|
||||
local serr = ""
|
||||
|
||||
for search in path:gmatch("[^;]+") do
|
||||
search = search:gsub("%?", name)
|
||||
|
||||
if fs.exists(search) then
|
||||
return search
|
||||
|
||||
else
|
||||
if #serr > 0 then
|
||||
serr = serr .. "\n "
|
||||
end
|
||||
|
||||
serr = serr .. "no file '" .. search .. "'"
|
||||
end
|
||||
end
|
||||
|
||||
return nil, serr
|
||||
end
|
||||
|
||||
function _G.require(mod)
|
||||
expect(1, mod, "string")
|
||||
|
||||
if package.loaded[mod] then
|
||||
return package.loaded[mod]
|
||||
end
|
||||
|
||||
local serr = "module '" .. mod .. "' not found:"
|
||||
for _, searcher in ipairs(package.searchers) do
|
||||
local result, err = searcher(mod)
|
||||
if result then
|
||||
package.loaded[mod] = result
|
||||
return result
|
||||
else
|
||||
serr = serr .. "\n " .. err
|
||||
end
|
||||
end
|
||||
|
||||
error(serr, 2)
|
||||
end
|
||||
388
data/computercraft/lua/rom/startup/15_term.lua
Normal file
388
data/computercraft/lua/rom/startup/15_term.lua
Normal file
@@ -0,0 +1,388 @@
|
||||
-- some term things
|
||||
-- e.g. redirects, read()
|
||||
|
||||
local rc = ...
|
||||
|
||||
-- we need a couple of these
|
||||
local colors = require("colors")
|
||||
local native = require("term")
|
||||
local strings = require("cc.strings")
|
||||
local expect = require("cc.expect").expect
|
||||
|
||||
local term = {}
|
||||
package.loaded.term = term
|
||||
local thread = require("rc.thread")
|
||||
|
||||
local valid = {
|
||||
write = true,
|
||||
scroll = true,
|
||||
getCursorPos = true,
|
||||
setCursorPos = true,
|
||||
getCursorBlink = true,
|
||||
setCursorBlink = true,
|
||||
getSize = true,
|
||||
clear = true,
|
||||
clearLine = true,
|
||||
getTextColor = true,
|
||||
getTextColour = true,
|
||||
setTextColor = true,
|
||||
setTextColour = true,
|
||||
getBackgroundColor = true,
|
||||
getBackgroundColour = true,
|
||||
setBackgroundColor = true,
|
||||
setBackgroundColour = true,
|
||||
isColor = true,
|
||||
isColour = true,
|
||||
blit = true,
|
||||
setPaletteColor = true,
|
||||
setPaletteColour = true,
|
||||
getPaletteColor = true,
|
||||
getPaletteColour = true,
|
||||
|
||||
-- LeonOS-PC graphics mode settings
|
||||
setGraphicsMode = not not native.setGraphicsMode,
|
||||
getGraphicsMode = not not native.getGraphicsMode,
|
||||
drawPixels = not not native.drawPixels,
|
||||
getPixels = not not native.getPixels,
|
||||
setPixel = not not native.setPixel,
|
||||
getPixel = not not native.getPixel
|
||||
}
|
||||
|
||||
for k in pairs(valid) do
|
||||
term[k] = function(...)
|
||||
local redirect = thread.getTerm()
|
||||
if not redirect[k] then
|
||||
error("redirect object does not implement term."..k, 2)
|
||||
end
|
||||
|
||||
return redirect[k](...)
|
||||
end
|
||||
end
|
||||
|
||||
function term.current()
|
||||
return thread.getTerm()
|
||||
end
|
||||
|
||||
function term.native()
|
||||
return native
|
||||
end
|
||||
|
||||
function term.redirect(obj)
|
||||
expect(1, obj, "table")
|
||||
return thread.setTerm(obj)
|
||||
end
|
||||
|
||||
function term.at(x, y)
|
||||
term.setCursorPos(x, y)
|
||||
return term
|
||||
end
|
||||
|
||||
local keys = require("keys")
|
||||
|
||||
-- rc.write
|
||||
-- [[
|
||||
function rc.write(text)
|
||||
expect(1, text, "string")
|
||||
|
||||
local lines = 0
|
||||
local w, h = term.getSize()
|
||||
local x, y = term.getCursorPos()
|
||||
|
||||
local elements = strings.splitElements(text, w)
|
||||
|
||||
strings.wrappedWriteElements(elements, w, false, {
|
||||
newline = function()
|
||||
lines = lines + 1
|
||||
|
||||
x = 1
|
||||
if y >= h then
|
||||
term.scroll(1)
|
||||
else
|
||||
y = y + 1
|
||||
end
|
||||
|
||||
term.at(x, y)
|
||||
end,
|
||||
|
||||
append = function(newText)
|
||||
term.at(x, y).write(newText)
|
||||
x = x + #newText
|
||||
end,
|
||||
|
||||
getX = function()
|
||||
return x
|
||||
end
|
||||
})
|
||||
|
||||
return lines
|
||||
end
|
||||
|
||||
--]]
|
||||
|
||||
-- old write() used for redrawing in read()
|
||||
local function write(text)
|
||||
expect(1, text, "string")
|
||||
|
||||
local lines = 0
|
||||
local w, h = term.getSize()
|
||||
|
||||
local function inc_cy(cy)
|
||||
lines = lines + 1
|
||||
|
||||
if cy > h - 1 then
|
||||
term.scroll(1)
|
||||
return cy
|
||||
else
|
||||
return cy + 1
|
||||
end
|
||||
end
|
||||
|
||||
while #text > 0 do
|
||||
local nl = text:find("\n") or #text
|
||||
local chunk = text:sub(1, nl)
|
||||
text = text:sub(#chunk + 1)
|
||||
|
||||
local has_nl = chunk:sub(-1) == "\n"
|
||||
if has_nl then chunk = chunk:sub(1, -2) end
|
||||
|
||||
local cx, cy = term.getCursorPos()
|
||||
while #chunk > 0 do
|
||||
if cx > w then
|
||||
term.setCursorPos(1, inc_cy(cy))
|
||||
cx, cy = term.getCursorPos()
|
||||
end
|
||||
|
||||
local to_write = chunk:sub(1, w - cx + 1)
|
||||
term.write(to_write)
|
||||
|
||||
chunk = chunk:sub(#to_write + 1)
|
||||
cx, cy = term.getCursorPos()
|
||||
end
|
||||
|
||||
if has_nl then
|
||||
term.setCursorPos(1, inc_cy(cy))
|
||||
end
|
||||
end
|
||||
|
||||
return lines
|
||||
end--]]
|
||||
|
||||
rc.write = write
|
||||
|
||||
-- read
|
||||
local empty = {}
|
||||
function term.read(replace, history, complete, default)
|
||||
expect(1, replace, "string", "nil")
|
||||
expect(2, history, "table", "nil")
|
||||
expect(3, complete, "function", "nil")
|
||||
expect(4, default, "string", "nil")
|
||||
|
||||
if replace then replace = replace:sub(1, 1) end
|
||||
local hist = history or {}
|
||||
history = {}
|
||||
for i=1, #hist, 1 do
|
||||
history[i] = hist[i]
|
||||
end
|
||||
|
||||
local buffer = default or ""
|
||||
local prev_buf = buffer
|
||||
history[#history+1] = buffer
|
||||
|
||||
local hist_pos = #history
|
||||
local cursor_pos = 0
|
||||
|
||||
local stx, sty = term.getCursorPos()
|
||||
local w, h = term.getSize()
|
||||
|
||||
local dirty = false
|
||||
local completions = {}
|
||||
local comp_id = 0
|
||||
|
||||
local function clearCompletion()
|
||||
if completions[comp_id] then
|
||||
write((" "):rep(#completions[comp_id]))
|
||||
end
|
||||
end
|
||||
|
||||
local function full_redraw(force)
|
||||
if force or dirty then
|
||||
if complete and buffer ~= prev_buf then
|
||||
completions = complete(buffer) or empty
|
||||
comp_id = math.min(1, #completions)
|
||||
end
|
||||
prev_buf = buffer
|
||||
|
||||
term.setCursorPos(stx, sty)
|
||||
local text = buffer
|
||||
if replace then text = replace:rep(#text) end
|
||||
local ln = write(text)
|
||||
|
||||
if completions[comp_id] then
|
||||
local oldfg = term.getTextColor()
|
||||
local oldbg = term.getBackgroundColor()
|
||||
term.setTextColor(colors.white)
|
||||
term.setBackgroundColor(colors.gray)
|
||||
ln = ln + write(completions[comp_id])
|
||||
term.setTextColor(oldfg)
|
||||
term.setBackgroundColor(oldbg)
|
||||
else
|
||||
ln = ln + write(" ")
|
||||
end
|
||||
|
||||
if sty + ln > h then
|
||||
sty = sty - (sty + ln - h)
|
||||
end
|
||||
end
|
||||
|
||||
-- set cursor to the appropriate spot
|
||||
local cx, cy = stx, sty
|
||||
cx = cx + #buffer - cursor_pos-- + #(completions[comp_id] or "")
|
||||
while cx > w do
|
||||
cx = cx - w
|
||||
cy = cy + 1
|
||||
end
|
||||
term.setCursorPos(cx, cy)
|
||||
end
|
||||
|
||||
term.setCursorBlink(true)
|
||||
|
||||
while true do
|
||||
full_redraw()
|
||||
-- get input
|
||||
local evt, id = rc.pullEvent()
|
||||
|
||||
if evt == "char" then
|
||||
dirty = true
|
||||
clearCompletion()
|
||||
if cursor_pos == 0 then
|
||||
buffer = buffer .. id
|
||||
elseif cursor_pos == #buffer then
|
||||
buffer = id .. buffer
|
||||
else
|
||||
buffer = buffer:sub(0, -cursor_pos - 1)..id..buffer:sub(-cursor_pos)
|
||||
end
|
||||
|
||||
elseif evt == "paste" then
|
||||
dirty = true
|
||||
clearCompletion()
|
||||
if cursor_pos == 0 then
|
||||
buffer = buffer .. id
|
||||
elseif cursor_pos == #buffer then
|
||||
buffer = id .. buffer
|
||||
else
|
||||
buffer = buffer:sub(0, -cursor_pos - 1)..id..
|
||||
buffer:sub(-cursor_pos+(#id-1))
|
||||
end
|
||||
|
||||
elseif evt == "key" then
|
||||
id = keys.getName(id)
|
||||
|
||||
if id == "backspace" and #buffer > 0 then
|
||||
dirty = true
|
||||
if cursor_pos == 0 then
|
||||
buffer = buffer:sub(1, -2)
|
||||
clearCompletion()
|
||||
elseif cursor_pos < #buffer then
|
||||
buffer = buffer:sub(0, -cursor_pos - 2)..buffer:sub(-cursor_pos)
|
||||
end
|
||||
|
||||
elseif id == "delete" and cursor_pos > 0 then
|
||||
dirty = true
|
||||
|
||||
if cursor_pos == #buffer then
|
||||
buffer = buffer:sub(2)
|
||||
elseif cursor_pos == 1 then
|
||||
buffer = buffer:sub(1, -2)
|
||||
else
|
||||
buffer = buffer:sub(0, -cursor_pos - 1) .. buffer:sub(-cursor_pos + 1)
|
||||
end
|
||||
cursor_pos = cursor_pos - 1
|
||||
|
||||
elseif id == "up" then
|
||||
if #completions > 1 then
|
||||
dirty = true
|
||||
clearCompletion()
|
||||
if comp_id > 1 then
|
||||
comp_id = comp_id - 1
|
||||
else
|
||||
comp_id = #completions
|
||||
end
|
||||
|
||||
elseif hist_pos > 1 then
|
||||
cursor_pos = 0
|
||||
|
||||
history[hist_pos] = buffer
|
||||
hist_pos = hist_pos - 1
|
||||
|
||||
buffer = (" "):rep(#buffer)
|
||||
full_redraw(true)
|
||||
|
||||
buffer = history[hist_pos]
|
||||
dirty = true
|
||||
end
|
||||
|
||||
elseif id == "down" then
|
||||
if #completions > 1 then
|
||||
dirty = true
|
||||
clearCompletion()
|
||||
if comp_id < #completions then
|
||||
comp_id = comp_id + 1
|
||||
else
|
||||
comp_id = 1
|
||||
end
|
||||
|
||||
elseif hist_pos < #history then
|
||||
cursor_pos = 0
|
||||
|
||||
history[hist_pos] = buffer
|
||||
hist_pos = hist_pos + 1
|
||||
|
||||
buffer = (" "):rep(#buffer)
|
||||
full_redraw(true)
|
||||
|
||||
buffer = history[hist_pos]
|
||||
dirty = true
|
||||
end
|
||||
|
||||
elseif id == "left" then
|
||||
if cursor_pos < #buffer then
|
||||
clearCompletion()
|
||||
cursor_pos = cursor_pos + 1
|
||||
end
|
||||
|
||||
elseif id == "right" then
|
||||
if cursor_pos > 0 then
|
||||
cursor_pos = cursor_pos - 1
|
||||
|
||||
elseif comp_id > 0 then
|
||||
dirty = true
|
||||
buffer = buffer .. completions[comp_id]
|
||||
end
|
||||
|
||||
elseif id == "tab" then
|
||||
if comp_id > 0 then
|
||||
dirty = true
|
||||
buffer = buffer .. completions[comp_id]
|
||||
end
|
||||
|
||||
elseif id == "home" then
|
||||
cursor_pos = #buffer
|
||||
|
||||
elseif id == "end" then
|
||||
cursor_pos = 0
|
||||
|
||||
elseif id == "enter" then
|
||||
clearCompletion()
|
||||
write("\n")
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
term.setCursorBlink(false)
|
||||
|
||||
return buffer
|
||||
end
|
||||
|
||||
rc.read = term.read
|
||||
setmetatable(term, {__index = native})
|
||||
13
data/computercraft/lua/rom/startup/20_io.lua
Normal file
13
data/computercraft/lua/rom/startup/20_io.lua
Normal file
@@ -0,0 +1,13 @@
|
||||
_G.io = require("rc.io")
|
||||
|
||||
function _G.print(...)
|
||||
local args = table.pack(...)
|
||||
|
||||
for i=1, args.n, 1 do
|
||||
args[i] = tostring(args[i])
|
||||
end
|
||||
|
||||
io.stdout:write(table.concat(args, "\t"), "\n")
|
||||
|
||||
return true
|
||||
end
|
||||
184
data/computercraft/lua/rom/startup/30_peripheral.lua
Normal file
184
data/computercraft/lua/rom/startup/30_peripheral.lua
Normal file
@@ -0,0 +1,184 @@
|
||||
-- rc.peripheral
|
||||
|
||||
local old = require("peripheral")
|
||||
local expect = require("cc.expect").expect
|
||||
|
||||
local p = {}
|
||||
package.loaded.peripheral = p
|
||||
|
||||
local sides = {"bottom", "top", "left", "right", "front", "back"}
|
||||
|
||||
function p.getNames()
|
||||
local names = {}
|
||||
|
||||
for i=1, #sides, 1 do
|
||||
local side = sides[i]
|
||||
if old.isPresent(side) then
|
||||
names[#names+1] = side
|
||||
|
||||
if old.hasType(side, "modem") and not old.call(side, "isWireless") then
|
||||
local remote_names = old.call(side, "getNamesRemote")
|
||||
for j=1, #remote_names, 1 do
|
||||
names[#names+1] = remote_names[j]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return names
|
||||
end
|
||||
|
||||
-- figure out where a peripheral is
|
||||
-- returns 0 if the peripheral is directly connected,
|
||||
-- and a side if it's connected through a modem
|
||||
local function findByName(name)
|
||||
if old.isPresent(name) then
|
||||
return 0
|
||||
|
||||
else
|
||||
for i=1, #sides, 1 do
|
||||
local side = sides[i]
|
||||
|
||||
if old.hasType(side, "modem") and not old.call(side, "isWireless") then
|
||||
if old.call(side, "isPresentRemote", name) then
|
||||
return side
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function p.isPresent(name)
|
||||
expect(1, name, "string")
|
||||
|
||||
return not not findByName(name)
|
||||
end
|
||||
|
||||
function p.getType(per)
|
||||
expect(1, per, "string", "table")
|
||||
|
||||
if type(per) == "string" then
|
||||
local place = findByName(per)
|
||||
|
||||
if place == 0 then
|
||||
return old.getType(per)
|
||||
|
||||
elseif place then
|
||||
return old.call(place, "getTypeRemote", per)
|
||||
end
|
||||
|
||||
else
|
||||
return table.unpack(per.__types)
|
||||
end
|
||||
end
|
||||
|
||||
function p.hasType(per, ptype)
|
||||
expect(1, per, "string", "table")
|
||||
expect(2, ptype, "string")
|
||||
|
||||
if type(per) == "string" then
|
||||
local place = findByName(per)
|
||||
|
||||
if place == 0 then
|
||||
return old.hasType(per, ptype)
|
||||
|
||||
elseif place then
|
||||
return old.call(place, "hasTypeRemote", per, ptype)
|
||||
end
|
||||
|
||||
else
|
||||
return per.__types[ptype]
|
||||
end
|
||||
end
|
||||
|
||||
function p.getMethods(name)
|
||||
expect(1, name, "string")
|
||||
|
||||
local place = findByName(name)
|
||||
if place == 0 then
|
||||
return old.getMethods(name)
|
||||
|
||||
elseif place then
|
||||
return old.call(place, "getMethodsRemote", name)
|
||||
end
|
||||
end
|
||||
|
||||
function p.getName(per)
|
||||
expect(1, per, "table")
|
||||
return per.__info.name
|
||||
end
|
||||
|
||||
function p.call(name, method, ...)
|
||||
expect(1, name, "string")
|
||||
expect(2, method, "string")
|
||||
|
||||
local place = findByName(name)
|
||||
if place == 0 then
|
||||
return old.call(name, method, ...)
|
||||
|
||||
elseif place then
|
||||
return old.call(place, "callRemote", name, method, ...)
|
||||
end
|
||||
end
|
||||
|
||||
function p.wrap(name)
|
||||
expect(1, name, "string")
|
||||
|
||||
local place = findByName(name)
|
||||
if not place then return end
|
||||
|
||||
local methods, types
|
||||
if place == 0 then
|
||||
methods = old.getMethods(name)
|
||||
types = table.pack(old.getType(name))
|
||||
else
|
||||
methods = old.call(place, "getMethodsRemote", name)
|
||||
types = table.pack(old.call(place, "getTypesRemote", name))
|
||||
end
|
||||
|
||||
for i=1, #types, 1 do
|
||||
types[types[i]] = true
|
||||
end
|
||||
|
||||
local wrapper = {
|
||||
__info = {
|
||||
name = name,
|
||||
types = types,
|
||||
}
|
||||
}
|
||||
|
||||
if place == 0 then
|
||||
for i=1, #methods, 1 do
|
||||
wrapper[methods[i]] = function(...)
|
||||
return old.call(name, methods[i], ...)
|
||||
end
|
||||
end
|
||||
|
||||
else
|
||||
for i=1, #methods, 1 do
|
||||
wrapper[methods[i]] = function(...)
|
||||
return old.call(place, "callRemote", name, methods[i], ...)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return wrapper
|
||||
end
|
||||
|
||||
function p.find(ptype, filter)
|
||||
expect(1, ptype, "string")
|
||||
expect(2, filter, "function", "nil")
|
||||
|
||||
local wrapped = {}
|
||||
|
||||
for _, name in ipairs(p.getNames()) do
|
||||
if p.hasType(name, ptype) then
|
||||
local wrapper = p.wrap(name)
|
||||
if (p.filter and p.filter(name, wrapper)) or not p.filter then
|
||||
wrapped[#wrapped+1] = wrapper
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return table.unpack(wrapped)
|
||||
end
|
||||
168
data/computercraft/lua/rom/startup/35_http.lua
Normal file
168
data/computercraft/lua/rom/startup/35_http.lua
Normal file
@@ -0,0 +1,168 @@
|
||||
-- HTTP library adapted from .OS --
|
||||
|
||||
-- native http.request: function(
|
||||
-- url:string[, post:string[, headers:table[, binarymode:boolean]]])
|
||||
-- post is the data to POST. otherwise a GET is sent.
|
||||
-- OR: function(parameters:table)
|
||||
-- where parameters = {
|
||||
-- url = string, -- the URL
|
||||
-- body = string, -- the data to POST/PATCH/PUT
|
||||
-- headers = table, -- request headers
|
||||
-- binary = boolean, -- self explanatory
|
||||
-- method = string} -- the HTTP method to use - one of:
|
||||
-- - GET
|
||||
-- - POST
|
||||
-- - HEAD
|
||||
-- - OPTIONS
|
||||
-- - PUT
|
||||
-- - DELETE
|
||||
-- - PATCH
|
||||
-- - TRACE
|
||||
--
|
||||
-- native http.checkURL: function(url:string)
|
||||
-- url is a URL to try to reach. queues a http_check event with the result.
|
||||
-- native http.websocket(url:string[, headers:table])
|
||||
-- url is the url to which to open a websocket. queues a websocket_success
|
||||
-- event on success, and websocket_failure on failure.
|
||||
-- native http.addListener(port:number) (LeonOS-PC only)
|
||||
-- add a listener on the specified port. when that port receives data,
|
||||
-- the listener queues a http_request(port:number, request, response).
|
||||
-- !!the response is not send until response.close() is called!!
|
||||
-- native http.removeListener(port:number) (LeonOS-PC only)
|
||||
-- remove the listener from that port
|
||||
|
||||
local rc = ...
|
||||
|
||||
if not package.loaded.http then
|
||||
return
|
||||
end
|
||||
|
||||
local old = package.loaded.http
|
||||
|
||||
local http = {}
|
||||
|
||||
package.loaded.http = http
|
||||
|
||||
local field = require("cc.expect").field
|
||||
local expect = require("cc.expect").expect
|
||||
|
||||
local function listenForResponse(url)
|
||||
while true do
|
||||
local sig, a, b, c = rc.pullEvent()
|
||||
if sig == "http_success" and a == url then
|
||||
return b
|
||||
elseif sig == "http_failure" and a == url then
|
||||
return nil, b, c
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function http.request(url, post, headers, binary, method, sync)
|
||||
if type(url) ~= "table" then
|
||||
url = {
|
||||
url = url,
|
||||
body = post,
|
||||
headers = headers,
|
||||
binary = binary,
|
||||
method = method or (post and "POST") or "GET",
|
||||
sync = not not sync
|
||||
}
|
||||
end
|
||||
|
||||
field(url, "url", "string")
|
||||
field(url, "body", "string", "nil")
|
||||
field(url, "headers", "table", "nil")
|
||||
field(url, "binary", "boolean", "nil")
|
||||
field(url, "method", "string")
|
||||
field(url, "sync", "boolean", "nil")
|
||||
|
||||
local ok, err = old.request(url)
|
||||
if not ok then
|
||||
return nil, err
|
||||
end
|
||||
|
||||
if sync then return listenForResponse(url.url) end
|
||||
end
|
||||
|
||||
function http.get(url, headers, binary)
|
||||
if type(url) == "table" then
|
||||
url.sync = true
|
||||
return http.request(url)
|
||||
else
|
||||
return http.request(url, nil, headers, binary, "GET", true)
|
||||
end
|
||||
end
|
||||
|
||||
function http.post(url, body, headers, binary)
|
||||
if type(url) == "table" then
|
||||
url.sync = true
|
||||
url.method = "POST"
|
||||
return http.request(url)
|
||||
else
|
||||
return http.request(url, body, headers, binary, "POST", true)
|
||||
end
|
||||
end
|
||||
|
||||
http.checkURLAsync = old.checkURL
|
||||
|
||||
function http.checkURL(url)
|
||||
expect(1, url, "string")
|
||||
|
||||
local ok, err = old.checkURL(url)
|
||||
if not ok then
|
||||
return nil, err
|
||||
end
|
||||
|
||||
local sig, _url, a, b
|
||||
repeat
|
||||
sig, _url, a, b = coroutine.yield()
|
||||
until sig == "http_check" and url == _url
|
||||
|
||||
return a, b
|
||||
end
|
||||
|
||||
http.websocketAsync = old.websocket
|
||||
|
||||
function http.websocket(url, headers)
|
||||
expect(1, url, "string")
|
||||
expect(2, headers, "string")
|
||||
|
||||
local ok, err = old.websocket(url, headers)
|
||||
if not ok then
|
||||
return nil, err
|
||||
end
|
||||
|
||||
while true do
|
||||
local sig, a, b, c = coroutine.yield()
|
||||
if sig == "websocket_success" and a == url then
|
||||
return b, c
|
||||
elseif sig == "websocket_failure" and a == url then
|
||||
return nil, b
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if old.addListener then
|
||||
function http.listen(port, callback)
|
||||
expect(1, port, "number")
|
||||
expect(2, callback, "function")
|
||||
old.addListener(port)
|
||||
|
||||
while true do
|
||||
local sig, a, b, c = coroutine.yield()
|
||||
if sig == "stop_listener" and a == port then
|
||||
old.removeListener(port)
|
||||
break
|
||||
elseif sig == "http_request" and a == port then
|
||||
if not callback(b, c) then
|
||||
old.removeListener(port)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
function http.listen()
|
||||
error("This functionality requires LeonOS-PC", 0)
|
||||
end
|
||||
end
|
||||
36
data/computercraft/lua/rom/startup/40_commands.lua
Normal file
36
data/computercraft/lua/rom/startup/40_commands.lua
Normal file
@@ -0,0 +1,36 @@
|
||||
-- rc.commands
|
||||
|
||||
local expect = require("cc.expect").expect
|
||||
|
||||
if not package.loaded.commands then return end
|
||||
|
||||
local native = package.loaded.commands
|
||||
local c = {}
|
||||
package.loaded.commands = c
|
||||
|
||||
c.native = native
|
||||
c.async = {}
|
||||
|
||||
for k, v in pairs(native) do c[k] = v end
|
||||
|
||||
local command_list = native.list()
|
||||
|
||||
for i=1, #command_list, 1 do
|
||||
local command = command_list[i]
|
||||
c.async[command] = function(...)
|
||||
return c.execAsync(command, ...)
|
||||
end
|
||||
c[command] = c[command] or function(...)
|
||||
return c.exec(command, ...)
|
||||
end
|
||||
end
|
||||
|
||||
function c.exec(command, ...)
|
||||
expect(1, command, "string")
|
||||
return c.native.exec(table.concat(table.pack(command, ...), " "))
|
||||
end
|
||||
|
||||
function c.execAsync(command, ...)
|
||||
expect(1, command, "string")
|
||||
return c.native.execAsync(table.concat(table.pack(command, ...), " "))
|
||||
end
|
||||
101
data/computercraft/lua/rom/startup/90_settings.lua
Normal file
101
data/computercraft/lua/rom/startup/90_settings.lua
Normal file
@@ -0,0 +1,101 @@
|
||||
-- setting definitions
|
||||
|
||||
local settings = require("settings")
|
||||
|
||||
settings.define("list.show_hidden", {
|
||||
description = "Show hidden files in list's output",
|
||||
type = "boolean",
|
||||
default = false
|
||||
})
|
||||
|
||||
settings.define("bios.compat_mode", {
|
||||
description = "Attempt some LeonOS compatibility by injecting APIs into _G.",
|
||||
type = "boolean",
|
||||
default = false
|
||||
})
|
||||
|
||||
settings.define("shell.tracebacks", {
|
||||
description = "Show error tracebacks in the shell.",
|
||||
type = "boolean",
|
||||
default = false
|
||||
})
|
||||
|
||||
settings.define("edit.scroll_offset", {
|
||||
description = "How many lines to keep between the cursor and the screen edge.",
|
||||
type = "number",
|
||||
default = 3
|
||||
})
|
||||
|
||||
settings.define("edit.force_highlight", {
|
||||
description = "Whether to use the highlighting editor, even on basic computers.",
|
||||
type = "boolean",
|
||||
default = false
|
||||
})
|
||||
|
||||
settings.define("edit.scroll_factor", {
|
||||
description = "Related to how many lines the editor should jump at a time when scrolling. Determined by term_height/scroll_factor. Adjust this for performance.",
|
||||
type = "number",
|
||||
default = 8
|
||||
})
|
||||
|
||||
settings.define("edit.color_separator", {
|
||||
description = "What color separating characters (e.g. ()[];{}) should be.",
|
||||
type = "string",
|
||||
default = "lightBlue"
|
||||
})
|
||||
|
||||
settings.define("edit.color_operator", {
|
||||
description = "What color operators (e.g. +-/*) should be.",
|
||||
type = "string",
|
||||
default = "lightGray"
|
||||
})
|
||||
|
||||
settings.define("edit.color_keyword", {
|
||||
description = "What color keywords (e.g. local, for, if) should be.",
|
||||
type = "string",
|
||||
default = "orange"
|
||||
})
|
||||
|
||||
settings.define("edit.color_boolean", {
|
||||
description = "What color booleans (true/false) should be.",
|
||||
type = "string",
|
||||
default = "purple"
|
||||
})
|
||||
|
||||
settings.define("edit.color_comment", {
|
||||
description = "What color comments should be.",
|
||||
type = "string",
|
||||
default = "gray"
|
||||
})
|
||||
|
||||
settings.define("edit.color_global", {
|
||||
description = "What color globals (e.g. print, require) should be.",
|
||||
type = "string",
|
||||
default = "lime"
|
||||
})
|
||||
|
||||
settings.define("edit.color_string", {
|
||||
description = "What color strings should be.",
|
||||
type = "string",
|
||||
default = "red"
|
||||
})
|
||||
|
||||
settings.define("edit.color_number", {
|
||||
description = "What color numbers (e.g. 2, 0xF3, 0.42) should be.",
|
||||
type = "string",
|
||||
default = "magenta"
|
||||
})
|
||||
|
||||
settings.define("bios.restrict_globals", {
|
||||
description = "Disallow global variables",
|
||||
type = "boolean",
|
||||
default = false
|
||||
})
|
||||
|
||||
settings.define("bios.parallel_startup", {
|
||||
description = "Run startup scripts from /startup in parallel",
|
||||
type = "boolean",
|
||||
default = false
|
||||
})
|
||||
|
||||
settings.load()
|
||||
63
data/computercraft/lua/rom/update.lua
Normal file
63
data/computercraft/lua/rom/update.lua
Normal file
@@ -0,0 +1,63 @@
|
||||
-- update: download a new copy of LeonOS
|
||||
|
||||
local rc = require("rc")
|
||||
local term = require("term")
|
||||
local colors = require("colors")
|
||||
local textutils = require("textutils")
|
||||
|
||||
if not package.loaded.http then
|
||||
io.stderr:write("The HTTP API is disabled and the updater cannot continue. Please enable the HTTP API in the ComputerCraft configuration and try again.\n")
|
||||
return
|
||||
end
|
||||
|
||||
term.at(1,1).clear()
|
||||
|
||||
textutils.coloredPrint(colors.yellow,
|
||||
"LeonOS Updater (Stage 1)\n===========================")
|
||||
|
||||
print("Checking for update...")
|
||||
|
||||
local http = require("http")
|
||||
local base = "https://raw.githubusercontent.com/LeonMMcoset/LeonOS/primary/"
|
||||
|
||||
local Bhandle, Berr = http.get(base .. "data/computercraft/lua/bios.lua")
|
||||
if not Bhandle then
|
||||
error(Berr, 0)
|
||||
end
|
||||
|
||||
local first = Bhandle.readLine()
|
||||
Bhandle.close()
|
||||
|
||||
local oldVersion = rc.version():gsub("LeonOS ", "")
|
||||
local newVersion = first:match("LeonOS v?(%d+.%d+.%d+)")
|
||||
|
||||
if newVersion and (oldVersion ~= newVersion) or (...) == "-f" then
|
||||
textutils.coloredPrint(colors.green, "Found", colors.white, ": ",
|
||||
colors.red, oldVersion, colors.yellow, " -> ", colors.lime,
|
||||
newVersion or oldVersion)
|
||||
|
||||
io.write("Apply update? [y/N]: ")
|
||||
if io.read() ~= "y" then
|
||||
textutils.coloredPrint(colors.red, "Not applying update.")
|
||||
return
|
||||
end
|
||||
|
||||
textutils.coloredPrint(colors.green, "Applying update.")
|
||||
local handle, err = http.get(base.."updater.lua", nil, true)
|
||||
if not handle then
|
||||
error("Failed downloading stage 2: " .. err, 0)
|
||||
end
|
||||
|
||||
local data = handle.readAll()
|
||||
handle.close()
|
||||
|
||||
local out = io.open("/.start_rc.lua", "w")
|
||||
out:write(data)
|
||||
out:close()
|
||||
|
||||
textutils.coloredWrite(colors.yellow, "Restarting...")
|
||||
rc.sleep(3)
|
||||
rc.reboot()
|
||||
else
|
||||
textutils.coloredPrint(colors.red, "None found")
|
||||
end
|
||||
Reference in New Issue
Block a user