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
|
||||
Reference in New Issue
Block a user