Files
LeonOS/data/computercraft/lua/rom/apis/shell.lua
Leonmmcoset 976769de2d refactor: 将默认路径从/rc更改为/leonos
更新所有相关文件中的路径引用,统一使用/leonos作为系统目录。同时更新版本号至0.1.7。
2025-09-01 11:03:43 +08:00

335 lines
6.8 KiB
Lua

-- 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 ".:/leonos/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