Merge branch 'unfinished-ag2' into Pre-Alpha-3.0.0

This commit is contained in:
2026-06-18 16:38:51 +03:00
90 changed files with 2913 additions and 922 deletions
+2
View File
@@ -1,3 +1,5 @@
.stfolder
.idea
home/*
halyde/logs/*
*.kate-swp
+5 -1
View File
@@ -1,5 +1,9 @@
{
"halyde": "https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/Pre-Alpha-3.0.0/",
"argentum": "https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/Pre-Alpha-3.0.0/",
"edit": "https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/Pre-Alpha-3.0.0/"
"edit": "https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/Pre-Alpha-3.0.0/",
"package1": "https://raw.githubusercontent.com/WahPlus/ArgentumPackages/refs/heads/main/",
"package2": "https://raw.githubusercontent.com/WahPlus/ArgentumPackages/refs/heads/main/",
"vpkg": "https://raw.githubusercontent.com/WahPlus/ArgentumPackages/refs/heads/main/",
"grouper": "https://raw.githubusercontent.com/WahPlus/ArgentumPackages/refs/heads/main/"
}
+5 -1
View File
@@ -1,3 +1,4 @@
local agregistry = {
["halyde"] = "https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/main/",
["edit"] = "https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/main/",
@@ -8,7 +9,10 @@ local agregistry = {
["hextra"] = "https://raw.githubusercontent.com/Ponali/ArgentumPackages/refs/heads/master/",
["utape"] = "https://raw.githubusercontent.com/Ponali/ArgentumPackages/refs/heads/master/",
["libctif"] = "https://raw.githubusercontent.com/Ponali/ArgentumPackages/refs/heads/master/",
["ctif-viewer"] = "https://raw.githubusercontent.com/Ponali/ArgentumPackages/refs/heads/master/"
["ctif-viewer"] = "https://raw.githubusercontent.com/Ponali/ArgentumPackages/refs/heads/master/",
["libsha256"] = "https://raw.githubusercontent.com/tema5002/ag-packages/refs/heads/main/",
["sha256sum"] = "https://raw.githubusercontent.com/tema5002/ag-packages/refs/heads/main/",
["base64"] = "https://raw.githubusercontent.com/mcplayer3/AgPackages/refs/heads/main/"
}
return agregistry
+520
View File
@@ -0,0 +1,520 @@
local cliparse = require("cliparse")
local fs = require("filesystem")
local component = require("component")
local json = require("json")
local function getFile(path)
checkArg(1, path, "string")
if path:sub(1, 7) == "http://" or path:sub(1, 8) == "https://" then
if not component.list("internet")() then
return false, "Internet card required but not found."
end
local handle, data, tmpdata = component.internet.request(path), "", nil
local success, errorMessage = pcall(function()
handle:finishConnect()
end)
if not success then
return false, errorMessage
end
local code, message = handle:response()
if code and code ~= 200 then
return false, ("%d %s"):format(code, message)
end
repeat
tmpdata = handle.read(math.huge or math.maxinteger)
data = data .. (tmpdata or "")
until not tmpdata
return true, data
elseif path:sub(1, 1) == "/" then
if not fs.exists(path) then
return false, "No such file or directory: " .. path
end
if fs.isDirectory(path) then
return false, "Expected file, found directory: " .. path
end
local handle, data, tmpdata = fs.open(path, "r", false), "", nil
if not handle then
return false, data
end
repeat
tmpdata = handle:read(math.huge or math.maxinteger)
data = data .. (tmpdata or "")
until not tmpdata
return true, data
else
return false, "Unsupported path: " .. path
end
end
cliparse.config({
["x"] = 0,
["exclude-deps"] = 0,
["u"] = 0,
["update-registry"] = 0,
["f"] = 0,
["force"] = 0,
["c"] = 0,
["clean"] = 0,
["s"] = 1,
["source"] = 1,
["C"] = 0,
["cascade"] = 0,
})
local parsed, errorMessage = cliparse.parse(...)
if not parsed then
print(("\27[91m%s\n\27[0mExiting."):format(errorMessage))
return
end
local command = parsed.args[1]
if not command then
print("\27[91mNo command specified.\n\27[0mExiting.")
return
end
if
not (
command == "install"
or command == "remove"
or command == "update"
or command == "list"
or command == "repo-list"
or command == "repo-add"
or command == "repo-remove"
or command == "info"
)
then
print(("\27[91mInvalid command: %s\n\27[0mExiting."):format(command))
return
end
local packages = parsed.args
table.remove(packages, 1)
-- Remove the command from the actual package list
-- local result, data, failure
do
local function check(condition, message)
if not condition then
print(message)
failure = true
end
end
if parsed.flags.u or parsed.flags["update-registry"] then
terminal.write("Updating registry...")
result, data = getFile("https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/Pre-Alpha-3.0.0/ag2/registry.json")
check(result, "\27[91mFailed to get registry: " .. data .. "\27[0m")
local handle, errorMessage = fs.open("/ag2/registry.json", "w")
check(handle, "\27[91mFailed to open write handle to registry: " .. errorMessage .. "\27[0m")
local success, errorMessage = handle:write(data)
check(success, "\27[91mFailed to write to registry: " .. errorMessage .. "\27[0m")
handle:close()
else
result, data = getFile("/ag2/registry.json")
check(result, "\27[91mFailed to get registry: " .. data .. "\27[0m")
end
end
local success, registry = pcall(function()
return json.decode(data)
end)
if not success then
print(("\27[91mFailed to parse registry: %s\n\27[0mExiting."):format(registry))
return
end
local function getServersidePackageConfig(source, package)
local success, data = getFile(fs.concat(source, "/ag2.json"))
if not success then
return false, ("\27[91mFailed to get package config (ag2.json) of package '%s': " .. data .. "\27[0m"):format(package)
end
local success, packageConfig = pcall(function()
return json.decode(data)
end)
if not success then
return false, ("\27[91mFailed to parse package config (ag2.json) of package '%s': " .. packageConfig .. "\27[0m"):format(package)
end
if not packageConfig[package] then
return false, ("\27[91mRepository package config (ag2.json) does not contain package '%s'.\27[0m"):format(package)
end
return packageConfig[package]
end
-- Check if everything is valid
failure = false
local dependencyCounter = 0
local previousI = 1 -- See 190
::RESETLOOP::
if command == "install" then
for i = previousI, #packages do
if not packages[i] then
-- When packages are removed, the last packages can end up reading as nil
goto SKIP
end
local otherPackages = table.copy(packages)
table.remove(otherPackages, i)
-- This is to check if the package can be found in the others, or in other words, checking for duplicates
if table.find(otherPackages, packages[i]) then
print(("\27[93mDuplicate package specified (%s), skipping\27[0m"):format(packages[i]))
table.remove(packages, i)
i = i - 1
goto SKIP
end
local source
if parsed.s or parsed.source then
source = parsed.s or parsed.source
else
source = registry[packages[i]]
end
if not source then
print("\27[91mCould not find package in registry and no source provided: " .. packages[i] .. "\27[0m")
failure = true
goto SKIP
end
local packageConfig, errorMessage = getServersidePackageConfig(source, packages[i])
if not packageConfig then
failure = true
print(errorMessage)
goto SKIP
end
if packageConfig.type == "group" then
table.remove(packages, i)
for _, package in ipairs(packageConfig.packages) do
table.insert(packages, package)
end
previousI = i
goto RESETLOOP
-- Apparently "for i = 1, n" loops don't change when n changes. So when the table gets extended, packages near the end won't get picked up.
-- This is why it is needed to restart the loop.
elseif packageConfig.type == "virtual-package" then
print(("Installing virtual package %s"):format(packages[i]))
local pkgAskText = ("Select a package by typing in its number: 1) %s"):format(packageConfig.packages[1])
-- This is all a silly workaround to place commas correctly
for i = 2, #packageConfig.packages do
pkgAskText = pkgAskText .. (", %d) %s"):format(i, packageConfig.packages[i])
end
print(pkgAskText)
::RETRY::
local packageSel = terminal.read()
if not tonumber(packageSel) or tonumber(packageSel) % 1 ~= 0 then
-- Is there really no better way to check for an int..?
print("\27[93mNon-integer received - try again\27[0m")
goto RETRY
end
if tonumber(packageSel) < 1 or tonumber(packageSel) > #packageConfig.packages then
print("\27[93mInteger out of range - try again\27[0m")
end
packages[i] = packageConfig.packages[tonumber(packageSel)]
i = i - 1
goto SKIP
end
if fs.exists(("/ag2/pkg/%s.json"):format(packages[i])) then
print(("\27[93mPackage %s is already installed, skipping\27[0m"):format(packages[i]))
table.remove(packages, i)
i = i - 1
goto SKIP
end
if packageConfig.dependencies then
for _, dependency in ipairs(packageConfig.dependencies) do
table.insert(packages, i + 1, dependency)
dependencyCounter = dependencyCounter + 1
end
end
-- TODO: Add checks for conflicting packages
::SKIP::
end
elseif command == "remove" then
::JUMPBACK::
local doJumpBack = false
for i = 1, #packages do
if not fs.exists(("/ag2/pkg/%s.json"):format(packages[i])) then
if parsed.s or parsed.source then
source = parsed.s or parsed.source
else
source = registry[package]
end
if source then
local packageConfig = getServersidePackageConfig(source, packages[i])
if packageConfig then
if packageConfig.type == "virtual-package" or packageConfig.type == "group" then
table.remove(packages, i)
for _, groupPackage in ipairs(packageConfig.packages) do
table.insert(packages, groupPackage)
goto GOAHEAD
end
end
end
end
print(("\27[93mPackage %s is not installed, skipping\27[0m"):format(packages[i]))
table.remove(packages, i)
i = i - 1
::GOAHEAD::
end
end
-- I was originally gonna add this in the dependency cascade section, but realized it could shorten the normal dependency check code a bit
local dependencyList = {}
for _, packageConfig in ipairs(fs.list("/ag2/pkg/")) do
local package = packageConfig:sub(1, -6)
-- I'm not adding error handling here because if this fails then fuck you for touching the files by hand and good luck figuring this shit out
local _, data = getFile(("/ag2/pkg/%s.json"):format(package))
data = json.decode(data)
dependencyList[package] = data.dependencies
end
for _, package in ipairs(packages) do
if dependencyList[package] then
-- Check if all the deps are no longer needed and if they're auto-installed and stuff
for _, dependency in pairs(dependencyList[package]) do
if fs.exists(("/ag2/pkg/%s.json"):format(dependency)) then
local _, data = getFile(("/ag2/pkg/%s.json"):format(dependency))
data = json.decode(data)
if data.autoInstalled
and not table.find(packages, dependency) -- Just to prevent dependency loops and issues when re-checking the packages after jumpback
then
dependencyCounter = dependencyCounter + 1
table.insert(packages, dependency)
end
else
-- It could still be a group or a vpackage, and checking that is the job of the loop 2 loops back
table.insert(packages, dependency)
doJumpBack = true
end
end
end
end
-- Check for cascading dependencies
for packageName, dependencies in pairs(dependencyList) do
if not table.find(packages, packageName) then
for _, dependency in ipairs(dependencies) do
if table.find(packages, dependency) then
if parsed.flags.cascade or parsed.flags.C then
table.insert(packages, packageName)
dependencyCounter = dependencyCounter + 1
doJumpBack = true
-- Listen, I'm so sorry for this abhorrent bullshit code, but the newly added packages have to get checked one way or another and hopefully this is readable enough.
else
-- The Pyramids of Giza were built entirely out of silver. No they weren't...
print(("\27[93mPackage %s is depended on by %s, cannot uninstall without --cascade\27[0m"):format(dependency, packageName))
failure = true
end
end
end
end
end
if doJumpBack then
goto JUMPBACK
-- IT'S NOT SPAGHETTI SHUT UP SHUT UP SHU
end
end
-- TODO: Add checks for the other commands
if #packages == 0 then
print("\27[93mNo packages selected.\n\27[0mExiting.")
return
end
if failure then
print("Exiting.")
return
end
if command == "install" then
if dependencyCounter == 1 then
print("\27[93m1 dependency pulled in.\27[0m")
elseif dependencyCounter >= 2 then
print(("\27[93m%d dependencies pulled in.\27[0m"):format(dependencyCounter))
end
print("Packages that will be installed:")
print(table.concat(packages, ", "))
local answer = terminal.read({prefix = "\nContinue? [Y/n] "})
if answer:lower() == "n" then
print("Exiting.")
return
end
for _, package in ipairs(packages) do
local source
if parsed.s or parsed.source then
source = parsed.s or parsed.source
else
source = registry[package]
end
print(("Installing %s..."):format(package))
local _, data = getFile(fs.concat(source, "/ag2.json"))
local packageConfig = json.decode(data)[package]
if packageConfig.directories then
for _, directory in ipairs(packageConfig.directories) do
print((" Creating directory %s..."):format(directory))
fs.makeDirectory(directory)
end
end
if packageConfig.files then
for _, file in ipairs(packageConfig.files) do
::RETRY::
print((" Downloading file %s..."):format(file))
local success, data = getFile(fs.concat(source, file))
if not success then
print(("\27[91mFailed to get file '%s' of package '%s': " .. data .. "\27[0m"):format(file, package))
local answer = terminal.read({prefix = "Abort, Retry, Skip? [a/R/s]"})
if answer:lower() == "a" then
print("Exiting.")
return
elseif answer:lower() == "s" then
print((" \27[93mSkipped file %s.\27[0m"):format(file))
goto SKIP
else
goto RETRY
end
end
if fs.exists(file) then
print(("\27[93mFile '%s' already exists.\27[0m"):format(file))
local answer = terminal.read({prefix = "Abort, Overwrite, Skip? [a/O/s]"})
if answer:lower() == "a" then
print("Exiting.")
return
elseif answer:lower() == "s" then
print((" \27[93mSkipped file %s.\27[0m"):format(file))
goto SKIP
end
end
local handle, errorMessage = fs.open(file, "w")
if not handle then
print(("\27[91mFailed to open write handle to file '%s': " .. errorMessage .. "\27[0m"):format(file))
local answer = terminal.read({prefix = "Abort, Retry, Skip? [a/R/s]"})
if answer:lower() == "a" then
print("Exiting.")
return
elseif answer:lower() == "s" then
print((" \27[93mSkipped file %s.\27[0m"):format(file))
goto SKIP
else
goto RETRY
end
end
local success, errorMessage = handle:write(data)
if not success then
handle:close()
print(("\27[91mFailed to write to file '%s': " .. errorMessage .. "\27[0m"):format(file))
local answer = terminal.read({prefix = "Abort, Retry, Skip? [a/R/s]"})
if answer:lower() == "a" then
print("Exiting.")
return
elseif answer:lower() == "s" then
print((" \27[93mSkipped file %s.\27[0m"):format(file))
goto SKIP
else
goto RETRY
end
end
handle:close()
::SKIP::
end
end
print(" Writing tracking file...")
if not fs.exists("/ag2/pkg/") then
fs.makeDirectory("/ag2/pkg/")
-- Technically this would break if /ag2/pkg/ was a file, but... why would it be a file?
end
-- TODO: Make functions for reading from and writing to a file with error handling since this is really repetitive
::RETRY::
local handle, errorMessage = fs.open(("/ag2/pkg/%s.json"):format(package), "w")
if not handle then
print(("\27[91mFailed to open write handle to file '/ag2/pkg/%s.json': " .. errorMessage .. "\27[0m"):format(package))
local answer = terminal.read({prefix = "Abort, Retry, Skip? [a/R/s]"})
if answer:lower() == "a" then
print("Exiting.")
return
elseif answer:lower() == "s" then
print((" \27[93mSkipped file /ag2/pkg/%s.json.\27[0m"):format(package))
goto SKIP
else
goto RETRY
end
end
local packageData = {
name = package,
version = packageConfig.version,
autoInstalled = false,
-- TODO: Make the above actually work
dependencies = packageConfig.dependencies,
conflicts = packageConfig.conflicts,
files = packageConfig.files,
directories = packageConfig.directories,
config = packageConfig.config
}
local trackingFile = json.encode(packageData)
local success, errorMessage = handle:write(trackingFile)
if not success then
handle:close()
print(("\27[91mFailed to write to file '/ag2/pkg/%s.json': " .. errorMessage .. "\27[0m"):format(package))
local answer = terminal.read({prefix = "Abort, Retry, Skip? [a/R/s]"})
if answer:lower() == "a" then
print("Exiting.")
return
elseif answer:lower() == "s" then
print((" \27[93mSkipped file /ag2/pkg/%s.json.\27[0m"):format(package))
goto SKIP
else
goto RETRY
end
else
handle:close()
end
::SKIP::
end
elseif command == "remove" then
if dependencyCounter == 1 then
print("\27[93m1 orphaned dependency will be removed.\27[0m")
elseif dependencyCounter >= 2 then
print(("\27[93m%d orphaned dependencies will be removed.\27[0m"):format(dependencyCounter))
end
print("Packages that will be removed:")
print(table.concat(packages, ", "))
local answer = terminal.read({prefix = "\nContinue? [Y/n] "})
if answer:lower() == "n" then
print("Exiting.")
return
end
for _, package in ipairs(packages) do
-- See line 263
local _, data = getFile(("/ag2/pkg/%s.json"):format(package))
data = json.decode(data)
if data.files then
for _, file in ipairs(data.files) do
print((" Removing file %s..."):format(file))
fs.remove(file) -- I cannot think of how this could fail. If you can, please add error handling here
end
end
if data.directories then
for _, directory in ipairs(data.directories) do
if fs.isDirectory(directory) then
if next(fs.list(directory)) == nil then
-- Apparently THAT's the best way to check if a table is empty in Lua. Alright I guess.
print((" Removing directory %s..."):format(directory))
fs.remove(directory)
else
print((" Directory %s specified by package %s still has something in it, skipping"):format(directory))
end
end
end
end
print(" Removing tracking file...")
fs.remove(("/ag2/pkg/%s.json"):format(package)) -- See line 500
end
end
print("Operation completed successfully.")
+30 -33
View File
@@ -9,7 +9,7 @@ if not command then
return
end
if not component.list("internet")() then
print("\27[91mThis program requires an internet card to run.")
print("\27[91mThis program requires an internet card to run.\27[0m")
return
end
local internet = component.internet
@@ -69,12 +69,12 @@ local function getAgConfig(package, source)
source = source or agReg[package]
local data, errorMessage = getFile(source .. "argentum.cfg")
if not data or data == "" then
print("\27[91mCould not fetch Ag config: " .. (errorMessage or "returned nil data"))
print("\27[91mCould not fetch Ag config: " .. (errorMessage or "returned nil data") .. "\27[0m")
return false
end
local func, errorMessage = load(data, "=argentum.cfg", "bt", {})
if not func then
print("\27[91mCould not fetch Ag config: " .. errorMessage .. "\nPlease contact the package owner.")
print("\27[91mCould not fetch Ag config: " .. errorMessage .. "\nPlease contact the package owner.\27[0m")
return false
end
local agcfg
@@ -82,22 +82,22 @@ local function getAgConfig(package, source)
agcfg = func()
end)
if not status then
print("\27[91mCould not fetch Ag config: " .. errorMessage .. "\nPlease contact the package owner.")
print("\27[91mCould not fetch Ag config: " .. errorMessage .. "\nPlease contact the package owner.\27[0m")
return false
end
if not agcfg[package] or not agcfg[package].maindir or not agcfg[package].directories or not agcfg[package].files or not agcfg[package].version then
local response = ("\27[91mAg config of " .. package .. " is improperly configured.\nPlease contact the package owner.")
local response = "\27[91mAg config of " .. package .. " is improperly configured.\nPlease contact the package owner.\27[0m"
end
return agcfg
end
local function doChecks(package)
if not agReg[package] and not source then
print("\27[91mPackage " .. package .. " does not exist.")
print("\27[91mPackage " .. package .. " does not exist.\27[0m")
return false
end
if fs.exists("/argentum/store/" .. package) then
print("\27[91mPackage " .. package .. " is already installed.")
print("\27[91mPackage " .. package .. " is already installed.\27[0m")
return false
end
agcfg = getAgConfig(package, source)
@@ -107,7 +107,7 @@ local function doChecks(package)
if agcfg[package].dependencies then
for _, dependency in ipairs(agcfg[package].dependencies) do
if not agReg[dependency] and not agcfg[dependency] then
local response = terminal.read({prefix = "\27[91mPackage " .. package .. " requires dependency " .. dependency .. " that does not exist.\n[A - Abort/s - Skip]"})
local response = terminal.read({prefix = "\27[91mPackage " .. package .. " requires dependency " .. dependency .. " that does not exist.\n[A - Abort/s - Skip]\27[0m"})
if response:lower() ~= "s" then
fs.remove("/argentum/store/" .. package)
return false
@@ -132,24 +132,21 @@ local function lpad(str, len, char)
return string.rep(char, len - #str) .. str
end
local gpu = component.gpu
local width,height = gpu.getResolution()
local function progress(package,progress)
local info = string.format("%s %s%%",package,lpad(math.floor(progress*100),2))
local width, height = terminal.getResolution()
info=info..string.rep(" ",width-#info)
local progX = math.floor(progress*width)
gpu.setBackground(0x00FF00)
gpu.setForeground(0x000000)
gpu.set(1,height,info:sub(1,progX))
gpu.setBackground(0x000000)
gpu.setForeground(0xFFFFFF)
gpu.set(progX+1,height,info:sub(progX+1))
local function progress(package, progress)
terminal.write("\x1b[s")
local info = string.format("%s %s%%", package, lpad(math.floor(progress * 100), 2))
info = info .. string.rep(" ", width - #info)
local progX = math.floor(progress * width)
terminal.write("\x1b[42m\x1b[30m" .. info:sub(1, progX) .. "\x1b[40m\x1b[37m" .. info:sub(progX + 1) .. "\x1b[0m")
terminal.write("\x1b[u")
end
local function clearProgress()
gpu.setBackground(0x000000)
gpu.fill(1,height,width,1," ")
terminal.write("\x1b[s")
terminal.write("\r\x1b[40m" .. string.rep(" ", width) .. "\x1b[0m\r")
terminal.write("\x1b[u")
end
local function installPackage(package, overwriteFlag)
@@ -166,7 +163,7 @@ local function installPackage(package, overwriteFlag)
if agcfg[package].dependencies then
for _, dependency in ipairs(agcfg[package].dependencies) do
if not agReg[dependency] and not agcfg[dependency] then
local response = terminal.read({prefix = "\27[91mPackage " .. package .. " requires dependency " .. dependency .. " that does not exist.\n[A - Abort/s - Skip]"})
local response = terminal.read({prefix = "\27[91mPackage " .. package .. " requires dependency " .. dependency .. " that does not exist.\n[A - Abort/s - Skip]\27[0m"})
if response:lower() ~= "s" then
fs.remove("/argentum/store/" .. package)
return false
@@ -218,7 +215,7 @@ local function installPackage(package, overwriteFlag)
else
packageStore = packageStore .. "\nA" .. file
end
local handle = fs.open(file, "w")
local handle, err = fs.open(file, "w")
handle:write(data)
handle:close()
::skip::
@@ -234,7 +231,7 @@ end
local function removePackage(package)
print("Removing " .. package .. "...")
if not fs.exists("/argentum/store/" .. package .. "/package.cfg") then
print("\27[91mLocal Ag config of " .. package .. " does not exist.")
print("\27[91mLocal Ag config of " .. package .. " does not exist.\27[0m")
return false
end
local handle, data, tmpdata = fs.open("/argentum/store/" .. package .. "/package.cfg", "r"), "", nil
@@ -344,7 +341,7 @@ if command == "install" then
handle:write(newRegistry)
handle:close()
else
print("\27[91mFailed to fetch Ag registry: " .. (errorMessage or "returned nil"))
print("\27[91mFailed to fetch Ag registry: " .. (errorMessage or "returned nil") .. "\27[0m")
end
agReg = require("/argentum/registry.cfg")
while true do
@@ -402,19 +399,19 @@ elseif command == "remove" then
end
while true do
if not fs.exists("/argentum/store/" .. packages[i]) then
print("\27[91mPackage " .. packages[i] .. " is not installed.")
print("\27[91mPackage " .. packages[i] .. " is not installed.\27[0m")
table.insert(fails, packages[i])
table.remove(packageList, table.find(packageList, packages[i]))
table.remove(packages, table.find(packages, packages[i]))
i = i - 1
elseif packages[i] == "halyde" then -- yes, this stuff is hard-coded.
print("\27[91mFor obvious reasons, you can't uninstall Halyde.")
print("\27[91mFor obvious reasons, you can't uninstall Halyde.\27[0m")
table.insert(fails, packages[i])
table.remove(packageList, table.find(packageList, packages[i]))
table.remove(packages, table.find(packages, packages[i]))
i = i - 1
elseif packages[i] == "argentum" then
print("\27[91mFor obvious reasons, you can't uninstall Argentum.")
print("\27[91mFor obvious reasons, you can't uninstall Argentum.\27[0m")
table.insert(fails, packages[i])
table.remove(packageList, table.find(packageList, packages[i]))
table.remove(packages, table.find(packages, packages[i]))
@@ -495,7 +492,7 @@ elseif command == "update" then
handle:write(newRegistry)
handle:close()
else
print("\27[91mFailed to fetch Ag registry: " .. (errorMessage or "returned nil"))
print("\27[91mFailed to fetch Ag registry: " .. (errorMessage or "returned nil") .. "\27[0m")
end
agReg = require("/argentum/registry.cfg")
if not packages[1] then
@@ -541,7 +538,7 @@ elseif command == "update" then
table.remove(packages, table.find(packages, packages[i]))
i = i - 1
else
print(packages[i].." is out of date [\x1b[93m"..version.."\x1b[39m < \x1b[92m"..agcfg[packages[i]].version.."\x1b[39m]")
print(packages[i].." is out of date [\x1b[93m"..version.."\x1b[0m < \x1b[92m"..agcfg[packages[i]].version.."\x1b[0m")
end
end
i = i + 1
@@ -630,7 +627,7 @@ elseif command == "info" then
handle:write(newRegistry)
handle:close()
else
print("\27[91mFailed to fetch Ag registry: " .. (errorMessage or "returned nil"))
print("\27[91mFailed to fetch Ag registry: " .. (errorMessage or "returned nil") .. "\27[0m")
end
agReg = require("/argentum/registry.cfg")
if not agReg[packages[1]] and not source then
@@ -677,7 +674,7 @@ elseif command == "list" then
handle:write(newRegistry)
handle:close()
else
print("\27[91mFailed to fetch Ag registry: " .. (errorMessage or "returned nil"))
print("\27[91mFailed to fetch Ag registry: " .. (errorMessage or "returned nil") .. "\27[0m")
end
agReg = require("/argentum/registry.cfg")
local sortedPackages = {}
+1 -1
View File
@@ -9,7 +9,7 @@ cliparse.config({
})
local parsed, err = cliparse.parse(...)
if not parsed then
return print("\x1b[91m" .. err)
return print("\x1b[91m" .. err .. "\x1b[0m")
end
local freq =
+5 -5
View File
@@ -34,25 +34,25 @@ if type(args[1])=="string" then
local compID,err = getComponentID(args[1])
if not compID then
print("\x1b[91mCould not get component ID from '"..args[1].."'.")
if type(err)=="string" then print("\x1b[91m"..err) end
if type(err)=="string" then print("\x1b[91m"..err.."\x1b[0m") end
return
end
if not force then
if component.virtual.check(compID) then
return print("\x1b[91mThis component is virtual and cannot be booted from directly.\nID: "..compID)
return print("\x1b[91mThis component is virtual and cannot be booted from directly.\nID: "..compID.."\x1b[0m")
end
local type = component.type(compID)
if type~="filesystem" and type~="drive" then
return print("\x1b[91mThis component is not a storage medium.\nID: "..compID)
return print("\x1b[91mThis component is not a storage medium.\nID: "..compID.."\x1b[0m")
end
if type=="filesystem" and not fileExists(compID,"/init.lua") then
return print("\x1b[91mThis storage medium doesn't have an \"init.lua\" file.\nID: "..compID)
return print("\x1b[91mThis storage medium doesn't have an \"init.lua\" file.\nID: "..compID.."\x1b[0m")
end
end
computer.setBootAddress(compID)
if computer.getBootAddress()~=compID then
return print("\x1b[91mFailed to set the boot address.")
return print("\x1b[91mFailed to set the boot address.\x1b[0m")
end
computer.shutdown(true)
else
+20 -16
View File
@@ -1,21 +1,25 @@
local files = { ... }
local shell = require("shell")
local fs = require("filesystem")
if not files or not files[1] then
shell.run("help cat")
return
local shell = require("shell")
local args = {...}
if not args[1] then
return shell.run("help cat")
end
for _, file in ipairs(files) do
if file:sub(1, 1) ~= "/" then
file = fs.concat(shell.getWorkingDirectory(), file)
end
if not fs.exists(file) then
print("\27[91mFile does not exist.")
end
for _, file in pairs(args) do
file = shell.resolvePath(file)
local handle = fs.open(file, "r")
local data
repeat
data = handle:read(math.huge or math.maxinteger)
if handle == nil then
terminal.write("\27[91mCan't open " .. file .. "\27[0m\n")
goto continue
end
while true do
local data = handle:read(math.huge or math.maxinteger)
if data == nil then break end
terminal.write(data)
until not data
end
handle:close()
::continue::
end
+20 -9
View File
@@ -1,15 +1,26 @@
local directory = ...
local args = {...}
if args[2] then
terminal.write("\27[91mToo many arguments.\27[0m")
end
if not args[1] then
return
end
local fs = require("filesystem")
local shell = require("shell")
if not directory then
local directory = shell.resolvePath(args[1])
if not fs.exists(directory) then
terminal.write("\27[91mError: " .. directory .. ": No such file or directory\27[0m\n")
return
end
if directory:sub(1, 1) ~= "/" then
directory = fs.concat(shell.getWorkingDirectory(), directory)
end
if fs.exists(directory) and fs.isDirectory(directory) then
shell.setWorkingDirectory(fs.canonical(directory))
else
print("\27[91mNo such directory.")
if not fs.isDirectory(directory) then
terminal.write("\27[91mError: " .. directory .. ": Not a directory\27[0m\n")
return
end
shell.setWorkingDirectory(fs.canonical(directory))
+1
View File
@@ -1,2 +1,3 @@
terminal.clear()
-- truly so much going on here
-- meow
+61 -18
View File
@@ -1,27 +1,70 @@
local fromFile, toFile = ...
local fs = require("filesystem")
local shell = require("shell")
if not fromFile or not toFile then
shell.run("help cp")
local args = {...}
if not args[1] then
return shell.run("help cp")
end
if not args[2] then
terminal.write("\27[91mError: No destination\27[0m\n")
return
end
if fromFile:sub(1, 1) ~= "/" then
fromFile = fs.concat(shell.getWorkingDirectory(), fromFile)
end
if toFile:sub(1, 1) ~= "/" then
toFile = fs.concat(shell.getWorkingDirectory(), toFile)
end
if fromFile == toFile then
print("\27[91mSource and destination are the same.")
local dest = shell.resolvePath(args[#args])
if fs.isFile(dest) then
if #args ~= 2 then
terminal.write("\27[91mError: Destination is not a directory\27[0m\n")
return
end
if not fs.exists(fromFile) then
print("\27[91mSource file does not exist.")
end
local src = shell.resolvePath(args[1])
if not fs.exists(src) then
terminal.write("\27[91mError: " .. src .. ": No such file or directory\27[0m\n")
return
end
if fs.exists(toFile) and not (table.find({...}, "-o") or table.find({...}, "--overwrite")) then
print("\27[91mDestination file already exists. Run this command again with -o to overwrite it.")
end
if fs.isDirectory(src) then
terminal.write("\27[91mError: Cannot write directory " .. src .. " to file " .. dest .. "\27[0m\n")
return
end
fs.copy(src, dest)
elseif fs.isDirectory(dest) then
for i = 1, #args - 1 do
local src = shell.resolvePath(args[i])
if src == dest then
terminal.write("\27[91mError: Source and destination are the same\27[0m\n")
goto continue
end
if not fs.exists(src) then
terminal.write("\27[91mError: " .. src .. ": No such file or directory\27[0m\n")
goto continue
end
fs.copy(src, fs.concat(dest, fs.basename(src)))
::continue::
end
elseif not fs.exists(dest) then
if #args ~= 2 then
terminal.write("\27[91mError: " .. dest .. ": No such file or directory\27[0m\n")
return
end
local src = shell.resolvePath(args[1])
if not fs.exists(src) then
terminal.write("\27[91mError: " .. src .. ": No such file or directory\27[0m\n")
return
end
local destp = fs.parent(dest)
if not fs.exists(destp) then
terminal.write("\27[91mError: " .. destp .. ": No such file or directory\27[0m\n")
return
end
if not fs.isDirectory(destp) then
terminal.write("\27[91mError: " .. destp .. ": Not a directory\27[0m\n")
return
end
fs.copy(src, dest)
else
terminal.write("\27[91mUnknown error\27[0m\n")
end
fs.copy(fromFile, toFile)
+5 -5
View File
@@ -4,7 +4,7 @@ local component = require("component")
local fs = require("filesystem")
if not component.list("internet")() then
print("\27[91mThis program requires an internet card to run.")
print("\27[91mThis program requires an internet card to run.\27[0m")
return
end
@@ -26,11 +26,11 @@ local status, errorMessage = pcall(function()
request:finishConnect()
end)
if not status then
print("\27[91mDownload failed: " .. errorMessage)
print("\27[91mDownload failed: " .. errorMessage .. "\27[0m")
end
local responseCode = request:response()
if responseCode and responseCode ~= 200 then
print("\27[91mDownload failed: " .. tostring(responseCode))
print("\27[91mDownload failed: " .. tostring(responseCode) .. "\27[0m")
end
repeat
tmpdata = request.read(math.huge)
@@ -41,9 +41,9 @@ local saveLocationOK = false
repeat
saveLocation = terminal.read(nil, "File save location: ", fs.concat(require("shell").getWorkingDirectory(), url:match("/([^/]+)$")))
if fs.isDirectory(saveLocation) then
print("\27[91mThe specified location is a directory.")
print("\27[91mThe specified location is a directory.\27[0m")
elseif fs.exists(saveLocation) then
local answer = terminal.read({prefix = "\27[91mThere is already a file at the specified directory. Overwrite it? [Y/n]"})
local answer = terminal.read({prefix = "\27[91mThere is already a file at the specified directory. Overwrite it? [Y/n]\27[0m"})
if answer:lower() ~= "n" then
saveLocationOK = true
end
+3 -4
View File
@@ -16,8 +16,7 @@ local tab = " "
--local ocelot = component.ocelot
local function rawset(x, y, text)
terminal.setCursorPos(x,y)
terminal.write(text, false)
terminal.write("\x1b[".. tostring(y) .. ";" .. tostring(x) .. "H" .. text)
end
local filestring, filepath, handle, data, tmpdata
@@ -261,7 +260,7 @@ local function save()
gpu.setBackground(0xFFFFFF)
gpu.setForeground(0)
gpu.set(1, height - 1, string.rep(" ", width))
terminal.setCursorPos(1, height - 1)
rawset(1, height - 1)
local savepath = terminal.read({prefix = "\27[107m\27[30mSave location: ", defaultText = filepath})
gpu.setBackground(0xFFFFFF)
gpu.setForeground(0)
@@ -300,7 +299,7 @@ while true do
renderFlag, cursorRenderFlag, specialKey = processEvent(args)
if specialKey == "x" then
if changesMade then
terminal.setCursorPos(1, height - 1)
rawset(1, height - 1)
local response = terminal.read({prefix = "\27[107m\27[30mWould you like to save changes? [Y/n] "})
if response:lower() ~= "n" then
save()
+38 -55
View File
@@ -2,82 +2,65 @@ local component = require("component")
local computer = require("computer")
local filesystem = require("filesystem")
local function convert(value, fromUnit, toUnit)
local units = {B = 1, KiB = 1024, MiB = 1024^2, GiB = 1024^3}
return value * units[fromUnit] / units[toUnit]
end
local function printstat(text)
local cursorPosX, cursorPosY = terminal.getCursorPos()
terminal.setCursorPos(35, cursorPosY)
terminal.write(text .. "\n", false)
terminal.write("\27[35G" .. text .. "\n")
end
local logo = ""
local handle, tmpdata = filesystem.open("/halyde/config/oslogo.ans", "r"), nil
repeat
tmpdata = handle:read(math.inf or math.maxinteger)
tmpdata = handle:read(math.huge)
logo = logo .. (tmpdata or "")
until not tmpdata
handle:close()
terminal.write(logo)
terminal.write("\27[17A")
terminal.write(logo, false)
local cursorPosX, cursorPosY = terminal.getCursorPos()
terminal.setCursorPos(cursorPosX, cursorPosY - 17)
printstat("\27[92mOS\27[0m: " .. _OSVERSION)
printstat("\27[92mArchitecture\27[0m: " .. _VERSION)
local componentCounter = 0
for _, _ in component.list() do
for _ in component.list() do
componentCounter = componentCounter + 1
end
printstat("\27[92mComponents\27[0m: " .. tostring(componentCounter))
printstat("\27[92mCoroutines\27[0m: " .. tostring(#tsched.getTasks()))
printstat(
"\27[92mBattery\27[0m: " .. tostring(math.floor(computer.energy() / computer.maxEnergy() * 1000 + 0.5) / 10) .. "%"
)
printstat("\27[92mBattery\27[0m: " .. tostring(math.floor(computer.energy() / computer.maxEnergy() * 1000 + 0.5) / 10) .. "%")
local totalMemory = computer.totalMemory()
local usedMemory = computer.totalMemory() - computer.freeMemory()
local totalMemoryString
if convert(totalMemory, "B", "GiB") >= 1 then
totalMemoryString = tostring(math.floor(convert(totalMemory, "B", "GiB") * 100 + 0.5) / 100) .. " GiB"
elseif convert(totalMemory, "B", "MiB") >= 1 then
totalMemoryString = tostring(math.floor(convert(totalMemory, "B", "MiB") * 100 + 0.5) / 100) .. " MiB"
elseif convert(totalMemory, "B", "KiB") >= 1 then
totalMemoryString = tostring(math.floor(convert(totalMemory, "B", "KiB") * 100 + 0.5) / 100) .. " KiB"
else
totalMemoryString = tostring(totalMemory) .. " B"
local function formatBytes(bytes)
if convert(bytes, "B", "GiB") >= 1 then
return tostring(math.floor(convert(bytes, "B", "GiB") * 100 + 0.5) / 100) .. " GiB"
elseif convert(bytes, "B", "MiB") >= 1 then
return tostring(math.floor(convert(bytes, "B", "MiB") * 100 + 0.5) / 100) .. " MiB"
elseif convert(bytes, "B", "KiB") >= 1 then
return tostring(math.floor(convert(bytes, "B", "KiB") * 100 + 0.5) / 100) .. " KiB"
else
return tostring(bytes) .. " B"
end
end
local usedMemoryString
if convert(usedMemory, "B", "GiB") >= 1 then
usedMemoryString = tostring(math.floor(convert(usedMemory, "B", "GiB") * 100 + 0.5) / 100) .. " GiB"
elseif convert(usedMemory, "B", "MiB") >= 1 then
usedMemoryString = tostring(math.floor(convert(usedMemory, "B", "MiB") * 100 + 0.5) / 100) .. " MiB"
elseif convert(usedMemory, "B", "KiB") >= 1 then
usedMemoryString = tostring(math.floor(convert(usedMemory, "B", "KiB") * 100 + 0.5) / 100) .. " KiB"
else
usedMemoryString = tostring(usedMemory) .. " B"
end
printstat("\27[92mMemory\27[0m: " .. usedMemoryString .. " / " .. totalMemoryString)
printstat("\27[92mMemory\27[0m: " .. formatBytes(usedMemory) .. " / " .. formatBytes(totalMemory))
local totalDisk = component.invoke(computer.getBootAddress(), "spaceTotal")
local usedDisk = component.invoke(computer.getBootAddress(), "spaceUsed")
local totalDiskString
if convert(totalDisk, "B", "GiB") >= 1 then
totalDiskString = tostring(math.floor(convert(totalDisk, "B", "GiB") * 100 + 0.5) / 100) .. " GiB"
elseif convert(totalDisk, "B", "MiB") >= 1 then
totalDiskString = tostring(math.floor(convert(totalDisk, "B", "MiB") * 100 + 0.5) / 100) .. " MiB"
elseif convert(totalDisk, "B", "KiB") >= 1 then
totalDiskString = tostring(math.floor(convert(totalDisk, "B", "KiB") * 100 + 0.5) / 100) .. " KiB"
else
totalDiskString = tostring(totalDisk) .. " B"
end
local usedDiskString
if convert(usedDisk, "B", "GiB") >= 1 then
usedDiskString = tostring(math.floor(convert(usedDisk, "B", "GiB") * 100 + 0.5) / 100) .. " GiB"
elseif convert(usedDisk, "B", "MiB") >= 1 then
usedDiskString = tostring(math.floor(convert(usedDisk, "B", "MiB") * 100 + 0.5) / 100) .. " MiB"
elseif convert(usedDisk, "B", "KiB") >= 1 then
usedDiskString = tostring(math.floor(convert(usedDisk, "B", "KiB") * 100 + 0.5) / 100) .. " KiB"
else
usedDiskString = tostring(usedDisk) .. " B"
end
printstat("\27[92mDisk\27[0m: " .. usedDiskString .. " / " .. totalDiskString)
local width, height = component.invoke(component.list("gpu")(), "getResolution")
printstat("\27[92mDisk\27[0m: " .. formatBytes(usedDisk) .. " / " .. formatBytes(totalDisk))
local gpuComponent = component.list("gpu")()
local width, height = component.invoke(gpuComponent, "getResolution")
printstat("\27[92mResolution\27[0m: " .. tostring(width) .. "x" .. tostring(height) .. "\n")
printstat("\27[40m \27[41m \27[42m \27[43m \27[44m \27[45m \27[46m \27[47m ")
printstat("\27[100m \27[101m \27[102m \27[103m \27[104m \27[105m \27[106m \27[107m ")
local cursorPosX, cursorPosY = terminal.getCursorPos()
terminal.setCursorPos(cursorPosX, cursorPosY + 5)
terminal.write("\27[5B\27[0m")
+261 -33
View File
@@ -1,40 +1,268 @@
local shell = require("shell")
local fs = require("filesystem")
local args = {...}
local command = args[1]
args = nil
if not command then
local handle, data, tmpdata = fs.open("/halyde/apps/helpdb/default.txt", "r"), "", nil
repeat
tmpdata = handle:read(math.huge or math.maxinteger)
data = data .. (tmpdata or "")
until not tmpdata
print(data)
local shell = require("shell")
local arg = ... or "default"
local what = arg
local aliases = shell.getAliases()
if aliases[what] then
what = aliases[what]
end
local path = "/halyde/apps/helpdb/" .. what
if not fs.exists(path) then
print("Could not find help file for: " .. arg .. ".")
return
end
local aliases = shell.getAliases()
if aliases[command] then
command = aliases[command]
if path == "/halyde/apps/helpdb/default" then
return shell.run("cat " .. path) -- smh
end
if fs.exists("/halyde/apps/helpdb/" .. command .. ".txt") then
local handle, data, tmpdata = fs.open("/halyde/apps/helpdb/" .. command .. ".txt", "r"), "", nil
repeat
tmpdata = handle:read(math.huge or math.maxinteger)
data = data .. (tmpdata or "")
until not tmpdata
print(data)
-- local aliases = table.copy(shell.aliases)
if table.find(aliases, command) then
local aliasIndex = table.find(aliases, command)
local aliasString = "Aliases:\n " .. aliasIndex
aliases[aliasIndex] = nil
while table.find(aliases, command) do
aliasIndex = table.find(aliases, command)
aliasString = aliasString .. ", " .. aliasIndex
aliases[aliasIndex] = nil
local handle = fs.open(path, "r")
local data = {
command = "",
usage = "",
description = "",
args = {},
examples = {}
}
while true do
local line = ""
while true do
local char = handle:read(1)
if not char then
if line == "" then
line = nil
break
end
print(aliasString)
break
end
if char == "\n" then
break
end
if char == "\r" then
local next_char = handle:read(1)
if next_char and next_char == "\n" then
break
elseif next_char then
local pos = file:seek("cur")
if pos then
file:seek("set", pos - 1)
end
break
end
break
end
line = line .. char
end
if line == nil then
break
end
line = line:match("^%s*(.-)%s*$")
if line then
local key, value = line:match("^(%w+)%s+(.*)$")
if not key then goto continue end
if key:lower() == "command" then
data.command = value
end
if key:lower() == "usage" then
data.usage = value
end
if key:lower() == "description" then
data.description = value
end
if key:lower():match("^arg%d+$") then
local num = key:lower():match("^arg(%d+)$")
if not data.args[tonumber(num)] then data.args[tonumber(num)] = {} end
data.args[tonumber(num)].name = value
end
if key:lower():match("^arg%d+description$") then
local num = key:lower():match("^arg(%d+)description$")
if not data.args[tonumber(num)] then data.args[tonumber(num)] = {} end
data.args[tonumber(num)].description = value
end
if key:lower():match("^arg%d+sub%d+$") then
local main_num, sub_num = key:lower():match("^arg(%d+)sub(%d+)$")
if main_num and sub_num then
if not data.args[tonumber(main_num)] then data.args[tonumber(main_num)] = {} end
if not data.args[tonumber(main_num)].subflags then data.args[tonumber(main_num)].subflags = {} end
if not data.args[tonumber(main_num)].subflags[tonumber(sub_num)] then
data.args[tonumber(main_num)].subflags[tonumber(sub_num)] = {}
end
data.args[tonumber(main_num)].subflags[tonumber(sub_num)].name = value
end
end
if key:lower():match("^arg%d+sub%d+description$") then
local main_num, sub_num = key:lower():match("^arg(%d+)sub(%d+)description$")
if main_num and sub_num then
if not data.args[tonumber(main_num)] then data.args[tonumber(main_num)] = {} end
if not data.args[tonumber(main_num)].subflags then data.args[tonumber(main_num)].subflags = {} end
if not data.args[tonumber(main_num)].subflags[tonumber(sub_num)] then
data.args[tonumber(main_num)].subflags[tonumber(sub_num)] = {}
end
data.args[tonumber(main_num)].subflags[tonumber(sub_num)].description = value
end
end
if key:lower():match("^example%d+$") then
local num = key:lower():match("^example(%d+)$")
if not data.examples[tonumber(num)] then data.examples[tonumber(num)] = {} end
data.examples[tonumber(num)].name = value
end
if key:lower():match("^example%d+description$") then
local num = key:lower():match("^example(%d+)description$")
if not data.examples[tonumber(num)] then data.examples[tonumber(num)] = {} end
data.examples[tonumber(num)].description = value
end
::continue::
end
else
print("Could not find help file for: " .. command .. ".")
end
handle:close()
--print(require("serialize")(data, "\t"))
-- Halyde terminal doesn't support bold (CSI 1 m) but who cares
if data.command then
terminal.write("\27[1mUsage: \27[0m\n")
terminal.write(" \27[96m" .. data.command)
if data.usage then
terminal.write("\27[93m " .. data.usage)
end
terminal.write("\27[0m\n\n")
end
local width, height = terminal.getResolution()
local function wrap_text(text, indent)
if not text then return "" end
local words = {}
for word in text:gmatch("%S+") do
table.insert(words, word)
end
local lines = {}
local current_line = ""
for i, word in ipairs(words) do
if #current_line + #word + 1 <= width * 0.66 - indent then
if current_line == "" then
current_line = word
else
current_line = current_line .. " " .. word
end
else
table.insert(lines, current_line)
current_line = word
end
end
if current_line ~= "" then
table.insert(lines, current_line)
end
local result = {}
for i, line in ipairs(lines) do
if i == 1 then
table.insert(result, line)
else
table.insert(result, string.rep(" ", indent) .. line)
end
end
return table.concat(result, "\n")
end
if data.description then
terminal.write("\27[1mDescription:\27[0m\n")
terminal.write(" " .. wrap_text(data.description, 2))
terminal.write("\n\n")
end
if #data.args > 0 then
terminal.write("\27[1mArguments:\27[0m\n")
local max_len = 0
for _, flag in ipairs(data.args) do
if flag.name then
max_len = math.max(max_len, #flag.name)
end
for _, subf in ipairs(flag.subflags or {}) do
if subf.name then
max_len = math.max(max_len, #subf.name + 2)
end
end
end
for _, flag in ipairs(data.args) do
terminal.write(" \27[93m" .. (flag.name or "") .. "\27[0m" .. string.rep(" ", max_len - (flag.name and #flag.name or 0) + 2) .. wrap_text(flag.description, 4 + max_len) .. "\n")
for _, subf in ipairs(flag.subflags or {}) do
terminal.write(" \27[92m" .. (subf.name or "") .. "\27[0m" .. string.rep(" ", max_len - (subf.name and #subf.name or 0)) .. wrap_text(subf.description, 4 + max_len) .. "\n")
end
end
terminal.write("\n")
end
local function formatExampleName(name, utility)
if not name then return name end
local contains = false
if name:find(utility, 1, true) then
contains = true
else
for alias, cmd in pairs(aliases) do
if cmd == utility and name:find(alias, 1, true) then
contains = true
break
end
end
end
if not contains then
return "\27[92m" .. name
end
local formatted = name
formatted = formatted:gsub("(" .. utility .. ")", "\27[96m%1\27[92m")
for alias, cmd in pairs(aliases) do
if cmd == utility then
formatted = formatted:gsub("(" .. alias .. ")", "\27[96m%1\27[92m")
end
end
return formatted
end
if #data.examples > 0 then
terminal.write("\27[1mExamples:\27[0m\n")
local max_len = 0
for _, flag in ipairs(data.examples) do
max_len = math.max(max_len, #flag.name)
end
for _, flag in ipairs(data.examples) do
terminal.write(" " .. formatExampleName(flag.name, arg) .. "\27[0m" .. string.rep(" ", max_len - #flag.name + 2) .. wrap_text(flag.description, 4 + max_len) .. "\n")
end
terminal.write("\n")
end
local first = true
for k, v in pairs(aliases) do
if v == arg then
if first then
terminal.write("\27[1mAliases:\27[0m\n ")
end
terminal.write("\27[96m" .. k)
if not first then
terminal.write("\27[0m, ")
end
first = false
end
end
if not first then
terminal.writec(0xa)
end
+47
View File
@@ -0,0 +1,47 @@
COMMAND ag2
USAGE [COMMAND] [PACKAGES] [FLAGS]
DESCRIPTION Uses the Argentum 2 package manager.
ARG1 COMMAND
ARG1SUB1 install
ARG1SUB2 remove
ARG1SUB3 update
ARG1SUB4 list
ARG1SUB5 repo-list
ARG1SUB6 repo-add
ARG1SUB7 repo-remove
ARG1SUB8 info
ARG2 PACKAGES
ARG3 FLAGS
ARG3SUB1 -x, --exclude-deps
ARG3SUB2 -u, --update-repos
ARG3SUB3 -f, --force
ARG3SUB4 -c, --clean
ARG3SUB5 -s, --source [URL]
ARG3SUB6 -C, --cascade
ARG1DESCRIPTION Specifies the operation for Argentum 2 to do.
ARG1SUB1DESCRIPTION Installs packages.
ARG1SUB2DESCRIPTION Removes packages.
ARG1SUB3DESCRIPTION Updates packages.
ARG1SUB4DESCRIPTION Lists all available packages.
ARG1SUB5DESCRIPTION Lists all installed repositories.
ARG1SUB6DESCRIPTION Adds a custom repository.
ARG1SUB7DESCRIPTION Removes a repository.
ARG1SUB8DESCRIPTION Shows a packages version, description and other relevant information.
ARG2DESCRIPTION Packages to apply operations to.
ARG3DESCRIPTION These flags are available and can be inserted anywhere
ARG3SUB1DESCRIPTION Ignore dependencies. WARNING: Using this can and will leave you with broken packages. Use it at your own risk and only when truly necessary.
ARG3SUB2DESCRIPTION Update the list of repositories.
ARG3SUB3DESCRIPTION Force the operation, even if there are conflicts or unresolvable dependencies. WARNING: Using this can and will leave you with broken packages. Use it at your own risk and only when truly necessary.
ARG3SUB4DESCRIPTION Clean up now-unnecessary packages (previous dependencies).
ARG3SUB5DESCRIPTION Use a custom source for the operation.
ARG3SUB6DESCRIPTION When removing a package that other packages depend on, remove those packages too instead of aborting.
EXAMPLE1 ag2 install halyde
EXAMPLE2 ag2 list
EXAMPLE3 ag2 info halyde
EXAMPLE4 ag2 remove -x edit
EXAMPLE5 ag2 remove -c hal-draw
EXAMPLE1DESCRIPTION Installs the halyde package.
EXAMPLE2DESCRIPTION Lists all packages.
EXAMPLE3DESCRIPTION Shows information about the halyde package.
EXAMPLE4DESCRIPTION Removes edit, but does not remove any packages that depend on it.
EXAMPLE5DESCRIPTION Removes hal-draw and any dependencies that are no longer needed.
+29
View File
@@ -0,0 +1,29 @@
COMMAND argentum
USAGE [COMMAND] [PACKAGES]
DESCRIPTION Uses the Argentum package manager.
ARG1 COMMAND
ARG1SUB1 install
ARG1SUB2 remove
ARG1SUB3 update
ARG1SUB4 list
ARG1SUB5 search
ARG1SUB6 info
ARG2 PACKAGES
ARG1DESCRIPTION Specifies the operation for Ag to do.
ARG1SUB1DESCRIPTION Installs packages.
ARG1SUB2DESCRIPTION Removes packages.
ARG1SUB3DESCRIPTION Updates packages.
ARG1SUB4DESCRIPTION Lists all available packages.
ARG1SUB5DESCRIPTION Searches all available packages.
ARG1SUB6DESCRIPTION Shows information on a specific package.
ARG2DESCRIPTION Packages to apply operations to.
EXAMPLE1 ag install hal-draw
EXAMPLE2 ag list
EXAMPLE3 ag info hal-draw
EXAMPLE4 ag update hal-draw
EXAMPLE5 ag update hal-draw
EXAMPLE1DESCRIPTION Installs the hal-draw package.
EXAMPLE2DESCRIPTION Lists all packages.
EXAMPLE3DESCRIPTION Shows information about hal-draw.
EXAMPLE4DESCRIPTION Updates the hal-draw package if it's not at the newest version.
EXAMPLE5DESCRIPTION Updates all packages.
-18
View File
@@ -1,18 +0,0 @@
Usage: argentum [COMMAND] [PACKAGES]
Uses the Argentum package manager.
COMMAND Specifies the operation for Ag to do.
install Installs packages.
remove Removes packages.
update Updates packages.
list Lists all available packages.
search Searches all available packages.
info Shows information on a specific package.
PACKAGES* Packages to apply operations to.
Examples:
ag install hal-draw Installs the hal-draw package.
ag list Lists all packages.
ag info hal-draw Shows information about hal-draw.
ag update hal-draw Updates the hal-draw package if it's not at the newest version.
ag update Updates all packages.
+8
View File
@@ -0,0 +1,8 @@
COMMAND beep
USAGE [FLAGS]
DESCRIPTION Make the computer beep.
ARG1 FLAGS
ARG1SUB1 -f, --frequency
ARG1SUB2 -t, --time
ARG1SUB1DESCRIPTION Specifies the frequency, in Hz. Defaults to 440Hz.
ARG1SUB2DESCRIPTION Specifies how long, in seconds, the computer should beep. Defaults to 0.1s.
-6
View File
@@ -1,6 +0,0 @@
Usage: beep [FLAGS]
Make the computer beep.
FLAGS
-f, --frequency Specifies the frequency, in Hz. Defaults to 440Hz.
-t, --time Specifies how long, in seconds, the computer should beep. Defaults to 0.1s.
+23
View File
@@ -0,0 +1,23 @@
COMMAND boot
USAGE [ADDRESS] [FLAGS]
DESCRIPTION Restarts and automatically boots into any storage medium. Meant to be used for systems using a Lua BIOS EEPROM.
ARG1 ADDRESS
ARG1SUB1 hdd1
ARG1SUB2 hdd2
ARG1SUB3 floppy
ARG1SUB4
ARG2 FLAGS
ARG2SUB1 -f, --force
ARG1DESCRIPTION The storage medium to boot to.
ARG1SUB1DESCRIPTION The first hard drive inserted in the computer.
ARG1SUB2DESCRIPTION The second hard drive inserted in the computer.
ARG1SUB3DESCRIPTION The floppy disk that is inserted in the computer.
ARG1SUB4DESCRIPTION The ID of the component, abbreviated. Must have three or more characters.
ARG2DESCRIPTION Specifies extra options when executing the command.
ARG2SUB1DESCRIPTION Forces booting into the storage medium.
EXAMPLE1 boot hdd1
EXAMPLE2 boot hdd2
EXAMPLE3 boot floppy
EXAMPLE1DESCRIPTION Boot into the first hard drive inserted in the computer.
EXAMPLE2DESCRIPTION Boot into the second hard drive inserted in the computer.
EXAMPLE3DESCRIPTION Boot into the floppy disk inserted in the comuter.
-15
View File
@@ -1,15 +0,0 @@
Usage: boot [ADDRESS] [FLAGS]
Restarts and automatically boots into any storage medium. Meant to be used for systems using a Lua BIOS EEPROM.
ADDRESS The storage medium to boot to.
hdd1 The first hard drive inserted in the computer.
hdd2 The second hard drive inserted in the computer.
floppy The floppy disk that is inserted in the computer.
The ID of the component, abbreviated. Must have three or more characters.
FLAGS Specifies extra options when executing the command.
-f, --force Forces booting into the storage medium.
Examples:
boot hdd1 Boot into the first hard drive inserted in the computer.
boot hdd2 Boot into the second hard drive inserted in the computer.
boot floppy Boot into the floppy disk inserted in the comuter.
+9
View File
@@ -0,0 +1,9 @@
COMMAND cat
USAGE [FILES]...
DESCRIPTION Concatenates and prints a file.
ARG1 FILES
ARG1DESCRIPTION Specifies the paths to the files to print.
EXAMPLE1 cat /init.lua
EXAMPLE2 cat help.lua cat.lua
EXAMPLE1DESCRIPTION Concatenates and prints init.lua in the root directory.
EXAMPLE2DESCRIPTION Concatenates and prints help.lua and cat.lua in the current working directory.
-8
View File
@@ -1,8 +0,0 @@
Usage: cat [FILES]...
Concatenates and prints a file.
FILES Specifies the paths to the files to print.
Examples:
cat /init.lua Concatenates and prints init.lua in the root directory.
cat help.lua cat.lua Concatenates and prints help.lua and cat.lua in the current working directory.
+13
View File
@@ -0,0 +1,13 @@
COMMAND cd
USAGE [PATH]
DESCRIPTION Sets the shell working directory.
ARG1 PATH
ARG1DESCRIPTION Specifies the path to set the shell working directory to.
EXAMPLE1 cd /home/
EXAMPLE2 cd halyde
EXAMPLE3 cd ..
EXAMPLE4 ..
EXAMPLE1DESCRIPTION Sets the shell working directory to /home/.
EXAMPLE2DESCRIPTION Sets the shell working directory to a directory named "halyde" in the current working directory.
EXAMPLE3DESCRIPTION Sets the shell working directory back one directory.
EXAMPLE4DESCRIPTION Equivalent of "cd ..".
-10
View File
@@ -1,10 +0,0 @@
Usage: cd [PATH]
Sets the shell working directory.
PATH Specifies the path to set the shell working directory to.
Examples:
cd /home/ Sets the shell working directory to /home/.
cd halyde Sets the shell working directory to a directory named "halyde" in the current working directory.
cd .. Sets the shell working directory back one directory.
.. Equivalent of "cd ..".
+4
View File
@@ -0,0 +1,4 @@
COMMAND clear
DESCRIPTION Clears the screen.
EXAMPLE1 clear
EXAMPLE1DESCRIPTION Clears the screen.
-5
View File
@@ -1,5 +0,0 @@
Usage: clear
Clears the screen.
Examples:
clear Clears the screen.
+11
View File
@@ -0,0 +1,11 @@
COMMAND cp
USAGE [SOURCES]... [DESTINATION]
DESCRIPTION Copy files and directories.
ARG1 SOURCES
ARG2 DESTINATION
ARG1DESCRIPTION Specifies the files and directories to be copied.
ARG2DESCRIPTION Specifies the path or a directory to copy to.
EXAMPLE1 cp /home/a.txt /b.txt
EXAMPLE2 cp c.lua /halyde/apps .
EXAMPLE1DESCRIPTION Copies the file at /home/a.txt to /b.txt.
EXAMPLE2DESCRIPTION Copies c.lua and /halyde/apps to the shell working directory.
-11
View File
@@ -1,11 +0,0 @@
Usage: cp [FLAGS] [SOURCE] [DESTINATION]
Copies a file.
FLAGS Specifies extra options when executing the command.
-o, --overwrite Allows any file that might be at the destination to be overwritten.
SOURCE Specifies the file to be copied.
DESTINATION Specifies the path to copy the file to.
Examples:
cp /home/a.txt /b.txt Copies the file at /home/a.txt to /b.txt.
cp -o c.lua d.txt Copies the file c.lua to another file called d.txt in the shell working directory, overwriting any file that might be there.
+7
View File
@@ -0,0 +1,7 @@
COMMAND download
USAGE [URL]
DESCRIPTION Downloads a file from the internet.
ARG1 URL
ARG1DESCRIPTION Specifies the URL from which to download the file from.
EXAMPLE1 download https://github.com/
EXAMPLE1DESCRIPTION Downloads github.com.
-7
View File
@@ -1,7 +0,0 @@
Usage: download [URL]
Downloads a file from the internet.
URL Specifies the URL from which to download the file from.
Examples:
download https://github.com/ Downloads github.com.
+9
View File
@@ -0,0 +1,9 @@
COMMAND echo
USAGE [TEXT]...
DESCRIPTION Concatenates and prints text to the terminal.
ARG1 TEXT
ARG1DESCRIPTION Text to print.
EXAMPLE1 echo test
EXAMPLE2 echo Hello World!
EXAMPLE1DESCRIPTION Prints "test" to the terminal.
EXAMPLE2DESCRIPTION Prints "Hello World!" to the terminal.
-8
View File
@@ -1,8 +0,0 @@
Usage: echo [TEXT]...
Concatenates and prints text to the terminal.
TEXT Text to print.
Examples:
echo test Prints "test" to the terminal.
echo Hello World! Prints "Hello World!" to the terminal.
+9
View File
@@ -0,0 +1,9 @@
COMMAND edit
USAGE [PATH]
DESCRIPTION Opens a file with the text editor, or a new blank file if not specified.
ARG1 PATH
ARG1DESCRIPTION Specifies the file to be opened.
EXAMPLE1 edit
EXAMPLE2 edit /LICENSE
EXAMPLE1DESCRIPTION Opens a new blank file in the text editor.
EXAMPLE2DESCRIPTION Opens /LICENSE in the text editor.
-8
View File
@@ -1,8 +0,0 @@
Usage: edit [PATH]
Opens a file with the text editor, or a new blank file if not specified.
PATH* Specifies the file to be opened.
Examples:
edit Opens a new blank file in the text editor.
edit /LICENSE Opens /LICENSE in the text editor.
+4
View File
@@ -0,0 +1,4 @@
COMMAND fetch
DESCRIPTION Displays system information including OS version, Lua version, memory, etc.
EXAMPLE1 fetch
EXAMPLE1DESCRIPTION Displays system information.
-5
View File
@@ -1,5 +0,0 @@
Usage: fetch
Displays system information including OS version, Lua version, memory, etc.
Examples:
fetch Displays system information.
+9
View File
@@ -0,0 +1,9 @@
COMMAND help
USAGE [COMMAND]
DESCRIPTION Displays info on the command specified, or a list of commands if one is not specified.
ARG1 COMMAND
ARG1DESCRIPTION Command to display information on.
EXAMPLE1 help
EXAMPLE2 help cp
EXAMPLE1DESCRIPTION Displays a list of all default commands available.
EXAMPLE2DESCRIPTION Displays information about the cp command.
-8
View File
@@ -1,8 +0,0 @@
Usage: help [COMMAND]
Displays info on the command specified, or a list of commands if one is not specified.
COMMAND* Command to display information on.
Examples:
help Displays a list of all default commands available.
help cp Displays information about the cp command.
+24
View File
@@ -0,0 +1,24 @@
COMMAND label
USAGE [ADDRESS] [LABEL]
DESCRIPTION Get or set a label of a component that supports labelling.
ARG1 ADDRESS
ARG1SUB1 eeprom
ARG1SUB2 halyde
ARG1SUB3 slotN
ARG1SUB4 #N
ARG2 LABEL
ARG1DESCRIPTION The component to use for getting or setting the label.
ARG1SUB1DESCRIPTION The computer's EEPROM.
ARG1SUB2DESCRIPTION The drive where the Halyde installation resides in.
ARG1SUB3DESCRIPTION The slot number of the drive, in top-to-bottom order (range 7-9)
ARG1SUB4DESCRIPTION The slot number of the drive, in drive space (range 1-3)
ARG1SUB5DESCRIPTION The ID of the component, abbreviated. Must have three or more characters.
ARG2DESCRIPTION The label to set the component to. If not found, the current label will be printed out.
EXAMPLE1 label #3
EXAMPLE2 label eeprom
EXAMPLE3 label slot8 Storage
EXAMPLE4 label halyde Halyde
EXAMPLE1DESCRIPTION Get the label of the third drive in the computer.
EXAMPLE2DESCRIPTION Get the label of the EEPROM inserted in the computer.
EXAMPLE3DESCRIPTION Set the drive at slot 8 to have the label "Storage"
EXAMPLE4DESCRIPTION Set the label of the Halyde installation to "Halyde"
-16
View File
@@ -1,16 +0,0 @@
Usage: label [ADDRESS] [LABEL]
Get or set a label of a component that supports labelling.
ADDRESS The component to use for getting or setting the label.
eeprom The computer's EEPROM.
halyde The drive where the Halyde installation resides in.
slotN The slot number of the drive, in top-to-bottom order (range 7-9)
#N The slot number of the drive, in drive space (range 1-3)
The ID of the component, abbreviated. Must have three or more characters.
LABEL* The label to set the component to. If not found, the current label will be printed out.
Examples:
label #3 Get the label of the third drive in the computer.
label eeprom Get the label of the EEPROM inserted in the computer.
label slot8 Storage Set the drive at slot 8 to have the label "Storage"
label halyde Halyde Set the label of the Halyde installation to "Halyde"
+19
View File
@@ -0,0 +1,19 @@
COMMAND log
USAGE [OPERATION] [ARGS]
DESCRIPTION Tool to manage system logs.
ARG1 OPERATION
ARG1SUB1 view [LOG]
ARG1SUB2 list
ARG1SUB3 clear [LOG]
ARG1SUB4 info/warn/error [LOG] [TEXT]
ARG2 ARGS
ARG1DESCRIPTION Operation to do with the system logs.
ARG1SUB1DESCRIPTION View a log file.
ARG1SUB2DESCRIPTION List all logs.
ARG1SUB3DESCRIPTION Clear a log file, or all if none specified.
ARG1SUB4DESCRIPTION Create a log entry for the specified log at the specified log level.
ARG2DESCRIPTION Arguments (specified under OPERATION)
EXAMPLE1 log view example
EXAMPLE2 log list
EXAMPLE3 log clear example
EXAMPLE4 log info example This is an example.
-15
View File
@@ -1,15 +0,0 @@
Usage: log [OPERATION] [ARGS]
Tool to manage system logs.
OPERATION Operation to do with the system logs.
view [LOG] View a log file.
list List all logs.
clear [LOG*] Clear a log file, or all if none specified.
info/warn/error [LOG] [TEXT] Create a log entry for the specified log at the specified log level.
ARGS Arguments (specified under OPERATION)
Examples:
log view example
log list
log clear example
log info example This is an example.
+11
View File
@@ -0,0 +1,11 @@
COMMAND ls
USAGE [PATH]...
DESCRIPTION Lists all files and directories in the specified path, or in the shell working directory if the path isn't specified. Directories are shown in yellow, executable files are shown in green, and other files are shown in white.
ARG1 PATH
ARG1DESCRIPTION Path to the directories to list files and subdirectories from.
EXAMPLE1 ls
EXAMPLE2 ls /halyde
EXAMPLE3 ls apps
EXAMPLE1DESCRIPTION Lists all files and directories from the current shell working directory.
EXAMPLE2DESCRIPTION Lists all files and directories from /halyde.
EXAMPLE3DESCRIPTION Lists all files and directories from the apps directory in the shell working directory.
-10
View File
@@ -1,10 +0,0 @@
Usage: ls [PATH]
Lists all files and directories in the specified path, or in the shell working directory if the path isn't specified.
Directories are shown in yellow, executable files are shown in green, and other files are shown in white.
PATH* Path to the folder to list files and directories from.
Examples:
ls Lists all files and directories from the current shell working directory.
ls /halyde Lists all files and directories from /halyde.
ls apps Lists all files and directories from the apps directory in the shell working directory.
+4
View File
@@ -0,0 +1,4 @@
COMMAND lscor
DESCRIPTION Lists every active coroutine by ID and name.
EXAMPLE1 lscor
EXAMPLE1DESCRIPTION Lists every active coroutine by ID and name.
-5
View File
@@ -1,5 +0,0 @@
Usage: lscor
Lists every active coroutine by ID and name.
Examples:
lscor Lists every active coroutine by ID and name.
+37
View File
@@ -0,0 +1,37 @@
COMMAND lsdrv
USAGE [FLAGS]
DESCRIPTION Shows all drives that are inserted into the computer.
ARG1 FLAGS
ARG1SUB1 -a, --all
ARG1SUB2 -o, --output [COLS]
ARG1SUB3 -s, --show [EXPR]
ARG1SUB4 -S, --sort [EXPR]
ARG1SUB5 EXPR
ARG2 PACKAGES
ARG1DESCRIPTION Specifies extra options when executing the command.
ARG1SUB1DESCRIPTION Shows every column and every component. Acts the same as '-o all -s all'. Possible columns are: "slot", "capacity", "managed", "readOnly", "id", "mount", "bootable", and "label". If the list of columns start with a "+", the default columns will appear first. Default columns are slots, capacity, the entire ID, the mount point, and the drive label.
ARG1SUB2DESCRIPTION Specifies the columns to output in the output table.
ARG1SUB3DESCRIPTION Only list drives when the expression returns 'true'.
ARG1SUB4DESCRIPTION Sort the output by an expression that returns a number. The higher the number, the lower the drive is displayed, and vice-versa.
ARG1SUB5DESCRIPTION An expression in Lua, for filtering or sorting output. If this expression contains spaces, make sure to put quotation marks on them!
ARG1SUB6DESCRIPTION Built-in variables are: "component", "computer", "type", "id", "readonly", "capacity", "managed", "eeprom", "halyde", "tmp", "proxy", "slot", and "all" (true).
EXAMPLE1 lsdrv
EXAMPLE2 lsdrv -a
EXAMPLE3 lsdrv -o +bootable
EXAMPLE4 lsdrv -o slot,label -s halyde
EXAMPLE5 lsdrv -o mount,capacity,label -s "not halyde"
EXAMPLE6 lsdrv -s type=='filesystem'
EXAMPLE7 lsdrv -s slot==1
EXAMPLE8 lsdrv -S capacity
EXAMPLE9 lsdrv -S -capacity
EXAMPLE10 lsdrv -o +managed -S managed
EXAMPLE1DESCRIPTION Show regular drives, with the default columns.
EXAMPLE2DESCRIPTION Show all storage components, with every column.
EXAMPLE3DESCRIPTION Show drives, with an added "bootable" category.
EXAMPLE4DESCRIPTION Show the slot and the label of the drive where Halyde is installed.
EXAMPLE5DESCRIPTION Show the mount points, capacities and labels of all drives other than Halyde.
EXAMPLE6DESCRIPTION Only show managed drives.
EXAMPLE7DESCRIPTION Show all drives that aren't physical (Virtual components, tmpfs)
EXAMPLE8DESCRIPTION Sort the drives by capacity, in ascending order.
EXAMPLE9DESCRIPTION Sort the drives by capacity, in descending order.
EXAMPLE10DESCRIPTION Show managed drives first, then unmanaged drives second, with an extra "managed" column.
-27
View File
@@ -1,27 +0,0 @@
Usage: lsdrv [FLAGS]
Shows all drives that are inserted into the computer.
FLAGS Specifies extra options when executing the command.
-a, --all Shows every column and every component. Acts the same as '-o all -s all'.
-o, --output [COLS] Specifies the columns to output in the output table.
Possible columns are: "slot", "capacity", "managed", "readOnly", "id", "mount", "bootable", and "label".
If the list of columns start with a "+", the default columns will appear first.
Default columns are slots, capacity, the entire ID, the mount point, and the drive label.
-s, --show [EXPR] Only list drives when the expression returns 'true'.
-S, --sort [EXPR] Sort the output by an expression that returns a number.
The higher the number, the lower the drive is displayed, and vice-versa.
EXPR An expression in Lua, for filtering or sorting output.
If this expression contains spaces, make sure to put quotation marks on them!
Built-in variables are: "component", "computer", "type", "id", "readonly", "capacity", "managed", "eeprom", "halyde", "tmp", "proxy", "slot", and "all" (true).
Examples:
lsblk Show regular drives, with the default columns.
lsblk -a Show all storage components, with every column.
lsblk -o +bootable Show drives, with an added "bootable" category.
lsblk -o slot,label -s halyde Show the slot and the label of the drive where Halyde is installed.
lsblk -o mount,capacity,label -s "not halyde" Show the mount points, capacities and labels of all drives other than Halyde.
lsblk -s type=='filesystem' Only show managed drives.
lsblk -s slot==1 Show all drives that aren't physical (Virtual components, tmpfs)
lsblk -S capacity Sort the drives by capacity, in ascending order.
lsblk -S -capacity Sort the drives by capacity, in descending order.
lsblk -o +managed -S managed Show managed drives first, then unmanaged drives second, with an extra "managed" column.
+4
View File
@@ -0,0 +1,4 @@
COMMAND lua
DESCRIPTION Starts the Lua shell, where you can type commands to interpret them in real time.
EXAMPLE1 lua
EXAMPLE1DESCRIPTION Starts the Lua shell.
-5
View File
@@ -1,5 +0,0 @@
Usage: lua
Starts the Lua shell, where you can type commands to interpret them in real time.
Examples:
lua Starts the Lua shell.
+2
View File
@@ -0,0 +1,2 @@
COMMAND maindrv
DESCRIPTION Shows the entire ID of the drive where Halyde is installed to.
-2
View File
@@ -1,2 +0,0 @@
Usage: maindrv
Shows the entire ID of the drive where Halyde is installed to.
+7
View File
@@ -0,0 +1,7 @@
COMMAND mkdir
USAGE [PATH]...
DESCRIPTION Makes a directory.
ARG1 PATH
ARG1DESCRIPTION Specifies the path to create the directory in.
EXAMPLE1 mkdir a
EXAMPLE1DESCRIPTION Creates a directory named a in the current shell working directory.
-7
View File
@@ -1,7 +0,0 @@
Usage: mkdir [PATH]
Makes a directory.
PATH Specifies the path to create the directory in.
Examples:
mkdir a Creates a directory named a in the current shell working directory.
+11
View File
@@ -0,0 +1,11 @@
COMMAND mv
USAGE [SOURCE].. [DESTINATION]
DESCRIPTION Moves/renames a file.
ARG1 SOURCE
ARG2 DESTINATION
ARG1DESCRIPTION Specifies the files and directories to be moved.
ARG2DESCRIPTION Specifies the path or a directory to copy to.
EXAMPLE1 mv /home/a.txt /b.txt
EXAMPLE2 mv ../c.lua /halyde/apps .
EXAMPLE1DESCRIPTION Moves the file at /home/a.txt to /b.txt.
EXAMPLE2DESCRIPTION Moves c.lua from a subdirectory and /halyde/apps to the shell working directory.
-11
View File
@@ -1,11 +0,0 @@
Usage: mv [FLAGS] [SOURCE] [DESTINATION]
Moves/renames a file.
FLAGS Specifies extra options when executing the command.
-o, --overwrite Allows any file that might be at the destination to be overwritten.
SOURCE Specifies the file to be moved/renamed.
DESTINATION Specifies the path/filename to move/rename the file to.
Examples:
mv /home/a.txt /b.txt Moves the file at /home/a.txt to /b.txt.
mv -o c.lua d.txt Renames the file c.lua to another file called d.txt in the shell working directory, overwriting any file that might be there.
+4
View File
@@ -0,0 +1,4 @@
COMMAND reboot
DESCRIPTION Reboots the computer.
EXAMPLE1 reboot
EXAMPLE1DESCRIPTION Reboots the computer.
-5
View File
@@ -1,5 +0,0 @@
Usage: reboot
Reboots the computer.
Examples:
reboot Reboots the computer.
+11
View File
@@ -0,0 +1,11 @@
COMMAND res
USAGE [FLAGS]
DESCRIPTION Gets or sets the current resolution.
ARG1 FLAGS
ARG1SUB1 [no flags]
ARG1SUB2 -x [number]
ARG1SUB3 -y [number]
ARG1DESCRIPTION Specifies extra options when executing the command.
ARG1SUB1DESCRIPTION Displays the current and maximum resolution.
ARG1SUB2DESCRIPTION Displays the current and maximum resolution on the x-axis. A new x-axis resolution can be specified as a number.
ARG1SUB3DESCRIPTION Displays the current and maximum resolution on the y-axis. A new y-axis resolution can be specified as a number.
-9
View File
@@ -1,9 +0,0 @@
Usage: res [FLAGS]
Gets or sets the current resolution.
FLAGS* Specifies extra options when executing the command.
[no flags] Displays the current and maximum resolution.
-x [number*] Displays the current and maximum resolution on the x-axis.
A new x-axis resolution can be specified as a number.
-y [number*] Displays the current and maximum resolution on the y-axis.
A new y-axis resolution can be specified as a number.
+7
View File
@@ -0,0 +1,7 @@
COMMAND rm
USAGE [PATH]...
DESCRIPTION Removes files and directories.
ARG1 PATH
ARG1DESCRIPTION Specifies the files and directories to be moved/renamed.
EXAMPLE1 rm a.txt
EXAMPLE1DESCRIPTION Removes a.txt in the current shell working directory.
-7
View File
@@ -1,7 +0,0 @@
Usage: rm [PATH]
Removes files and directories.
PATH Specifies the file to be moved/renamed.
Examples:
rm a.txt Removes a.txt in the current shell working directory.
+4
View File
@@ -0,0 +1,4 @@
COMMAND shutdown
DESCRIPTION Shuts down the computer.
EXAMPLE1 shutdown
EXAMPLE1DESCRIPTION Shuts down the computer.
-5
View File
@@ -1,5 +0,0 @@
Usage: shutdown
Shuts down the computer.
Examples:
shutdown Shuts down the computer.
+5
View File
@@ -0,0 +1,5 @@
COMMAND touch
USAGE [FILE]...
DESCRIPTION Create an empty file.
ARG1 FILE
ARG1DESCRIPTION The path of the files to create.
-6
View File
@@ -1,6 +0,0 @@
Usage: touch [FLAGS] [FILE]
Creates a file with empty content.
FLAGS Specifies extra options when executing the command.
-o, --overwrite Allows emptying out a file if it already exists.
FILE The path of the file to create.²
+9 -9
View File
@@ -1,7 +1,7 @@
local component = require("component")
local computer = require("computer")
local args = {...}
if not args then return print("\x1b[91mCannot get arguments.") end
if not args then return print("\x1b[91mCannot get arguments.\x1b[0m") end
if not args[1] then
return require("shell").run("help label")
end
@@ -29,27 +29,27 @@ elseif inputID:sub(1,1)=="#" and tonumber(inputID:sub(2)) then
componentFromSlot(slotNum)
elseif #inputID>=3 then
local fullID = component.get(inputID)
if not fullID then return print("\x1b[91mCould not find entire component ID from \""..inputID.."\".") end
if not fullID then return print("\x1b[91mCould not find entire component ID from \""..inputID.."\".\x1b[0m") end
comp = component.proxy(fullID)
else
print("\x1b[91mAddress must have atleast 3 characters")
print("\x1b[91mAddress must have atleast 3 characters\x1b[0m")
return require("shell").run("help label")
end
if not comp then
return print("\x1b[91mCould not find component from \""..inputID.."\".")
return print("\x1b[91mCould not find component from \""..inputID.."\".\x1b[0m")
end
local compID = comp.address
local function formatID(id)
return id:sub(1,8).."\x1b[37m"..id:sub(9).."\x1b[39m"
return id:sub(1,8).."\x1b[37m"..id:sub(9).."\x1b[0m"
end
local function unsupported(act)
print("This \x1b[92m"..(comp.type or "unknown").."\x1b[39m component doesn't support "..act.." labels.\nID: "..formatID(compID))
print("This \x1b[92m"..(comp.type or "unknown").."\x1b[0m component doesn't support "..act.." labels.\nID: "..formatID(compID).."\x1b[0m")
end
local function compError(act,reason)
print("\x1b[91mAn error occured while "..act.." the label of this component.\nComponent: "..(compID or "unknown id").." ("..((comp or {}).type or "unknown type")..")\n\n"..reason)
print("\x1b[91mAn error occured while "..act.." the label of this component.\nComponent: "..(compID or "unknown id").." ("..((comp or {}).type or "unknown type")..")\n\n"..reason.."\x1b[0m")
end
local function formatLabel(label)
@@ -67,7 +67,7 @@ if type(args[2])~="string" then
label = comp.getLabel()
end)
if success then
print("Label of "..formatID(compID)..((comp.type and comp.type~="filesystem") and " ("..comp.type..")" or "")..":\n \x1b[92m"..formatLabel(label).."\x1b[39m")
print("Label of "..formatID(compID)..((comp.type and comp.type~="filesystem") and " ("..comp.type..")" or "")..":\n \x1b[92m"..formatLabel(label).."\x1b[0m")
else
compError("getting",reason)
end
@@ -85,7 +85,7 @@ else
label = comp.setLabel(newLabel)
end)
if success then
print("Successfully set label of "..formatID(compID)..(comp.type and " ("..comp.type..")" or "").." to:\n \x1b[92m"..formatLabel(label).."\x1b[39m")
print("Successfully set label of "..formatID(compID)..(comp.type and " ("..comp.type..")" or "").." to:\n \x1b[92m"..formatLabel(label).."\x1b[0m")
else
compError("setting",reason)
end
+4 -4
View File
@@ -27,9 +27,9 @@ local function viewlog(logname)
entry = string.sub(entry, 1, -1)
end
if entry:sub(1, 4) == "WARN" then
print("\x1b[93m" .. entry)
print("\x1b[93m" .. entry .. "\x1b[0m")
elseif entry:sub(1, 5) == "ERROR" then
print("\x1b[91m" .. entry)
print("\x1b[91m" .. entry .. "\x1b[0m")
else
print(entry)
end
@@ -58,9 +58,9 @@ local function listlogs2()
print("Found \x1b[93m" .. #logs .. "\x1b[0m logs.")
for i in ipairs(logs) do
if i == #logs then
print("\x1b[93m└ \x1b[0m" .. logs[i] .. "\x1b[90m.log")
print("\x1b[93m└ \x1b[0m" .. logs[i] .. "\x1b[90m.log\x1b[0m")
else
print("\x1b[93m├ \x1b[0m" .. logs[i] .. "\x1b[90m.log")
print("\x1b[93m├ \x1b[0m" .. logs[i] .. "\x1b[90m.log\x1b[0m")
end
end
end
+48 -61
View File
@@ -1,71 +1,58 @@
local args = {...}
local target = args[1]
args = nil
local fs = require("filesystem")
local unicode = require("unicode")
local maxLength = 0
local margin = 2 -- minimum space between filename and size
local dirTable = {}
local fileTable = {}
local workingDirectory = require("shell").getWorkingDirectory()
local shell = require("shell")
if target then
if target:sub(1, 1) ~= "/" then
target = fs.concat(workingDirectory, target)
end
else
target = workingDirectory
local function formatSize(size, isDir)
if isDir then return "[DIR]" end
if size >= 1024^3 then return string.format("%.1fGiB", size / 1024^3) end
if size >= 1024^2 then return string.format("%.1fMiB", size / 1024^2) end
if size >= 1024 then return string.format("%.1fKiB", size / 1024) end
return size.."B"
end
local files = fs.list(target)
local function getFileColor(name, isDir)
if isDir then return "\27[93m" end
if name:match("%.lua$") then return "\27[92m" end
return "\27[0m"
end
if files then
local args = {...}
if not args[1] then
args = {require("shell").getWorkingDirectory()}
end
for _, path in pairs(args) do
path = shell.resolvePath(path)
local files = fs.list(path)
if not files then
terminal.write("\27[91mError: " .. path .. ": No such file or directory\27[0m\n")
goto continue
end
local fileList = {}
for _, file in pairs(files) do
if file:sub(-1, -1) == "/" then
table.insert(dirTable, file)
file = file:sub(1, -2)
else
table.insert(fileTable, file)
local isDir = file:sub(-1) == "/"
local name = isDir and file:sub(1, -2) or file
local size = isDir and 0 or fs.size(fs.concat(path, file))
table.insert(fileList, {name = name, isDir = isDir, size = size})
end
if unicode.wlen(file) > maxLength then
maxLength = unicode.wlen(file)
end
end
table.sort(dirTable)
table.sort(fileTable)
files = {}
for _, v in ipairs(dirTable) do
table.insert(files, v)
end
for _, v in ipairs(fileTable) do
table.insert(files, v)
end
dirTable, fileTable = nil, nil
for _, file in ipairs(files) do
local dir = false
local filetext
if file:sub(-1, -1) == "/" then
dir = true
filetext = "\27[93m"..file:sub(1, -2)
elseif file:find(".") and file:match("[^.]+$") == "lua" then
filetext = "\27[92m"..file
end
filetext = (filetext or file)..string.rep(" ", maxLength - unicode.wlen(file) + margin)
if dir then
print(filetext.." \27[0m[DIR]")
else
local size = fs.size(fs.concat(target, file))
local sizeString
if convert(size, "B", "GiB") >= 1 then
sizeString = tostring(math.floor(convert(size, "B", "GiB") * 100 + 0.5) / 100).." GiB"
elseif convert(size, "B", "MiB") >= 1 then
sizeString = tostring(math.floor(convert(size, "B", "MiB") * 100 + 0.5) / 100).." MiB"
elseif convert(size, "B", "KiB") >= 1 then
sizeString = tostring(math.floor(convert(size, "B", "KiB") * 100 + 0.5) / 100).." KiB"
else
sizeString = tostring(size).." B"
end
print(filetext.."\27[0m"..sizeString)
-- directories first
-- then files
table.sort(fileList, function(a, b)
if a.isDir ~= b.isDir then return a.isDir end
return a.name < b.name
end)
local maxSizeLen = 0
for _, item in ipairs(fileList) do
maxSizeLen = math.max(maxSizeLen, #formatSize(item.size, item.isDir))
end
terminal.write(path.."\n")
for _, item in ipairs(fileList) do
local sizeStr = formatSize(item.size, item.isDir)
sizeStr = string.rep(" ", maxSizeLen - #sizeStr) .. sizeStr
local color = getFileColor(item.name, item.isDir)
terminal.write(string.format("%s %s%s\27[0m\n", sizeStr, color, item.name))
end
::continue::
end
+4 -4
View File
@@ -77,7 +77,7 @@ elseif outArgIdx then
if headers[word] then
addHeader(word)
else
print("\x1b[93mCategory \""..word.."\" doesn't exist\x1b[39m")
print("\x1b[93mCategory \""..word.."\" doesn't exist\x1b[0m")
end
end
end
@@ -190,7 +190,7 @@ local function handleComponent(id,type)
if proxy.getLabel then
local clabel = proxy.getLabel()
label=clabel and serialize.string(clabel) or "None"
label=clabel and serialize(clabel) or "None"
else
label="Unsupported"
end
@@ -266,7 +266,7 @@ if not showAll then
if func then
filter(comps,func)
else
return print("\x1b[91mInvalid component filter:\n\n"..tostring(err).."\x1b[39m")
return print("\x1b[91mInvalid component filter:\n\n"..tostring(err).."\x1b[0m")
end
else
filter(comps,function(comp)
@@ -286,7 +286,7 @@ if sortArgIdx then
return (boolToNum(func(a)) or 0)<(boolToNum(func(b)) or 0)
end)
else
return print("\x1b[91mInvalid sort expression:\n\n"..tostring(err).."\x1b[39m")
return print("\x1b[91mInvalid sort expression:\n\n"..tostring(err).."\x1b[0m")
end
else
table.sort(comps,function(a,b)
+11 -8
View File
@@ -12,15 +12,18 @@ for _, lib in pairs(libList) do
local name = lib:match("(.+)%.lua")
_G[name] = require(name)
end
end, debug.traceback)
end, function(errMsg)
return errMsg .. "\n\n" .. debug.traceback()
end)
if not status then
local firstLine = tostring(err):match("^[^\n]*")
print(
string.format(
"\x1b[91mLibrary %s has failed loading:\n │ %s",
lib:match("(.+)%.lua"),
tostring(err or "unknown error"):match("^(.-)\n")
"\x1b[91mLibrary %s has failed loading:\n │ %s\x1b[0m",
lib:match("(.+)%.lua") or lib,
firstLine or "unknown error"
)
)
) -- TODO: only show first line of error
log.lua.error(
string.format(
'The library located at "%s" has failed loading:\n%s',
@@ -35,7 +38,7 @@ end
if failed then
print(
string.format(
'\x1b[93mOne or more libraries failed to load. For more information, check the log entries located at "%s".',
'\x1b[93mOne or more libraries failed to load. For more information, check the log entries located at "%s".\x1b[0m',
tostring(log.lua.logpath or "[unknown]")
)
)
@@ -59,7 +62,7 @@ while true do
returns = false
end
if not func then
return print("\x1b[91msyntax error: " .. (err or "unknown error"))
return print("\x1b[91msyntax error: " .. (err or "unknown error") .. "\x1b[0m")
end
local res = { func() }
if returns then
@@ -74,7 +77,7 @@ while true do
return errMsg .. "\n\n" .. debug.traceback()
end)
if not result then
print("\27[91m" .. reason)
print("\27[91m" .. reason .. "\27[0m")
end
end
end
+1 -1
View File
@@ -3,7 +3,7 @@
local computer = require("computer")
if type(computer)~="table" then
return print("\x1b[91mComputer library returned '"..type(computer).."' type\x1b[39m")
return print("\x1b[91mComputer library returned '"..type(computer).."' type\x1b[0m")
end
local address = computer.getBootAddress()
+19 -10
View File
@@ -1,14 +1,23 @@
local directory = ...
local fs = require("filesystem")
local shell = require("shell")
if not directory then
require("shell").run("help mkdir")
return
local args = {...}
if not args[1] then
return shell.run("help mkdir")
end
if directory:sub(1, 1) ~= "/" then
directory = fs.concat(require("shell").getWorkingDirectory(), directory)
for _, directory in pairs(args) do
directory = shell.resolvePath(directory)
if fs.exists(directory) then
terminal.write("\27[91mError: " .. directory ..": An object already exists\27[0m\n")
goto continue
end
local what, err = fs.makeDirectory(directory)
if err ~= nil then
terminal.write("\27[91mError: " .. err .. "\27[0m\n")
goto continue
end
::continue::
end
if fs.exists(directory) then
print("\27[91mAn object already exists at the specified path.")
end
fs.makeDirectory(directory)
+64 -19
View File
@@ -1,25 +1,70 @@
local fromFile, toFile = ...
local shell = require("shell")
local fs = require("filesystem")
local shell = require("shell")
if not fromFile or not toFile then
shell.run("help mv")
local args = {...}
if not args[1] then
return shell.run("help mv")
end
if not args[2] then
terminal.write("\27[91mError: No destination\27[0m\n")
return
end
if fromFile:sub(1, 1) ~= "/" then
fromFile = fs.concat(shell.getWorkingDirectory(), fromFile)
end
if toFile:sub(1, 1) ~= "/" then
toFile = fs.concat(shell.getWorkingDirectory(), toFile)
end
if fromFile == toFile then
print("\27[91mSource and destination are the same.")
end
if not fs.exists(fromFile) then
print("\27[91mSource file does not exist.")
end
if fs.exists(toFile) and not (table.find({...}, "-o") or table.find({...}, "--overwrite")) then
print("\27[91mDestination file already exists. Run this command again with -o to overwrite it.")
local dest = shell.resolvePath(args[#args])
if fs.isFile(dest) then
if #args ~= 2 then
terminal.write("\27[91mError: Destination is not a directory\27[0m\n")
return
end
local src = shell.resolvePath(args[1])
if not fs.exists(src) then
terminal.write("\27[91mError: " .. src .. ": No such file or directory\27[0m\n")
return
end
if fs.isDirectory(src) then
terminal.write("\27[91mError: Cannot write directory " .. src .. " to file " .. dest .. "\27[0m\n")
return
end
fs.rename(src, dest)
elseif fs.isDirectory(dest) then
for i = 1, #args - 1 do
local src = shell.resolvePath(args[i])
if src == dest then
terminal.write("\27[91mError: Source and destination are the same\27[0m\n")
goto continue
end
if not fs.exists(src) then
terminal.write("\27[91mError: " .. src .. ": No such file or directory\27[0m\n")
goto continue
end
fs.rename(src, fs.concat(dest, fs.basename(src)))
::continue::
end
elseif not fs.exists(dest) then
if #args ~= 2 then
terminal.write("\27[91mError: " .. dest .. ": No such file or directory\27[0m\n")
return
end
local src = shell.resolvePath(args[1])
if not fs.exists(src) then
terminal.write("\27[91mError: " .. src .. ": No such file or directory\27[0m\n")
return
end
local destp = fs.parent(dest)
if not fs.exists(destp) then
terminal.write("\27[91mError: " .. destp .. ": No such file or directory\27[0m\n")
return
end
if not fs.isDirectory(destp) then
terminal.write("\27[91mError: " .. destp .. ": Not a directory\27[0m\n")
return
end
fs.rename(src, dest)
else
terminal.write("\27[91mUnknown error\27[0m\n")
end
fs.rename(fromFile, toFile)
+15 -15
View File
@@ -9,7 +9,7 @@ local curX, curY = gpu.getResolution()
local function setRes()
if not(args[1] == "-x" or args[1] == "-y") then
print("\x1b[91mUnknown argument. \x1b[39mTry running \x1b[92m\"help res\"")
print("\x1b[91mUnknown argument. \x1b[0mTry running \x1b[92m\"help res\"\x1b[0m")
return
end
@@ -21,7 +21,7 @@ local function setRes()
x = tonumber(args[i + 1])
lastarg = "x"
else
print("\x1b[91mValue \"x\" was set more than once. \x1b[39mTry running \x1b[92m\"help res\"")
print("\x1b[91mValue \"x\" was set more than once. \x1b[0mTry running \x1b[92m\"help res\"\x1b[0m")
return
end
elseif args[i] == "-y" then
@@ -29,7 +29,7 @@ local function setRes()
y = tonumber(args[i + 1])
lastarg = "y"
else
print("\x1b[91mValue \"y\" was set more than once. \x1b[39mTry running \x1b[92m\"help res\"")
print("\x1b[91mValue \"y\" was set more than once. \x1b[0mTry running \x1b[92m\"help res\"\x1b[0m")
return
end
end
@@ -37,42 +37,42 @@ local function setRes()
if x then
if x > maxX then
print("\x1b[91mGPU does not support x higher than " .. maxX)
print("\x1b[91mGPU does not support x higher than " .. maxX .. "\x1b[0m.")
return
end
end
if y then
if y > maxY then
print("\x1b[91mGPU does not support y higher than " .. maxY)
print("\x1b[91mGPU does not support y higher than " .. maxY .. "\x1b[0m.")
return
end
end
if x and not(y) then
gpu.setResolution(x, curY)
print("Successfully set X resolution from \x1b[93m" .. curX .. "\x1b[39m to \x1b[92m" .. x .. "\x1b[39m.")
print("Successfully set X resolution from \x1b[93m" .. curX .. "\x1b[0m to \x1b[92m" .. x .. "\x1b[0m.")
return
elseif not(x) and y then
gpu.setResolution(curX, y)
print("Successfully set Y resolution from \x1b[93m" .. curY .. "\x1b[39m to \x1b[92m" .. y .. "\x1b[39m.")
print("Successfully set Y resolution from \x1b[93m" .. curY .. "\x1b[0m to \x1b[92m" .. y .. "\x1b[0m.")
return
else
gpu.setResolution(x, y)
print("Successfully set resolution from \x1b[93m" .. curX .. "x" .. curY .. "\x1b[39m to \x1b[92m" .. x .. "x" .. y .. "\x1b[39m.")
print("Successfully set resolution from \x1b[93m" .. curX .. "x" .. curY .. "\x1b[0m to \x1b[92m" .. x .. "x" .. y .. "\x1b[0m.")
return
end
end
local function getRes(val)
if val == "x" then
print("Current X resolution: \x1b[93m" .. curX)
print("Maximum supported X resolution: \x1b[92m" .. maxX)
print("Current X resolution: \x1b[93m" .. curX .. "\x1b[0m")
print("Maximum supported X resolution: \x1b[92m" .. maxX .. "\x1b[0m")
elseif val == "y" then
print("Current Y resolution: \x1b[93m" .. curY)
print("Maximum supported Y resolution: \x1b[92m" .. maxY)
print("Current Y resolution: \x1b[93m" .. curY .. "\x1b[0m")
print("Maximum supported Y resolution: \x1b[92m" .. maxY .. "\x1b[0m")
else
print("Current resolution: \x1b[93m" .. curX .. "x" .. curY)
print("Maximum supported resolution: \x1b[92m" .. maxX .. "x" .. maxY)
print("Current resolution: \x1b[93m" .. curX .. "x" .. curY .. "\x1b[0m")
print("Maximum supported resolution: \x1b[92m" .. maxX .. "x" .. maxY .. "\x1b[0m")
end
end
@@ -92,5 +92,5 @@ if axis == "-x" then
elseif axis == "-y" then
getRes("y")
else
print("\x1b[91mUnknown argument. \x1b[39mTry running \x1b[92m\"help res\"")
print("\x1b[91mUnknown argument. \x1b[0mTry running \x1b[92m\"help res\"\x1b[0m")
end
+13 -12
View File
@@ -1,16 +1,17 @@
local file = ...
local shell = require("shell")
local fs = require("filesystem")
local shell = require("shell")
if not file then
shell.run("help rm")
return
local args = {...}
if not args[1] then
return shell.run("help rm")
end
if file:sub(1, 1) ~= "/" then
file = fs.concat(shell.getWorkingDirectory(), file)
for _, file in pairs(args) do
file = shell.resolvePath(file)
local result = fs.remove(file)
if result == false then
terminal.write("\27[91mError: cannot delete " .. file .. "\27[0m\n")
end
end
if not fs.exists(file) then
print("\27[91mFile does not exist.")
return
end
fs.remove(file)
+1 -1
View File
@@ -88,4 +88,4 @@ end
end ]]
raster.free()
terminal.setCursorPos(1,1)
terminal.clear()
+12 -17
View File
@@ -1,28 +1,23 @@
-- TODO: Rename this to something else (while making an alias from the original command).
-- Touch seems kind of a silly name for a command to make a file.
-- Maybe something like mkfile would be better?
local cliparse = require("cliparse")
cliparse.config({
["o"] = 0,
["overwrite"] = 0,
})
local parsed = cliparse.parse(...)
local file = parsed.args[1]
local fs = require("filesystem")
local shell = require("shell")
if not file then
local args = {...}
if not args[1] then
return shell.run("help touch")
end
if file:sub(1, 1) ~= "/" then
file = fs.concat(shell.getWorkingDirectory(), file)
end
for _, file in pairs(args) do
file = shell.resolvePath(file)
if fs.exists(file) and not (parsed.flags.o or parsed.flags.overwrite) then
return print("\x1b[91mFile already exists.\n│ To empty file contents, use -o.")
local handle, err = fs.open(file, "a")
if err ~= nil then
terminal.write("\27[91mError: " .. err .. "\27[0m\n")
goto continue
end
handle:close()
::continue::
end
local handle = fs.open(file, "w")
handle:write("") -- just in case
handle:close()
+501 -178
View File
@@ -1,3 +1,10 @@
--[[
TODO:
```bash
echo -e "\033[?25l" # hide
echo -e "\033[?25h" # show
```
]]
local module = {}
function module.check()
@@ -9,23 +16,11 @@ function module.init()
local unicode = require("unicode")
local event = require("event")
--local ocelot = component.proxy(component.list("ocelot")())
local component = require("component")
local computer = require("computer")
local gpu = component.gpu
_G._PUBLIC.terminal = {}
local cursorPosX = 1
local cursorPosY = 1
local width, height = gpu.getResolution()
function _PUBLIC.terminal.getCursorPos()
return cursorPosX,cursorPosY
end
function _PUBLIC.terminal.setCursorPos(x,y)
checkArg(1,x,"number","nil")
checkArg(2,y,"number","nil")
if type(x)~=nil then cursorPosX=math.min(math.max(x,1),width) end
if type(y)~=nil then cursorPosY=math.min(math.max(y,1),height) end
end
local readHistory = {}
function _PUBLIC.terminal.getHistory(id)
checkArg(1,id,"string")
@@ -45,193 +40,526 @@ function module.init()
table.insert(readHistory[id],hist)
end
local ANSIColorPalette = {
local function getColorPalette(depth)
if depth == 1 then
return {
["dark"] = {
[0] = 0x000000,
[1] = 0x800000,
[2] = 0x008000,
[3] = 0x808000,
[4] = 0x000080,
[5] = 0x800080,
[6] = 0x008080,
[7] = 0xC0C0C0
[1] = 0xffffff,
[2] = 0xffffff,
[3] = 0xffffff,
[4] = 0xffffff,
[5] = 0xffffff,
[6] = 0xffffff,
[7] = 0xffffff,
},
["bright"] = {
[0] = 0x808080,
[1] = 0xFF0000,
[2] = 0x00FF00,
[3] = 0xFFFF00,
[4] = 0x0000FF,
[5] = 0xFF00FF,
[6] = 0x00FFFF,
[7] = 0xFFFFFF
[0] = 0x000000,
[1] = 0xffffff,
[2] = 0xffffff,
[3] = 0xffffff,
[4] = 0xffffff,
[5] = 0xffffff,
[6] = 0xffffff,
[7] = 0xffffff,
}
}
defaultForegroundColor = ANSIColorPalette["bright"][7]
defaultBackgroundColor = ANSIColorPalette["dark"][0]
gpu.setForeground(defaultForegroundColor)
gpu.setBackground(defaultBackgroundColor)
local function scrollDown()
if gpu.copy(1,1,width,height,0,-1) then
local prevForeground = gpu.getForeground()
local prevBackground = gpu.getBackground()
gpu.setForeground(defaultForegroundColor)
gpu.setBackground(defaultBackgroundColor)
gpu.fill(1, height, width, 1, " ")
gpu.setForeground(prevForeground)
gpu.setBackground(prevBackground)
cursorPosY=height
end
if depth == 4 then
return {
-- Closest colors to the 4 bit OC pallete
-- Better than outright failure
["dark"] = {
[0] = 0x000000, -- black
[1] = 0x663300, -- brown (dark red)
[2] = 0x336600, -- green (dark green)
[3] = 0x336600, -- green (dark yellow)
[4] = 0x333399, -- blue (dark blue)
[5] = 0x9933CC, -- purple (dark purple)
[6] = 0x333399, -- blue (dark cyan)
[7] = 0xCCCCCC -- silver (dark white)
},
["bright"] = {
[0] = 0x333333, -- gray (bright black)
[1] = 0xff3333, -- red
[2] = 0x33cc33, -- lime (green)
[3] = 0xffff33, -- yellow
[4] = 0x333399, -- blue
[5] = 0xcc66cc, -- magenta (purple)
[6] = 0x336699, -- cyan
[7] = 0xffffff -- white
}
}
end
if depth == 8 then
return {
["dark"] = {
[0] = 0x0f0f0f, -- black
[1] = 0xcc2424, -- dark red
[2] = 0x339280, -- dark green
[3] = 0x996d00, -- dark yellow
[4] = 0x004980, -- dark blue
[5] = 0x9949c0, -- dark purple
[6] = 0x33b6c0, -- dark cyan
[7] = 0xffcccc -- dark white
},
["bright"] = {
[0] = 0x666d80, -- brighter black
[1] = 0xff6d40, -- red
[2] = 0x33db80, -- green
[3] = 0xffb600, -- yellow
[4] = 0x336dff, -- blue
[5] = 0xcc6dc0, -- purple
[6] = 0x33dbc0, -- cyan
[7] = 0xffffff -- white
}
}
end
--[[ Original color palette:
{
["dark"] = {
[0] = 0x171421,
[1] = 0xc01c28,
[2] = 0x26a269,
[3] = 0xa2734c,
[4] = 0x12488b,
[5] = 0xa347ba,
[6] = 0x2aa1b3,
[7] = 0xd0cfcc
},
["bright"] = {
[0] = 0x5e5c64,
[1] = 0xf66151,
[2] = 0x33d17a,
[3] = 0xe9ad0c,
[4] = 0x2a7bde,
[5] = 0xc061cb,
[6] = 0x33c7de,
[7] = 0xffffff
}
}
]]
-- Shouldn't reach here
error()
end
local function newLine()
cursorPosX=1
cursorPosY = cursorPosY + 1
if cursorPosY>height then
scrollDown()
end
end
local ANSIColorPalette = getColorPalette(gpu.maxDepth())
local function parseCodeNumbers(code)
local o = {}
for num in code:sub(3,-2):gmatch("[^;]+") do
table.insert(o,tonumber(num))
end
return o
end
local expecting_unicode_bytes = 0
local unicode_bytes_left = 0
local unicode_codepoint = 0
local cursor = { x = 1, y = 1, X = nil, Y = nil } -- X and Y are managed by ESC s and ESC u
local printState = 0 -- 0:none 1:in ESC 2:in CSI
local color = {
FG = ANSIColorPalette["bright"][7], BG = ANSIColorPalette["dark"][0],
fg = nil, bg = nil, reverse = false
}
color.fg = color.FG
color.bg = color.BG
local current_codepoint = 0
local bytes_remaining = 0
local seq = {}
local function from8BitColor(num)
num=math.floor(num)&255
if num<16 then return 0x444444*((num>>3)&1)+(0xBB0000*((num>>2)&1)|0x00BB00*((num>>1)&1)|0x0000BB*(num&1)) end
if num>=232 then return 0x10101*(8+(num-232)*10) end
num=num-16
local palette = {0,95,135,175,215,255}
return (palette[(num//36)%6+1]<<16)|(palette[(num//6)%6+1]<<8)|palette[num%6+1]
end
local writeBuf = {}
local function from24BitColor(r,g,b)
r,g,b=math.floor(r)&255,math.floor(g)&255,math.floor(b)&255
return (r<<16)|(g<<8)|b
end
local function findCodeEnd(text,i)
local function inRange(v,min,max)
return v>=min and v<=max
end
i=i+2
while i<=#text and not inRange(text:byte(i),0x40,0x7F) do i=i+1 end
return i
end
function _PUBLIC.terminal.write(text, textWrap)
-- you don't know how tiring this was just for ANSI escape code support
if textWrap == nil then
textWrap = true
end
if not text or not tostring(text) then
return
end
if text:find("\a") then
computer.beep()
end
text = tostring(text)
text = "\27[0m" .. text:gsub("\t", " ")
local readBreak = 0
-- readBreak is for when, inside the for loop, there normally would have been an increase in the "i" variable because it has read more than one character.
-- unfortunately, changing the "i" variable would have unpredictable effects, so to not risk anything, this workaround was done.
local section = ""
local function printSection()
if #section==0 then
return
end
while true do
gpu.set(cursorPosX,cursorPosY,section)
if unicode.wlen(section) > width - cursorPosX + 1 and textWrap then
section = section:sub(width - cursorPosX + 2)
newLine()
local function update_gpu_colors()
if color.reverse then
gpu.setForeground(color.bg)
gpu.setBackground(color.fg)
else
cursorPosX = cursorPosX+unicode.wlen(section)
gpu.setForeground(color.fg)
gpu.setBackground(color.bg)
end
end
local width, height = gpu.getResolution()
function _G._PUBLIC.terminal.getResolution()
return width, height
end
gpu.setForeground(color.fg)
gpu.setBackground(color.bg)
local function scroll()
if gpu.copy(1, 2, width, height - 1, 0, -1) then
gpu.setForeground(color.FG)
gpu.setBackground(color.BG)
gpu.fill(1, height, width, 1, " ")
gpu.setForeground(color.fg)
gpu.setBackground(color.bg)
cursor.y = height
end
end
local function check_wrap_and_scroll()
if cursor.x > width then
cursor.x = 1
cursor.y = cursor.y + 1
end
while cursor.y > height do
scroll()
end
end
local function exec_csi()
local params = {}
local op = 0
local current_num = 0
local have_num = false
for i = 1, #seq do
local byte = seq[i]
if 0x30 <= byte and byte <= 0x39 then
current_num = current_num * 10 + (byte - 0x30)
have_num = true
elseif byte == 0x3b then
table.insert(params, have_num and current_num or 0)
current_num = 0
have_num = false
else
if have_num then
table.insert(params, current_num)
end
if 0x40 <= byte and byte <= 0x7e then
op = byte
end
break
end
end
section = ""
local function get_param(idx, default)
if idx <= #params and params[idx] ~= nil then
return params[idx]
end
return default
end
for i=1,#text do
if readBreak>0 then
readBreak = readBreak - 1
goto continue
if op == 0x48 or op == 0x66 then
local row = get_param(1, 1)
local col = get_param(2, 1)
cursor.y = row
cursor.x = col
if cursor.x < 1 then cursor.x = 1 end
if cursor.y < 1 then cursor.y = 1 end
if cursor.x > width then cursor.x = width end
if cursor.y > height then cursor.y = height end
return
end
if string.byte(text,i)==10 then
printSection()
newLine()
elseif string.byte(text,i)==13 then
printSection()
cursorPosX=1
elseif string.byte(text,i)==0x1b and i<=#text-2 then
printSection()
--ocelot.log("0x1b char detected")
local codeType = string.sub(text,i+1,i+1)
if codeType=="[" then
-- Control Sequence Introducer
--ocelot.log("Control Sequence Introducer")
local codeEndIdx = findCodeEnd(text,i)
-- codeEndIdx = string.find(text,"m",i)
local code = string.sub(text,i,codeEndIdx)
--ocelot.log("Code: "..code.." ("..i..", "..codeEndIdx..")")
readBreak = readBreak + #code - 1
local nums = parseCodeNumbers(code)
local codeEnd = code:sub(-1)
--ocelot.log("Code end: "..codeEnd..", "..#codeEnd)
if codeEnd == "m" then
-- Select Graphic Rendition
--ocelot.log("Select Graphic Rendition, ID "..nums[1])
if nums[1]>=30 and nums[1]<=37 then
gpu.setForeground(ANSIColorPalette["dark"][nums[1]%10])
if op == 0x41 then
local n = get_param(1, 1)
cursor.y = cursor.y - n
if cursor.y < 1 then cursor.y = 1 end
return
end
if nums[1]==38 and nums[2]==5 then
gpu.setForeground(from8BitColor(nums[3]))
if op == 0x42 then
local n = get_param(1, 1)
cursor.y = cursor.y + n
if cursor.y > height then cursor.y = height end
return
end
if nums[1]==38 and nums[2]==2 then
gpu.setForeground(from24BitColor(nums[3],nums[4],nums[5]))
if op == 0x43 then
local n = get_param(1, 1)
cursor.x = cursor.x + n
if cursor.x > width then cursor.x = width end
return
end
if nums[1]==39 or nums[1]==0 then
gpu.setForeground(defaultForegroundColor)
if op == 0x44 then
local n = get_param(1, 1)
cursor.x = cursor.x - n
if cursor.x < 1 then cursor.x = 1 end
return
end
if nums[1]>=40 and nums[1]<=47 then
gpu.setBackground(ANSIColorPalette["dark"][nums[1]%10])
if op == 0x47 then
local col = get_param(1, 1)
cursor.x = col
if cursor.x < 1 then cursor.x = 1 end
if cursor.x > width then cursor.x = width end
return
end
if nums[1]==48 and nums[2]==5 then
gpu.setBackground(from8BitColor(nums[3]))
if op == 0x4a then
local mode = get_param(1, 0)
if mode == 0 then
update_gpu_colors()
gpu.fill(cursor.x, cursor.y, width - cursor.x + 1, height - cursor.y + 1, " ")
elseif mode == 1 then
update_gpu_colors()
gpu.fill(1, 1, cursor.x, cursor.y, " ")
elseif mode == 2 then
update_gpu_colors()
gpu.fill(1, 1, width, height, " ")
cursor.x = 1
cursor.y = 1
end
if nums[1]==48 and nums[2]==2 then
gpu.setBackground(from24BitColor(nums[3],nums[4],nums[5]))
return
end
if nums[1]==49 or nums[1]==0 then
gpu.setBackground(defaultBackgroundColor)
if op == 0x4b then
local mode = get_param(1, 0)
if mode == 0 then
update_gpu_colors()
gpu.fill(cursor.x, cursor.y, width - cursor.x + 1, 1, " ")
elseif mode == 1 then
update_gpu_colors()
gpu.fill(1, cursor.y, cursor.x, 1, " ")
elseif mode == 2 then
update_gpu_colors()
gpu.fill(1, cursor.y, width, 1, " ")
end
if nums[1]>=90 and nums[1]<=97 then
gpu.setForeground(ANSIColorPalette["bright"][nums[1]%10])
return
end
if nums[1]>=100 and nums[1]<=107 then
gpu.setBackground(ANSIColorPalette["bright"][nums[1]%10])
if op == 0x60 then
local col = get_param(1, 1)
cursor.x = col
if cursor.x < 1 then cursor.x = 1 end
if cursor.x > width then cursor.x = width end
return
end
if op == 0x64 then
local row = get_param(1, 1)
cursor.y = row
if cursor.y < 1 then cursor.y = 1 end
if cursor.y > height then cursor.y = height end
return
end
if op == 0x6d then
local j = 1
local function parse_extended_color()
local mode = get_param(j + 1, -1)
if mode == 5 then
local idx = get_param(j + 2, 0)
j = j + 2
if idx < 8 then
return ANSIColorPalette["dark"][idx]
elseif idx < 16 then
return ANSIColorPalette["bright"][idx - 8]
elseif idx < 232 then
local i = idx - 16
local b = (i % 6) * 51
local g = ((i // 6) % 6) * 51
local r = (i // 36) * 51
return (r << 16) | (g << 8) | b
else
local v = (idx - 232) * 10 + 8
return (v << 16) | (v << 8) | v
end
elseif mode == 2 then
local r = get_param(j + 2, 0)
local g = get_param(j + 3, 0)
local b = get_param(j + 4, 0)
j = j + 4
return (r << 16) | (g << 8) | b
end
return nil
end
if #params == 0 then
color.reverse = false
color.fg = color.FG
color.bg = color.BG
update_gpu_colors()
return
end
while j <= #params do
local p = params[j] or 0
if p == 0 then
color.reverse = false
color.fg = color.FG
color.bg = color.BG
elseif p == 1 then
elseif p == 2 then
elseif p == 3 then
elseif p == 4 then
elseif p == 5 or p == 6 then
elseif p == 7 then
color.reverse = true
elseif p == 8 then
color.fg = color.bg
elseif p == 9 then
elseif p == 21 then
elseif p == 22 then
elseif p == 23 then
elseif p == 24 then
elseif p == 25 then
elseif p == 27 then
color.reverse = false
elseif p == 28 then
color.fg = color.FG
elseif p == 29 then
elseif 30 <= p and p <= 37 then
color.fg = ANSIColorPalette["dark"][p - 30]
elseif p == 38 then
local c = parse_extended_color()
if c then color.fg = c end
elseif p == 39 then
color.fg = color.FG
elseif 40 <= p and p <= 47 then
color.bg = ANSIColorPalette["dark"][p - 40]
elseif p == 48 then
local c = parse_extended_color()
if c then color.bg = c end
elseif p == 49 then
color.bg = color.BG
elseif p == 58 then
parse_extended_color()
elseif p == 59 then
elseif 90 <= p and p <= 97 then
color.fg = ANSIColorPalette["bright"][p - 90]
elseif 100 <= p and p <= 107 then
color.bg = ANSIColorPalette["bright"][p - 100]
end
j = j + 1
end
update_gpu_colors()
return
end
if op == 0x73 then
cursor.X = cursor.x
cursor.Y = cursor.y
return
end
if op == 0x75 then
if cursor.X and cursor.Y then
cursor.x = cursor.X
cursor.y = cursor.Y
if cursor.x < 1 then cursor.x = 1 end
if cursor.y < 1 then cursor.y = 1 end
if cursor.x > width then cursor.x = width end
if cursor.y > height then cursor.y = height end
end
return
end
end
function _G._PUBLIC.terminal.writec(byte)
if byte == 0x1b then
_PUBLIC.terminal.flush()
printState = 1
seq = {}
return
end
if printState == 1 then
if byte == 0x5b then
printState = 2
else
printState = 0
end
return
end
if printState == 2 then
table.insert(seq, byte)
if 0x40 <= byte and byte <= 0x7e then
exec_csi()
printState = 0
seq = {}
end
return
end
if byte == 0xa then
_PUBLIC.terminal.flush()
cursor.y = cursor.y + 1
cursor.x = 1
check_wrap_and_scroll()
return
end
if byte == 0xd then
_PUBLIC.terminal.flush()
cursor.x = 1
return
end
if byte == 0x8 then
_PUBLIC.terminal.flush()
if cursor.x > 1 then
cursor.x = cursor.x - 1
end
return
end
if byte == 0x9 then
_PUBLIC.terminal.flush()
cursor.x = ((cursor.x - 1) // 8) * 8 + 9
if cursor.x < 1 then cursor.x = 1 end
if cursor.x > width then cursor.x = width end
return
end
if byte >= 0x20 and byte <= 0x7F then
table.insert(writeBuf, string.char(byte))
cursor.x = cursor.x + 1
check_wrap_and_scroll()
if cursor.y ~= writeBufY or #writeBuf >= 32 then
_PUBLIC.terminal.flush()
end
elseif byte >= 0xC2 and byte <= 0xDF then
current_codepoint = (byte & 0x1F)
bytes_remaining = 1
elseif byte >= 0xE0 and byte <= 0xEF then
current_codepoint = (byte & 0x0F)
bytes_remaining = 2
elseif byte >= 0xF0 and byte <= 0xF7 then
current_codepoint = (byte & 0x07)
bytes_remaining = 3
elseif byte >= 0x80 and byte <= 0xBF and bytes_remaining > 0 then
current_codepoint = (current_codepoint << 6) | (byte & 0x3F)
bytes_remaining = bytes_remaining - 1
if bytes_remaining == 0 then
table.insert(writeBuf, utf8.char(current_codepoint))
cursor.x = cursor.x + 1
check_wrap_and_scroll()
if cursor.y ~= writeBufY or #writeBuf >= 32 then
_PUBLIC.terminal.flush()
end
current_codepoint = 0
end
else
--gpu.set(cursorPosX,cursorPosY,string.sub(text,i,i))
section = section..string.sub(text,i,i)
current_codepoint = 0
bytes_remaining = 0
end
::continue::
end
printSection()
function _G._PUBLIC.terminal.write(text)
text = tostring(text)
for i = 1, #text do
_PUBLIC.terminal.writec(string.byte(text, i))
end
end
function _G._PUBLIC.terminal.clear()
update_gpu_colors()
gpu.fill(1, 1, width, height, " ")
writeBuf = {}
cursor.x = 1
cursor.y = 1
end
function _G._PUBLIC.terminal.flush()
if #writeBuf == 0 then return end
update_gpu_colors()
gpu.set(cursor.x - #writeBuf, cursor.y, table.concat(writeBuf))
writeBuf = {}
end
function _G.print(...)
@@ -239,19 +567,12 @@ function module.init()
local stringArgs = {}
for _, arg in pairs(args) do
if type(arg)=="table" then
table.insert(stringArgs, serialize.table(arg,true))
table.insert(stringArgs, serialize(arg))
elseif tostring(arg) then
table.insert(stringArgs, tostring(arg))
end
end
_PUBLIC.terminal.write(table.concat(stringArgs, " ") .. "\n")
end
function _G._PUBLIC.terminal.clear()
gpu.setForeground(defaultForegroundColor)
gpu.setBackground(defaultBackgroundColor)
gpu.fill(1,1,width,height," ")
cursorPosX, cursorPosY = 1, 1
_PUBLIC.terminal.write(table.concat(stringArgs, "\t") .. "\n")
end
function _G._PUBLIC.terminal.read(options)
@@ -272,6 +593,8 @@ function module.init()
local text = options.defaultText or ""
_G._PUBLIC.terminal.flush()
local historyIdx
if options.readHistoryType then
if not readHistory[options.readHistoryType] then
@@ -290,7 +613,7 @@ function module.init()
local cur = unicode.len(text)+1
if options.prefix then _PUBLIC.terminal.write(options.prefix) end
local startX, startY = cursorPosX, cursorPosY
local startX, startY = cursor.x, cursor.y
local fg, bg = gpu.getForeground(), gpu.getBackground()
local cursorBlink = true
local function checkScroll(y)
@@ -483,9 +806,9 @@ function module.init()
end
end
cursorPosX=1
cursorPosY=cursorPosY+math.ceil((unicode.wlen(text)+startX-1)/width)
if cursorPosY>height then scrollDown() end
cursor.x=1
cursor.y=cursor.y+math.ceil((unicode.wlen(text)+startX-1)/width)
if cursor.y>height then scroll() end
return text
end
+1 -1
View File
@@ -42,7 +42,7 @@ function module.init()
if type(tsched.currentTask) == "table" and type(tsched.currentTask.id) == "number" then
taskInfo.parent = tsched.currentTask.id
else
log.kernel.info("debug: tsched.currentTask is " .. require("serialize").table(tsched.currentTask))
log.kernel.info("debug: tsched.currentTask is " .. require("serialize")(tsched.currentTask))
end
table.insert(tsched.tasks, taskInfo)
if taskInfo.parent then
+5
View File
@@ -22,6 +22,11 @@ function _G.shell.getWorkingDirectory()
return workingDirectory
end
function _G.shell.resolvePath(path)
if path:sub(1, 1) == "/" then return path end
return fs.concat(workingDirectory, path)
end
function _G.shell.setWorkingDirectory(dir)
checkArg(1, dir, "string")
workingDirectory = dir
+23 -20
View File
@@ -32,6 +32,11 @@ function filesystem.canonical(path)
return "/" .. table.concat(segList, "/")
end
function filesystem.basename(path)
checkArg(1, path, "string")
return path:match("/([^/]+)/?$") or ""
end
function filesystem.concat(path1, path2)
checkArg(1, path1, "string")
checkArg(2, path2, "string")
@@ -67,6 +72,13 @@ function filesystem.absolutePath(path) -- returns the address and absolute path
return address, path
end
function filesystem.parent(path)
checkArg(1, path, "string")
local p = filesystem.canonical(path)
-- return "/" on "/"
return p == "/" and "/" or (p:match("^(.*)/[^/]+/?$") or "/")
end
function filesystem.exists(path) -- check if path exists
checkArg(1, path, "string")
local address, absPath = filesystem.absolutePath(path)
@@ -350,7 +362,7 @@ function filesystem.open(path, mode, buffered) -- opens a file and returns its h
end
end
function properHandle.seek(self, whence, offset)
checkArg(2, whence, "string", "number")
checkArg(2, whence, "string", "number", "nil")
checkArg(3, offset, "number", "nil")
if not offset then
offset = 0
@@ -475,14 +487,12 @@ local function copyContent(fromHandle, toHandle)
if not (fromHandle and toHandle) then
return
end
local memory = math.floor(computer.freeMemory() * 0.8)
local tmpdata
while true do
tmpdata = fromHandle:read(memory)
local tmpdata = fromHandle.read(2048)
if not tmpdata then
break
end
local status, reason = toHandle:write(tmpdata)
local status, reason = toHandle.write(tmpdata)
if status ~= true then
break
end
@@ -492,7 +502,6 @@ local function copyContent(fromHandle, toHandle)
end
local function copyRecursive(fromAddress, fromAbsPath, toAddress, toAbsPath)
-- TODO: make this use copyContent
if fromAbsPath:sub(-1) == "/" then
fromAbsPath = fromAbsPath:sub(1, -2)
end
@@ -506,31 +515,21 @@ local function copyRecursive(fromAddress, fromAbsPath, toAddress, toAbsPath)
end
for i = 1, #fileList do
local fromFile, toFile = fromAbsPath .. "/" .. fileList[i], toAbsPath .. "/" .. fileList[i]
--[[ local handle = component.invoke(fromAddress, "open", fromFile, "r")
local data, tmpdata = "", nil
repeat
tmpdata = component.invoke(fromAddress, "read", handle, math.huge or math.maxinteger)
data = data .. (tmpdata or "")
until not tmpdata
tmpdata = component.invoke(fromAddress, "close", handle)
local handle = component.invoke(toAddress, "open", toFile, "w")
component.invoke(toAddress, "write", handle, data)
component.invoke(toAddress, "close", handle) ]]
local fromHandle = component.invoke(fromAddress, "open", fromFile, "r")
local toHandle = component.invoke(toAddress, "open", toFile, "w")
copyContent({
["read"] = function(...)
return component.invoke(fromAddress, "read", handle, ...)
return component.invoke(fromAddress, "read", fromHandle, ...)
end,
["close"] = function(...)
return component.invoke(fromAddress, "close", handle, ...)
return component.invoke(fromAddress, "close", fromHandle, ...)
end,
}, {
["write"] = function(...)
return component.invoke(fromAddress, "write", handle, ...)
return component.invoke(toAddress, "write", toHandle, ...)
end,
["close"] = function(...)
return component.invoke(fromAddress, "close", handle, ...)
return component.invoke(toAddress, "close", toHandle, ...)
end,
})
end
@@ -545,6 +544,10 @@ function filesystem.isDirectory(path)
return component.invoke(address, "isDirectory", absPath)
end
function filesystem.isFile(path)
return not filesystem.isDirectory(path) and filesystem.exists(path)
end
function filesystem.rename(fromPath, toPath)
checkArg(1, fromPath, "string")
checkArg(2, toPath, "string")
+3 -6
View File
@@ -55,14 +55,11 @@ local logFileSizeLimit = 16384
local function writeToLog(path, text)
fs.makeDirectory("halyde/logs") -- Git likes to not clone empty directories
local handle
if fs.exists(path) then
handle = assert(fs.open(path, "a"))
else
handle = assert(fs.open(path, "w"))
end
local handle = fs.open(path, "a")
if handle then
handle:write(text .. "\n")
handle:close()
end
-- Log trimming if it gets too long
if fs.size(path) > logFileSizeLimit then
+43 -122
View File
@@ -1,127 +1,48 @@
local serialize = {}
function serialize.string(str)
return '"'..str:gsub("[%z\1-\31\34\92\127-\159]",function(c)
local byte = c:byte()
if byte== 7 then return "\\a" end
if byte== 8 then return "\\b" end
if byte== 9 then return "\\t" end
if byte==10 then return "\\n" end
if byte==11 then return "\\v" end
if byte==12 then return "\\f" end
if byte==13 then return "\\r" end
if byte==34 then return "\\\"" end
if byte==92 then return "\\\\" end
return string.format("\\x%02x",byte)
end)..'"'
local function _serialize(value, indent, level, visited)
local currentIndent = indent and string.rep(indent, level) or ""
local nextIndent = indent and string.rep(indent, level + 1) or ""
local sep = indent and "\n" or " "
local t = type(value)
if t == "nil" then return "nil" end
if t == "string" then return string.format("%q", value) end
if t == "number" then
if value ~= value then return "0/0" end
if value == math.huge then return "math.huge" end
if value == -math.huge then return "-math.huge" end
return tostring(value)
end
if t == "boolean" then return tostring(value) end
if t == "table" then
if visited[value] then return "..." end
visited[value] = true
local items = {}
local arrayCount = 0
for i = 1, #value do
if value[i] ~= nil then arrayCount = i else break end
end
for i = 1, arrayCount do
table.insert(items, nextIndent .. _serialize(value[i], indent, level + 1, visited))
end
for k, v in pairs(value) do
if type(k) ~= "number" or k < 1 or k > arrayCount then
local keyStr
if type(k) == "string" and k:match("^[%a_][%w_]*$") then
keyStr = k
else
keyStr = "[" .. _serialize(k, indent, level + 1, visited) .. "]"
end
table.insert(items, nextIndent .. keyStr .. " = " .. _serialize(v, indent, level + 1, visited))
end
end
visited[value] = nil
if #items == 0 then return "{}" end
return "{" .. sep .. table.concat(items, "," .. sep) .. sep .. currentIndent .. "}"
end
return tostring(value)
end
function serialize.table(tbl,colors,stack)
stack = table.copy(stack or {})
table.insert(stack,tbl)
local keyAmount = 0
local keyNumber = true
local out = ""
local first = true
for key,val in pairs(tbl) do
if not first then out=out..",\n" end
first=false
out=out.." "
if type(key)=="string" then
if key:match("^[%a_][%w_]*$") then
out=out..key.."="
else
out=out..'['..serialize.string(key)..']='
end
else
out=out.."["..tostring(key).."]="
end
if type(key)~="number" then
keyNumber=false
end
local success,reason = pcall(function()
local valStr = ""
if type(val)=="table" then
if #stack>4 or table.find(stack,val) then
valStr="..."
else
valStr=serialize.table(val,colors,stack)
end
elseif type(val)=="string" then
local lines = {}
for line in (val.."\n"):gmatch("([^\n]*)\n") do table.insert(lines,line) end
if #lines[#lines]==0 then
lines[#lines]=nil
lines[#lines]=lines[#lines].."\n"
end
for i=1,#lines do
if i<#lines then
lines[i]=serialize.string(lines[i].."\n")
else
lines[i]=serialize.string(lines[i])
end
end
valStr=table.concat(lines," ..\n ")
else
valStr=tostring(val)
end
local lines = {}
for line in (valStr.."\n"):gmatch("([^\n]*)\n") do table.insert(lines,line) end
out=out..table.concat(lines,"\n ")
lines = nil
keyAmount=keyAmount+1
end)
if not success then
if colors then out=out.."\x1b[91m" end
out=out.."["..tostring(reason).."]"
if colors then out=out.."\x1b[39m" end
end
coroutine.yield()
end
local metatbl = getmetatable(tbl)
local metakeys = {}
local metastring = ""
if type(metatbl)=="table" then
for i,v in pairs(metatbl) do
keyNumber=false
table.insert(metakeys,i)
end
end
if #metakeys>0 then
out=out.."\n "
if colors then metastring=metastring.."\x1b[92m" end
if table.find(metakeys,"__tostring") then
metastring=metastring.."tostring: "..serialize.string(tostring(tbl)).."\n "
table.remove(metakeys,table.find(metakeys,"__tostring"))
end
metastring=metastring..table.concat(metakeys,", ")
if colors then metastring=metastring.."\x1b[39m" end
out=out..metastring
end
if keyAmount==0 then return "{"..metastring.."}" end
if keyNumber then
-- fix strings not being serialised
local vals = {}
for _,v in pairs(tbl) do
if #vals>=5 and #stack>1 then
table.insert(vals,"...")
break
end
if type(v)=="table" then
table.insert(vals,serialize.table(v,colors,stack))
elseif type(v)=="string" then
table.insert(vals,serialize.string(v))
else
table.insert(vals,tostring(v))
end
end
return "{"..table.concat(vals,", ")..(#metakeys>0 and "\n "..metastring or "").."}"
end
return "{\n"..out.."\n}"
function serialize(value, indent)
return _serialize(value, indent, 0, {})
end
return serialize
+501
View File
@@ -0,0 +1,501 @@
local defaultDBPath = "/ag2/testdb.json"
local computer = require("computer")
local fs = require("filesystem")
-- local db = require("solvitdb")
local db = {}
local json = require("json")
function db.create(dbpath)
local handle = fs.open(dbpath,"w")
handle:write("{}")
handle:close()
end
function db.readJSON(dbpath)
local handle = fs.open(dbpath,"r")
local content = ""
while true do
local s = handle:read(math.huge or math.maxinteger)
if not s then break end
content=content..s
end
handle:close()
return content
end
function db.get(dbpath,pack)
local dbc = json.decode(db.readJSON(dbpath))
return dbc[pack]
end
function db.set(dbpath,pack,info)
local dbc = json.decode(db.readJSON(dbpath))
dbc[pack]=info
local handle = fs.open(dbpath,"w")
handle:write(json.encode(dbc))
handle:close()
end
function db.remove(dbpath,pack)
local dbc = json.decode(db.readJSON(dbpath))
dbc[pack]=nil
local handle = fs.open(dbpath,"w")
handle:write(json.encode(dbc))
handle:close()
end
function db.list(dbpath,pack)
local dbc = json.decode(db.readJSON(dbpath))
local keys = {}
for i,_ in pairs(dbc) do
table.insert(keys,i)
end
return ipairs(keys)
end
local avs = {}
function avs.splitSingular(s)
local result = {}
for str in string.gmatch(s, "([^.]+)") do
table.insert(result,tonumber(str) or -1)
end
return result
end
function avs.parse(pack)
if not string.find(pack,"=") then
return {pack}
end
local idx=pack:find("=")
local name=pack:sub(1,idx-1)
local verstr=pack:sub(idx+1)
if string.find(verstr,"-") then
idx=verstr:find("-")
verstr={verstr:sub(1,idx-1),verstr:sub(idx+1)}
else
verstr={verstr}
end
for i=1,#verstr do
verstr[i]=avs.splitSingular(verstr[i])
end
if #verstr>1 then
for i=1,3 do
verstr[1][i]=math.max(verstr[1][i],0)
end
end
return {name,verstr}
end
function avs.serializeSingle(ver)
local ver2 = table.copy(ver)
for i=1,3 do
if ver2[i]==-1 then
ver2[i]="*"
else
ver2[i]=tostring(ver2[i])
end
end
return ver2[1].."."..ver2[2].."."..ver2[3]
end
function avs.serializeVersion(ver)
local singles = {}
for i=1,#ver do
table.insert(singles,avs.serializeSingle(ver[i]))
end
if singles[1]==singles[2] then
singles={singles[1]}
end
local out=""
for i=1,#singles do
out=out..singles[i]
if i~=#singles then
out=out.."-"
end
end
return out
end
function avs.serializePack(pack)
if #pack==1 then
return pack[1]
end
return pack[1].."="..avs.serializeVersion(pack[2])
end
function avs.singleGreater(ver1,ver2)
for i=1,3 do
if ver1[i]~=ver2[i] then
if ver1[i]==-1 or ver1[i]>ver2[i] then
return true
end
if ver2[i]==-1 or ver2[i]>ver1[i] then
return false
end
end
end
return false
end
function avs.singleLesser(ver1,ver2)
return avs.singleGreater(ver2,ver1)
end
function avs.singleMin(ver1,ver2)
return avs.singleLesser(ver1,ver2) and ver1 or ver2
end
function avs.singleMax(ver1,ver2)
return avs.singleGreater(ver1,ver2) and ver1 or ver2
end
function avs.compatibleRange(vers)
for i=1,#vers do
if type(vers[i])=="string" then vers[i]=avs.parse(vers[i])[2] end
if #vers[i]==1 then vers[i]={vers[i][1],vers[i][1]} end
end
local range = vers[1]
for i=2,#vers do
range[1]=avs.singleMax(range[1],vers[i][1])
range[2]=avs.singleMin(range[2],vers[i][2])
end
if avs.singleGreater(range[1],range[2]) then
return nil
end
return range
end
function avs.matching(pack1,pack2)
if pack1[1]~=pack2[1] then return false end
local ver1 = pack1[2] or {{-1,-1,-1}}
local ver2 = pack2[2] or {{-1,-1,-1}}
if #ver1==1 then ver1={ver1[1],ver1[1]} end
if #ver2==1 then ver2={ver2[1],ver2[1]} end
return avs.compatibleRange({ver1,ver2})~=nil
end
local function packageInArray(pack,arr)
for i=1,#arr do
if avs.matching(avs.parse(arr[i]),pack) then
return true,arr[i]
end
end
return false
end
local function packageNameInArray(pack,arr)
for i=1,#arr do
if arr[i][1]==pack[1] then
return true,arr[i]
end
end
return false
end
local function removeFromArray(el,arr)
for i=1,#arr do
if arr[i]==el then
table.remove(arr,i)
return i
end
end
end
local function startTransaction(dbpath)
dbpath = dbpath or defaultDBPath
if not fs.exists(dbpath) then
db.create(dbpath)
end
local yieldClock = computer.uptime()
local function yieldIfNecessary()
if computer.uptime()-yieldClock>=0.1 then
coroutine.yield()
yieldClock = computer.uptime()
end
end
local installIncomplete = false
local removeIncomplete = false
local packInfo = {}
local ins = {}
local rem = {}
local transaction = {}
function transaction.install(name)
table.insert(ins,avs.parse(name))
installIncomplete = true
end
function transaction.remove(name)
table.insert(rem,avs.parse(name))
removeIncomplete = true
end
function transaction.autoRemove()
end
function transaction.update(name)
end
function transaction.updateAll(name)
end
function transaction.addInfo(name,info)
if not info.type then info.type="package" end
packInfo[name]=info
-- print(require("serialize")(packInfo))
end
local function getPackInfo(pack)
return packInfo[avs.serializePack(pack)]
end
local function findReverseDependencies(pack)
local out={}
for i,info in pairs(packInfo) do
if info.dependencies and packageInArray(pack,info.dependencies) then
table.insert(out,avs.parse(i))
end
end
return out
end
local function finalizeInstall(settings)
-- find missing package information
local missing = {}
for i=1,#ins do
if getPackInfo(ins[i])==nil then
table.insert(missing,avs.serializePack(ins[i]))
end
end
if #missing>0 then
return false,missing
end
-- find dependencies
installIncomplete=false
local i=1
while i<=#ins do
local deps = getPackInfo(ins[i]).dependencies
if deps and #deps>=1 then
for j=1,#deps do
local dep = avs.parse(deps[j])
local inArr,arrPack = packageNameInArray(dep,ins)
if inArr then
if not avs.matching(dep,arrPack) then
local msg = ""
if settings.resolveConflict then
msg="Cannot resolve conflict: "..msg
end
local rev = findReverseDependencies(arrPack)
if #rev==0 then
msg=msg.."Package "..avs.serializePack(ins[i]).." depends on "..avs.serializePack(dep)..", but one or more packages depend on "..avs.serializePack(arrPack)
return false, msg
elseif #rev==1 then
msg=msg.."Package "..avs.serializePack(ins[i]).." depends on "..avs.serializePack(dep)..", but package "..avs.serializePack(rev[1]).." depends on "..avs.serializePack(arrPack)
return false, msg
else
msg=msg.."Package "..avs.serializePack(ins[i]).." depends on "..avs.serializePack(dep)..", but packages "
for i=1,#rev do
if i>1 and i~=#rev then
msg=msg..", "
elseif i==#rev then
msg=msg.." and "
end
msg=msg..avs.serializePack(rev[i])
end
msg=msg.." depend on "..avs.serializePack(arrPack)
return false, msg
end
end
elseif type(db.get(dbpath,dep[1]))=="nil" then
installIncomplete=true
table.insert(ins,j,dep)
else
local dbinfo = db.get(dbpath,dep[1])
local dbpack = {dep[1]}
if dbinfo.version then
dbpack = avs.parse(dep[1].."="..dbinfo.version)
end
if not avs.matching(dep,dbpack) then
if settings.resolveConflict then
removeIncomplete=true
table.insert(rem,1,dbpack)
installIncomplete=true
table.insert(ins,j,dep)
else
local msg = "Package "..avs.serializePack(ins[i]).." depends on "..avs.serializePack(dep)..", but another version ("..avs.serializePack(dbpack)..") is installed"
return false, msg
end
end
end
end
i=i+#deps
end
i=i+1
end
end
local function finalizeRemove(settings)
removeIncomplete=false
-- filter to only have packages in the database
local i=1
while i<=#rem do
local dat = db.get(dbpath,rem[i][1])
if not dat then
table.remove(rem,i)
else
packInfo[avs.serializePack(rem[i])]=dat
i=i+1
end
end
-- get package info from database
for i=1,#rem do
if not getPackInfo(rem[i]) then
packInfo[avs.serializePack(rem[i])]=db.get(rem[i][1])
end
end
-- find if the main package is reverse dependant to another
i=1
while i<=#rem do
local dat = getPackInfo(rem[i])
if dat.reverseDependencies and #dat.reverseDependencies>0 then
for _,dep in ipairs(dat.reverseDependencies) do
if not packageNameInArray({dep},rem) then
if settings.cascade then
table.insert(rem,1,{dep})
i=i+1
else
return false, "Package "..rem[i][1].." is a dependency of "..dep
end
end
end
end
i=i+1
end
-- look for dependencies if settings.autoremove is on
if settings.autoremove then
i=1
while i<=#rem do
local deps = getPackInfo(rem[i]).dependencies
if deps and #deps>=1 then
for j=1,#deps do
local dep = avs.parse(deps[j])
local depdat = packInfo[deps[j]]
if not depdat then
depdat = db.get(dbpath,dep[1])
packInfo[deps[j]]=depdat
end
if (not packageNameInArray(dep,rem)) and type(db.get(dbpath,dep[1]))~="nil" and (#depdat.reverseDependencies==1 and depdat.reverseDependencies[1]==rem[i][1]) then
removeIncomplete=true
table.insert(rem,j,dep)
end
end
i=i+#deps
end
i=i+1
end
end
end
function transaction.finalize(settings)
settings = settings or {}
while installIncomplete or removeIncomplete do
while installIncomplete do
local out = {finalizeInstall(settings)}
if out[1]==false then return table.unpack(out) end
yieldIfNecessary()
end
while removeIncomplete do
local out = {finalizeRemove(settings)}
if out[1]==false then return table.unpack(out) end
yieldIfNecessary()
end
end
local install = {}
local remove = {}
for i=1,#ins do
table.insert(install,avs.serializePack(ins[i]))
end
for i=1,#rem do
table.insert(remove,avs.serializePack(rem[i]))
end
return true, {install=install,remove=remove}
-- return "true, {["install"] = {"dep1", "package1", "package2"}, ["remove"] = {"package3"}}" on success
-- return "false, {"dep1"}" when not enough data
-- return "false, "[verbose string]"" when conflict found
-- TODO: handle same range AVS
-- TODO: handle different intercompatible 1.*.* range AVS
-- TODO: handle different incompatible 1.*.* range AVS
-- TODO: handle different intercompatible 1.*.*-2.*.* range AVS
-- TODO: handle different incompatible 1.*.*-2.*.* range AVS
-- TODO: handle conflicts from package info
-- TODO: handle reverse conflicts from another package's info
-- TODO: handle automatic conflict resolving
-- TODO: handle update of a single package with no dependencies
-- TODO: handle update of a single package with dependencies that don't need updating
-- TODO: handle update of a single package with dependencies that need updating
-- TODO: handle update of a single package that has a set dependency version changed
-- TODO: handle updating all packages in the database
-- TODO: handle installing optional packages
-- TODO: handle installing virtual packages and store this vpack info to database
-- TODO: handle removing virtual packages from database info
-- TODO: handle installing groups and store this group info to database
-- TODO: handle removing groups and store this group info to database
end
local function storeInstall()
-- directly set
for _,pack in ipairs(ins) do
if getPackInfo(pack) then
local info = table.copy(getPackInfo(pack))
if pack[2] then
info.version=avs.serializeVersion(pack[2])
else
info.version=info.latestVersion or info.version
end
db.set(dbpath,pack[1],info)
end
end
-- set reverse dependencies
for _,pack in pairs(ins) do
local i = avs.serializePack(pack)
local v = packInfo[i]
if v and v.dependencies then
for _,dep in ipairs(v.dependencies) do
local depname = avs.parse(dep)[1]
local dat = db.get(dbpath,depname)
if not dat then goto continue end
if type(dat.reverseDependencies)~="table" then
dat.reverseDependencies={}
end
table.insert(dat.reverseDependencies,pack[1])
db.set(dbpath,depname,dat)
::continue::
end
end
end
end
local function storeRemove()
-- directly remove
for _,pack in ipairs(rem) do
db.remove(dbpath,pack[1])
end
-- remove reverse dependencies
for _,pack in ipairs(rem) do
local pdat = getPackInfo(pack)
if not pdat.dependencies then goto continue end
for _,dep in ipairs(pdat.dependencies) do
local depname = avs.parse(dep)[1]
local dat = db.get(dbpath,depname)
if dat.reverseDependencies then
removeFromArray(pack[1],dat.reverseDependencies)
end
db.set(dbpath,depname,dat)
end
::continue::
end
end
function transaction.store()
if #ins>0 then
storeInstall()
end
if #rem>0 then
storeRemove()
end
end
return transaction
end
return { avs=avs,startTransaction=startTransaction }
+312
View File
@@ -0,0 +1,312 @@
local solvitdb = {}
local fs = require("filesystem")
local function checkValidityAndOpen(path)
local handle = assert(fs.open(path))
local data = assert(handle:read(8))
if data:sub(5, 8) == "RTFM" then
local patLength = string.unpack("<I4", data:sub(1, 4))
return handle, patLength
else
error("missing magic")
end
end
local function readPat(handle, patLength)
-- This needs the handle to be at byte 4
local data, tmpdata = ""
repeat
tmpdata = handle:read(patLength - #data)
data = data .. (tmpdata or "")
until not tmpdata
local packages = {}
for packageData in data:gmatch("(.-%.....);") do
packages[packageData:sub(1, -6)] = string.unpack("<I4", packageData:sub(-4, -1))
end
return packages
end
local function insert(filePath, location, bytes)
local chunkLength = 512
local readHandle = assert(fs.open(filePath, "r"))
local tmpFilePath = filePath .. ".tmp"
local writeHandle = assert(fs.open(tmpFilePath, "w"))
local i = 0
while true do
local readAmount = chunkLength
if readAmount > location - i then
readAmount = location - i
end
if readAmount == 0 then
break
end
local data = readHandle:read(readAmount)
i = i + readAmount
assert(writeHandle:write(data))
end
assert(writeHandle:write(bytes))
while true do
local data = readHandle:read(chunkLength)
if not data then
break
end
assert(writeHandle:write(data))
end
readHandle:close()
writeHandle:close()
fs.rename(tmpFilePath, filePath)
end
local function remove(filePath, location, length)
local chunkLength = 512
if length > 512 then
chunkLength = length
end
-- The file has to get shortened, so I have no choice but to do these shenanigans
local readHandle = assert(fs.open(filePath, "r"))
local tmpFilePath = filePath .. ".tmp"
local writeHandle = assert(fs.open(tmpFilePath, "w"))
local i = 0
while true do
local readAmount = chunkLength
if readAmount > location - i then
readAmount = location - i
end
if readAmount == 0 then
break
end
local data = readHandle:read(readAmount)
i = i + readAmount
assert(writeHandle:write(data))
end
readHandle:seek(length)
while true do
local data = readHandle:read(chunkLength)
if not data then
break
end
assert(writeHandle:write(data))
end
readHandle:close()
writeHandle:close()
fs.rename(tmpFilePath, filePath)
end
local function adjustPatLocationsAfterPackage(filePath, packageName, offset)
local readHandle, patLength = checkValidityAndOpen(filePath)
local pat = readPat(readHandle, patLength)
readHandle:close()
local modifiedLocation = pat[packageName]
local toAdjust = {}
for name, location in pairs(pat) do
if location > modifiedLocation then
table.insert(toAdjust, { name = name, location = location })
end
end
if #toAdjust == 0 then
return
end
local readHandle = assert(fs.open(filePath, "r"))
readHandle:seek(8) -- Skip header
local patBytes = assert(readHandle:read(patLength))
readHandle:close()
local patFieldPositions = {}
for _, entry in ipairs(toAdjust) do
local needle = entry.name .. "."
local startPos = patBytes:find(needle, 1, true)
patFieldPositions[entry.name] = 8 + startPos + #needle - 1
end
local writeHandle = assert(fs.open(filePath, "a"))
for _, entry in ipairs(toAdjust) do
local newLocation = entry.location + offset
writeHandle:seek("set", patFieldPositions[entry.name])
writeHandle:write(string.pack("<I4", newLocation))
end
writeHandle:close()
end
function solvitdb.create(path)
checkArg(1, path, "string")
local handle = assert(fs.open(path, "w"))
assert(handle:write("\0\0\0\0RTFM"))
handle:close()
end
function solvitdb.set(path, name, data)
checkArg(1, path, "string")
checkArg(2, name, "string")
checkArg(3, data, "table")
local function sanitize(tab)
for _, item in pairs(tab) do
if type(item) == "string" then
item = item:lower():gsub("[;|]", "-")
elseif type(item) == "table" then
sanitize(item)
end
end
end
sanitize(data)
local readHandle, patLength = checkValidityAndOpen(path)
local pat = readPat(readHandle, patLength)
local writeHandle = assert(fs.open(path, "a"))
local encodedString = ""
if data.type == "package" then
encodedString = "P"
elseif data.type == "group" then
encodedString = "G"
elseif data.type == "virtual-package" then
encodedString = "V"
end
if data.dependencies then
encodedString = encodedString .. "d" .. table.concat(data.dependencies, ";") .. "|"
end
if data.reverseDependencies then
encodedString = encodedString .. "D" .. table.concat(data.reverseDependencies, ";") .. "|"
end
if data.conflicts then
encodedString = encodedString .. "c" .. table.concat(data.conflicts, ";") .. "|"
end
if data.packages then
encodedString = encodedString .. "p" .. table.concat(data.packages, ";") .. "|"
end
if data.version then
encodedString = encodedString .. "v" .. data.version .. "|"
end
if pat[name] then
readHandle:seek(pat[name])
local oldData, tmpdata = ""
repeat
tmpdata = readHandle:read(math.huge)
oldData = oldData .. (tmpdata or "")
until oldData:find("\n", 1, true) or not tmpdata
readHandle:close()
if not oldData:find("\n", 1, true) and not tmpdata then
error("hit unexpected EOF")
end
oldData = oldData:match("^[^\n]+")
local difference = #encodedString - #oldData
writeHandle:seek("set", pat[name] + patLength + 8)
if difference == 0 then
readHandle:close()
writeHandle:write(encodedString)
writeHandle:close()
elseif difference < 0 then
writeHandle:write(encodedString)
local currentSeek = writeHandle:seek()
writeHandle:close()
remove(path, currentSeek, -difference)
adjustPatLocationsAfterPackage(path, name, difference)
elseif difference > 0 then
writeHandle:write(encodedString:sub(1, #encodedString - difference))
local currentSeek = writeHandle:seek()
writeHandle:close()
insert(path, currentSeek + 1, encodedString:sub(#data - difference, -1))
adjustPatLocationsAfterPackage(path, name, difference)
end
else
readHandle:close()
writeHandle:seek("end")
local newPackageLocation = writeHandle:seek() - patLength - 8
writeHandle:write(encodedString .. "\n")
writeHandle:close()
local patData = ("%s.%s;"):format(name, string.pack("<I4", newPackageLocation))
insert(path, patLength + 8, patData)
local newPatLength = patLength + #patData
local writeHandle = assert(fs.open(path, "a")) -- If this works, that means handles must be reopened after insert() or remove()
writeHandle:seek("set", 0)
assert(writeHandle:write(string.pack("<I4", newPatLength)))
writeHandle:close()
end
end
function solvitdb.get(path, name)
checkArg(1, path, "string")
checkArg(2, name, "string")
local handle, patLength = checkValidityAndOpen(path)
local pat = readPat(handle, patLength)
if not pat[name] then
return nil
end
handle:seek(pat[name])
local data, tmpdata = ""
repeat
tmpdata = handle:read(math.huge)
data = data .. (tmpdata or "")
until data:find("\n", 1, true) or not tmpdata
handle:close()
if not data:find("\n", 1, true) and not tmpdata then
error("hit unexpected EOF")
end
data = data:match("^[^\n]+")
local output = {}
if data:sub(1, 1) == "P" then
output.type = "package"
elseif data:sub(1, 1) == "G" then
output.type = "group"
elseif data:sub(1, 1) == "V" then
output.type = "virtual-package"
else
error("unknown package type")
end
data = "." .. data:sub(2, -1)
for series in data:gmatch("%.([dDcp][^.]*)") do
local seriesOutput
if series:sub(1, 1) == "d" then
output.dependencies = {}
seriesOutput = output.dependencies
elseif series:sub(1, 1) == "D" then
output.reverseDependencies = {}
seriesOutput = output.reverseDependencies
elseif series:sub(1, 1) == "c" then
output.conflicts = {}
seriesOutput = output.conflicts
elseif series:sub(1, 1) == "p" then
output.packages = {}
seriesOutput = output.packages
elseif series:sub(1, 1) == "v" then
output.version = series:sub(2, -1)
goto SkipSeries
end
-- Finally a case where Lua's weird table linking shenanigans are actually useful
series = series:sub(2, -1)
for seriesItem in series:gmatch("[^;]+") do
table.insert(seriesOutput, seriesItem)
end
::SkipSeries::
end
return output
end
function solvitdb.list(path)
checkArg(1, path, "string")
local handle, patLength = checkValidityAndOpen(path)
local pat = readPat(handle, patLength)
handle:close()
local list = {}
for index, _ in pairs(pat) do
table.insert(list, index)
end
setmetatable(list, {
__call = function(self)
i, value = next(self, i)
return i, value
end,
})
return list
end
function solvitdb.remove()
end
return solvitdb