diff --git a/argentum.cfg b/argentum.cfg new file mode 100644 index 0000000..9f14220 --- /dev/null +++ b/argentum.cfg @@ -0,0 +1,79 @@ +local agcfg = { + ["halyde"] = { + ["maindir"] = "", + ["version"] = "1.1.0", + ["description"] = "A universal, customizable and feature-packed operating system for OpenComputers.", + ["directories"] = { + "halyde/apps", + "halyde/apps/helpdb", + "halyde/config", + "halyde/core", + "halyde/lib" + }, + ["files"] = { + "init.lua", + "halyde/apps/helpdb/cat.txt", + "halyde/apps/helpdb/cd.txt", + "halyde/apps/helpdb/clear.txt", + "halyde/apps/helpdb/cp.txt", + "halyde/apps/helpdb/default.txt", + "halyde/apps/helpdb/echo.txt", + "halyde/apps/helpdb/fetch.txt", + "halyde/apps/helpdb/help.txt", + "halyde/apps/helpdb/ls.txt", + "halyde/apps/helpdb/lua.txt", + "halyde/apps/helpdb/mv.txt", + "halyde/apps/helpdb/rm.txt", + "halyde/apps/cat.lua", + "halyde/apps/cd.lua", + "halyde/apps/clear.lua", + "halyde/apps/cp.lua", + "halyde/apps/download.lua", + "halyde/apps/echo.lua", + "halyde/apps/fetch.lua", + "halyde/apps/help.lua", + "halyde/apps/ls.lua", + "halyde/apps/lua.lua", + "halyde/apps/mv.lua", + "halyde/apps/rm.lua", + "halyde/config/shell.cfg", + "halyde/config/startupapps.cfg", + "halyde/core/boot.lua", + "halyde/core/cormgr.lua", + "halyde/core/datatools.lua", + "halyde/core/evmgr.lua", + "halyde/core/fullkb.lua", + "halyde/core/shell.lua", + "halyde/core/termlib.lua", + "halyde/lib/component.lua", + "halyde/lib/event.lua", + "halyde/lib/filesystem.lua", + "halyde/lib/raster.lua" + } + }, + ["argentum"] = { + ["maindir"] = "", + ["version"] = "1.0.0", + ["description"] = "The default package manager for Halyde.", + ["directories"] = { + "argentum", + "argentum/store" + }, + ["files"] = { + "argentum/registry.cfg", + "halyde/apps/argentum.lua", + "halyde/apps/helpdb/argentum.txt" + } + }, + ["edit"] = { + ["maindir"] = "", + ["version"] = "1.1.0", + ["description"] = "The default text editor for Halyde.", + ["files"] = { + "halyde/apps/edit.lua", + "halyde/apps/helpdb/edit.txt" + } + } +} + +return agcfg diff --git a/argentum/registry.cfg b/argentum/registry.cfg new file mode 100644 index 0000000..81d74aa --- /dev/null +++ b/argentum/registry.cfg @@ -0,0 +1,7 @@ +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/", + ["argentum"] = "https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/main/" +} + +return agregistry diff --git a/argentum/store/argentum/package.cfg b/argentum/store/argentum/package.cfg new file mode 100644 index 0000000..7d98f7c --- /dev/null +++ b/argentum/store/argentum/package.cfg @@ -0,0 +1,6 @@ +Aargentum/registry.cfg +Ahalyde/apps/argentum.lua +Ahalyde/apps/helpdb/argentum.txt +V1.0.0 +Aargentum/ +Aargentum/store/ diff --git a/argentum/store/edit/package.cfg b/argentum/store/edit/package.cfg new file mode 100644 index 0000000..c1465a4 --- /dev/null +++ b/argentum/store/edit/package.cfg @@ -0,0 +1,3 @@ +Ahalyde/apps/edit.lua +Ahalyde/apps/helpdb/edit.txt +V1.1.0 diff --git a/argentum/store/halyde/package.cfg b/argentum/store/halyde/package.cfg new file mode 100644 index 0000000..f026529 --- /dev/null +++ b/argentum/store/halyde/package.cfg @@ -0,0 +1,45 @@ +Ainit.lua +Ahalyde/apps/helpdb/cat.txt +Ahalyde/apps/helpdb/cd.txt +Ahalyde/apps/helpdb/clear.txt +Ahalyde/apps/helpdb/cp.txt +Ahalyde/apps/helpdb/default.txt +Ahalyde/apps/helpdb/echo.txt +Ahalyde/apps/helpdb/fetch.txt +Ahalyde/apps/helpdb/help.txt +Ahalyde/apps/helpdb/ls.txt +Ahalyde/apps/helpdb/lua.txt +Ahalyde/apps/helpdb/mv.txt +Ahalyde/apps/helpdb/rm.txt +Ahalyde/apps/cat.lua +Ahalyde/apps/cd.lua +Ahalyde/apps/clear.lua +Ahalyde/apps/cp.lua +Ahalyde/apps/download.lua +Ahalyde/apps/echo.lua +Ahalyde/apps/fetch.lua +Ahalyde/apps/help.lua +Ahalyde/apps/ls.lua +Ahalyde/apps/lua.lua +Ahalyde/apps/mv.lua +Ahalyde/apps/rm.lua +Ahalyde/config/shell.cfg +Ahalyde/config/startupapps.cfg +Ahalyde/core/boot.lua +Ahalyde/core/cormgr.lua +Ahalyde/core/datatools.lua +Ahalyde/core/evmgr.lua +Ahalyde/core/fullkb.lua +Ahalyde/core/shell.lua +Ahalyde/core/termlib.lua +Ahalyde/lib/component.lua +Ahalyde/lib/event.lua +Ahalyde/lib/filesystem.lua +Ahalyde/lib/raster.lua" +V1.0.0 +Ahalyde/ +Ahalyde/apps/ +Ahalyde/apps/helpdb/ +Ahalyde/config/ +Ahalyde/core/ +Ahalyde/lib/ diff --git a/halyde/apps/argentum.lua b/halyde/apps/argentum.lua new file mode 100644 index 0000000..a162e91 --- /dev/null +++ b/halyde/apps/argentum.lua @@ -0,0 +1,550 @@ +local packages = {...} +local command = packages[1] +table.remove(packages, 1) +local fs = import("filesystem") +local agReg = import("/argentum/registry.cfg") +if not command then + shell.run("help argentum") + return +end +if not component.list("internet")() then + print("\27[91mThis program requires an internet card to run.") + return +end +local internet = component.proxy(component.list("internet")()) +local source +if table.find(packages, "-s") then + source = table.remove(packages, table.find(packages, "-s") + 1) + table.remove(packages, table.find(packages, "-s")) + print("Using " .. source .. " as package source") +elseif table.find(packages, "--source") then + source = table.remove(packages, table.find(packages, "--source") + 1) + table.remove(packages, table.find(packages, "--source")) + print("Using " .. source .. " as package source") +else + print("Using main registry as package source") +end +if source and source:sub(1, 1) == "/" and source:sub(-1, -1) ~= "/" then + source = source .. "/" +end +local packageList = table.copy(packages) + +local function getFile(path) + if path:sub(1,1) == "/" then + if not fs.exists(path) then + return false, "file does not exist" + end + local handle, data, tmpdata = fs.open(path, "r"), "", nil + repeat + tmpdata = handle:read(math.huge) + data = data .. (tmpdata or "") + until not tmpdata + handle:close() + return data + else + local request, data, tmpdata = nil, "", nil + local status, errorMessage = pcall(function() + request = internet.request(path) + request:finishConnect() + end) + if not status then + return false, errorMessage + end + local responseCode = request:response() + if responseCode and responseCode ~= 200 then + return false, responseCode + end + repeat + tmpdata = request.read(math.huge) + data = data .. (tmpdata or "") + until not tmpdata + return data + end +end + +local i = 1 + +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")) + 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.") + return false + end + local agcfg + local status, errorMessage = pcall(function() + agcfg = func() + end) + if not status then + print("\27[91mCould not fetch Ag config: " .. errorMessage .. "\nPlease contact the package owner.") + 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.") + end + return agcfg +end + +local function doChecks(package) + if not agReg[package] and not source then + print("\27[91mPackage " .. package .. " does not exist.") + return false + end + if fs.exists("/argentum/store/" .. package) then + print("\27[91mPackage " .. package .. " is already installed.") + return false + end + agcfg = getAgConfig(package, source) + if not agcfg then + return false + end + if agcfg[package].dependencies then + for _, dependency in ipairs(agcfg[package].dependencies) do + if not agReg[dependency] and not agcfg[dependency] then + local response = read(nil, "\27[91mPackage " .. package .. " requires dependency " .. dependency .. " that does not exist.\n[A - Abort/s - Skip]") + if response:lower() ~= "s" then + fs.remove("/argentum/store/" .. package) + return false + end + end + end + for _, dependency in pairs(agcfg[package].dependencies) do + print(package .. " depends on " .. dependency) + if not table.find(packages, dependency) and doChecks(dependency) then + table.insert(packages, table.find(packages, package), dependency) + table.insert(packageList, dependency) + i = i + 1 + end + end + end + return true +end + +local function installPackage(package) + print("Installing " .. package .. "...") + local agcfg = getAgConfig(package, source) + if not agcfg then + return false + end + local source = source or agReg[package] + local packageStore = "V" .. agcfg[package].version + if agcfg[package].dependencies then + for _, dependency in ipairs(agcfg[package].dependencies) do + if not agReg[dependency] and not agcfg[dependency] then + local response = read(nil, "\27[91mPackage " .. package .. " requires dependency " .. dependency .. " that does not exist.\n[A - Abort/s - Skip]") + if response:lower() ~= "s" then + fs.remove("/argentum/store/" .. package) + return false + end + end + end + for _, dependency in pairs(agcfg[package].dependencies) do + if agReg[dependency] or agcfg[dependency] then + --installPackage(dependency) + packageStore = packageStore .. "\nD" .. dependency + end + end + end + if agcfg[package].directories then + for _, directory in pairs(agcfg[package].directories) do + if directory:sub(-1, -1) ~= "/" then + directory = directory .. "/" + end + packageStore = "A" .. directory .. "\n" .. packageStore + if not fs.exists(directory) then + fs.makeDirectory(directory) + end + end + end + for _, file in ipairs(agcfg[package].files) do + ::retry:: + print(" Downloading " .. file .. "...") + local data, errorMessage = getFile(source .. agcfg[package].maindir .. file) + if not data then + local response = read(nil, "\27[91mCould not fetch " .. file .. ": " .. errorMessage .. "\n\27[0m[a - Abort/R - Retry/s - Skip]") + if response:lower() == "a" then + fs.remove("/argentum/store/" .. package) + return false + elseif response:lower() == "s" then + goto skip + else + goto retry + end + end + if fs.exists(file) then + if not fs.exists("/argentum/store/" .. package .. "/files/" .. file:match("(.*/)")) then + fs.makeDirectory("/argentum/store/" .. package .. "/files/" .. file:match("(.*/)")) + end + fs.copy(file, "/argentum/store/" .. package .. "/files/" .. file) + packageStore = packageStore .. "\nM" .. file + else + packageStore = packageStore .. "\nA" .. file + end + local handle = fs.open(file, "w") + handle:write(data) + handle:close() + ::skip:: + end + fs.makeDirectory("/argentum/store/" .. package) + local handle = fs.open("/argentum/store/" .. package .. "/package.cfg", "w") + handle:write(packageStore) + handle:close() + return true +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.") + return false + end + local handle, data, tmpdata = fs.open("/argentum/store/" .. package .. "/package.cfg", "r"), "", nil + repeat + tmpdata = handle:read(math.huge) + data = data .. (tmpdata or "") + until not tmpdata + handle:close() + for line in (data.."\n"):gmatch("(.-)\n") do + if line:sub(1, 1) == "A" then + ::retry:: + print(" Removing " .. line:sub(2) .. "...") + if line:sub(-1, -1) == "/" and fs.list(line:sub(2))[1] then + print(" There are still files in " .. line:sub(2) .. ". Skipping.") + else + local result, errorMessage = fs.remove(line:sub(2)) + if not result then + local response = read(nil, "\27[91mFailed to remove " .. line:sub(2) .. ": " .. errorMessage .. "\n\27[0m[a - Abort/r - Retry/S - Skip]") + if response:lower() == "a" then + return false + elseif response:lower() == "r" then + goto retry + end + end + end + elseif line:sub(1, 1) == "M" then + ::retry:: + print(" Reverting " .. line:sub(2) .. "...") + local handle, data, tmpdata = fs.open("/argentum/store/" .. package .. "/files/" .. line:sub(2), "r"), "", nil + if not handle then + local response = read(nil, "\27[91mFailed to revert " .. line:sub(2) .. ": " .. data .. "\n\27[0m[a - Abort/R - Retry/s - Skip]") -- this is pretty stupid but i think the error message would get pushed to data + if response:lower() == "a" then + return false + elseif response:lower() == "s" then + goto skip + else + goto retry + end + end + repeat + tmpdata = handle:read(math.huge) + data = data .. (tmpdata or "") + until not tmpdata + handle:close() + local handle = fs.open(line:sub(2), "w") + handle:write(data) + handle:close() + ::skip:: + end + end + fs.remove("/argentum/store/" .. package .. "/") + return true +end + +-- Update registry +local fails = {} +if command == "install" then + if not packages or not packages[1] then + print("Please specify packages to install.") + return + end + while true do + if not doChecks(packages[i]) then + table.insert(fails, packages[i]) + table.remove(packageList, table.find(packageList, packages[i])) + end + i = i + 1 + if i > #packages then + break + end + end + local answer + if #fails == 0 then + print("Packages that will be installed: " .. table.concat(packageList, ", ")) + if read(nil, "Would you like to proceed? [Y/n] "):lower() == "n" then + return + end + elseif #packageList == 0 then + print("None of the packages can be installed.") + return + else + print("Some packages cannot be installed.") + print("Packages that will be installed: " .. table.concat(packageList, ", ")) + print("Packages that cannot be installed: " .. table.concat(fails, ", ")) + if read(nil, "Would you like to proceed? [y/N] "):lower() ~= "y" then + return + end + end + for _, failedPackage in pairs(fails) do + table.remove(packages, table.find(packages, failedPackage)) + end + fails = {} + for _, package in ipairs(packages) do + if not installPackage(package) then + table.insert(fails, package) + table.remove(packageList, table.find(packageList, package)) + end + end + if #fails == 0 then + print("Installation completed successfully.") + print("Packages installed: " .. table.concat(packageList, ", ")) + elseif #packageList == 0 then + print("All packages failed to install.") + print("Packages that could not be installed: " .. table.concat(fails, ", ")) + else + print("Some packages failed to install.") + print("Packages installed: " .. table.concat(packageList, ", ")) + print("Packages that could not be installed: " .. table.concat(fails, ", ")) + end +elseif command == "remove" then + if not packages or not packages[1] then + print("Please specify packages to remove.") + return + end + while true do + if not fs.exists("/argentum/store/" .. packages[i]) then + print("\27[91mPackage " .. packages[i] .. " is not installed.") + 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.") + 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 Halyde.") + table.insert(fails, packages[i]) + table.remove(packageList, table.find(packageList, packages[i])) + table.remove(packages, table.find(packages, packages[i])) + i = i - 1 + end + i = i + 1 + if i > #packages then + break + end + end + -- do dependency checks + local packagesInstalled = fs.list("/argentum/store") + for _, currentPackage in pairs(packagesInstalled) do + if currentPackage:sub(-1, -1) == "/" and fs.exists("/argentum/store/" .. currentPackage .. "package.cfg") then + local handle, data, tmpdata = fs.open("/argentum/store/" .. currentPackage .. "package.cfg", "r"), "", nil + repeat + tmpdata = handle:read(math.huge) + data = data .. (tmpdata or "") + until not tmpdata + handle:close() + for line in (data.."\n"):gmatch("(.-)\n") do + for i = 1, #packages do + if line == "D" .. packages[i] then + print(packages[i] .. " depends on " .. currentPackage:sub(1, -2)) + if not table.find(packages, currentPackage:sub(1, -2)) then + table.insert(packages, table.find(packages, packages[i]), currentPackage:sub(1, -2)) + table.insert(packageList, currentPackage:sub(1, -2)) + i = i + 1 + end + end + end + end + end + end + local answer + if #fails == 0 then + print("Packages that will be removed: " .. table.concat(packageList, ", ")) + if read(nil, "Would you like to proceed? [Y/n] "):lower() == "n" then + return + end + elseif #packageList == 0 then + print("None of the packages can be removed.") + return + else + print("Some packages cannot be removed.") + print("Packages that will be removed: " .. table.concat(packageList, ", ")) + print("Packages that cannot be removed: " .. table.concat(fails, ", ")) + if read(nil, "Would you like to proceed? [y/N] "):lower() ~= "y" then + return + end + end + for _, failedPackage in pairs(fails) do + table.remove(packages, table.find(packages, failedPackage)) + end + fails = {} + for _, package in ipairs(packages) do + if not removePackage(package) then + table.insert(fails, package) + table.remove(packageList, table.find(packageList, package)) + end + end + if #fails == 0 then + print("Removal completed successfully.") + print("Packages removed: " .. table.concat(packageList, ", ")) + elseif #packageList == 0 then + print("All packages failed to be removed.") + print("Packages that could not be removed: " .. table.concat(fails, ", ")) + else + print("Some packages failed to be removed.") + print("Packages removed: " .. table.concat(packageList, ", ")) + print("Packages that could not be removed: " .. table.concat(fails, ", ")) + end +elseif command == "update" then + if not packages[1] then + local packagesInstalled = fs.list("/argentum/store/") + for _, currentPackage in pairs(packagesInstalled) do + if currentPackage:sub(-1, -1) == "/" and fs.exists("/argentum/store/" .. currentPackage .. "package.cfg") then + table.insert(packages, currentPackage:sub(1, -2)) + table.insert(packageList, currentPackage:sub(1, -2)) + end + end + end + while true do + if not fs.exists("/argentum/store/" .. packages[i]) then + print("\27[91mPackage " .. packages[i] .. " is not installed.") + table.insert(fails, packages[i]) + table.remove(packageList, table.find(packageList, packages[i])) + table.remove(packages, table.find(packages, packages[i])) + i = i - 1 + end + i = i + 1 + if i > #packages then + break + end + end + local answer + if #fails == 0 then + print("Packages that will be updated: " .. table.concat(packageList, ", ")) + if read(nil, "Would you like to proceed? [Y/n] "):lower() == "n" then + return + end + elseif #packageList == 0 then + print("None of the packages can be updated.") + return + else + print("Some packages cannot be updated.") + print("Packages that will be updated: " .. table.concat(packageList, ", ")) + print("Packages that cannot be updated: " .. table.concat(fails, ", ")) + if read(nil, "Would you like to proceed? [y/N] "):lower() ~= "y" then + return + end + end + for _, failedPackage in pairs(fails) do + table.remove(packages, table.find(packages, failedPackage)) + end + fails = {} + for _, package in pairs(packages) do + local agcfg = getAgConfig(package, source) + if not agcfg then + return false + end + local handle, data, tmpdata = fs.open("/argentum/store/" .. package .. "/package.cfg", "r"), "", nil + repeat + tmpdata = handle:read(math.huge) + data = data .. (tmpdata or "") + until not tmpdata + handle:close() + local version = "0.0.0" + for line in (data.."\n"):gmatch("(.-)\n") do + if line:sub(1, 1) == "V" then + version = line:sub(2) + break + end + end + local handle, data, tmpdata = fs.open("/argentum/store/" .. package .. "/package.cfg", "r"), "", nil + repeat + tmpdata = handle:read(math.huge) + data = data .. (tmpdata or "") + until not tmpdata + handle:close() + local version = "0.0.0" + for line in (data.."\n"):gmatch("(.-)\n") do + if line:sub(1, 1) == "V" then + version = line:sub(2) + break + end + end + if agcfg[package].version == version then + print(package .. " is up to date") + goto skip + end + if not removePackage(package) then + table.insert(fails, packages[i]) + table.remove(packageList, table.find(packageList, packages[i])) + table.remove(packages, table.find(packages, packages[i])) + goto skip + end + if not installPackage(package) then + table.insert(fails, packages[i]) + table.remove(packageList, table.find(packageList, packages[i])) + table.remove(packages, table.find(packages, packages[i])) + goto skip + end + ::skip:: + end + if #fails == 0 then + print("Update completed successfully.") + print("Packages updated: " .. table.concat(packageList, ", ")) + elseif #packageList == 0 then + print("All packages failed to update.") + print("Packages that could not update: " .. table.concat(fails, ", ")) + else + print("Some packages failed to update.") + print("Packages updated: " .. table.concat(packageList, ", ")) + print("Packages that could not update: " .. table.concat(fails, ", ")) + end +elseif command == "info" then + if not packages[1] then + print("Please specify a package to show information about.") + return + end + if not agReg[packages[1]] and not source then + print("\27[91mPackage " .. packages[1] .. " does not exist.") + return + end + local agcfg = getAgConfig(packages[1], source) + if not agcfg then + return false + end + print("\27[93m" .. packages[1] .. "\27[0m v" .. agcfg[packages[1]].version .. "\n " .. (agcfg[packages[1]].description or "No description."):gsub("\n", " \n")) +elseif command == "search" then + if not packages[1] then + print("Please specify a search term.") + return + end + local searchResults = {} + for packageName, _ in pairs(agReg) do + if packageName:find(packages[1], 1, true) then + table.insert(searchResults, packageName) + end + end + if not searchResults[1] then + print("No search results found for " .. packages[1] .. ".") + return + end + table.sort(searchResults) + print("Search results: \n " .. table.concat(searchResults, "\n ")) +elseif command == "list" then + local sortedPackages = {} + for packageName, _ in pairs(agReg) do + table.insert(sortedPackages, packageName) + end + table.sort(sortedPackages) + print("List of available Ag packages: \n " .. table.concat(sortedPackages, "\n ")) +else + shell.run("help ag") +end diff --git a/halyde/apps/edit.lua b/halyde/apps/edit.lua index c0f7755..ce093a8 100644 --- a/halyde/apps/edit.lua +++ b/halyde/apps/edit.lua @@ -183,7 +183,7 @@ local function processEvent(args) changesMade = true cursorRenderFlag = true cursorWhite = true - if cursorPosY + scrollPosY - 1 > 1 then + if cursorPosX == 1 and cursorPosY + scrollPosY - 1 > 1 then cursorPosY = cursorPosY - 1 if cursorPosY < 1 then scrollPosY = scrollPosY - 1 @@ -256,7 +256,11 @@ local function save() end local handle, errorMessage = fs.open(savepath, "w") if handle then - handle:write(table.concat(tmpdata, "\n")) + if table.concat(tmpdata, "\n"):sub(-1, -1) == "\n" then + handle:write(table.concat(tmpdata, "\n")) + else + handle:write(table.concat(tmpdata, "\n") .. "\n") -- add a newline at the end to follow POSIX standards + end handle:close() rawset(1, height - 1, "\27[107m\27[30m" .. filestring .. string.rep(" ", width)) else diff --git a/halyde/apps/fetch.lua b/halyde/apps/fetch.lua index 54f2716..72bb967 100644 --- a/halyde/apps/fetch.lua +++ b/halyde/apps/fetch.lua @@ -14,7 +14,7 @@ print("\27[92mComponents\27[0m: "..tostring(componentCounter)) termlib.cursorPosX = 17 print("\27[92mCoroutines\27[0m: "..tostring(#cormgr.corList)) termlib.cursorPosX = 17 -print("\27[92mBattery\27[0m: "..tostring(math.floor(computer.maxEnergy() / computer.energy() * 1000 + 0.5) / 10).."%") +print("\27[92mBattery\27[0m: "..tostring(math.floor(computer.energy() / computer.maxEnergy() * 1000 + 0.5) / 10).."%") termlib.cursorPosX = 17 local totalMemory = computer.totalMemory() local usedMemory = computer.totalMemory() - computer.freeMemory() diff --git a/halyde/apps/helpdb/argentum.txt b/halyde/apps/helpdb/argentum.txt new file mode 100644 index 0000000..4a6a9dd --- /dev/null +++ b/halyde/apps/helpdb/argentum.txt @@ -0,0 +1,18 @@ +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. diff --git a/halyde/apps/helpdb/default.txt b/halyde/apps/helpdb/default.txt index 4ab965a..0cf95be 100644 --- a/halyde/apps/helpdb/default.txt +++ b/halyde/apps/helpdb/default.txt @@ -1,16 +1,17 @@ -All current Halyde shell commands: - cat Concatenates and prints a file. - cd Changes directory. - clear Clears the screen. - cp Copies a file. - echo Prints a message. - fetch Displays system information. - help Shows this. - ls Lists files. - lua Starts the Lua shell. - mv Moves/renames a file. - rm Deletes a file. - edit Opens the text editor. +All default Halyde shell commands: + cat Concatenates and prints a file. + cd Changes directory. + clear Clears the screen. + cp Copies a file. + echo Prints a message. + fetch Displays system information. + help Shows this. + ls Lists files. + lua Starts the Lua shell. + mv Moves/renames a file. + rm Deletes a file. + edit Opens the text editor. + argentum Uses the Argentum package manager. You can get additional information on any app or command by running: help [COMMAND] diff --git a/halyde/apps/helpdb/mkdir.txt b/halyde/apps/helpdb/mkdir.txt new file mode 100644 index 0000000..9acaadd --- /dev/null +++ b/halyde/apps/helpdb/mkdir.txt @@ -0,0 +1,7 @@ +Usage: mkdir [PATH] +Removes files and directories. + + PATH Specifies the path to create the directory in. + +Examples: + mkdir a Creates a directory named a in the current shell working directory. diff --git a/halyde/apps/helpdb/rm.txt b/halyde/apps/helpdb/rm.txt index 82495e4..dce6e8a 100644 --- a/halyde/apps/helpdb/rm.txt +++ b/halyde/apps/helpdb/rm.txt @@ -1,10 +1,7 @@ -Usage: rm [FLAGS] [PATH] +Usage: rm [PATH] Removes files and directories. - -r, --recursive Removes directories and their contents recursively. - -f, --force Ignores nonexistent files or directories. - PATH Specifies the file to be moved/renamed. + PATH Specifies the file to be moved/renamed. Examples: - rm a.txt Removes a.txt in the current shell working directory. - rm -r -f /halyde/core/ Removes everything in /halyde/core/ forcedly and recursively. + rm a.txt Removes a.txt in the current shell working directory. diff --git a/halyde/apps/mkdir.lua b/halyde/apps/mkdir.lua new file mode 100644 index 0000000..2b3b759 --- /dev/null +++ b/halyde/apps/mkdir.lua @@ -0,0 +1,16 @@ +local args = {...} +local directory = args[1] +args = nil +local fs = import("filesystem") + +if not directory then + shell.run("help mkdir") + return +end +if directory:sub(1, 1) ~= "/" then + directory = shell.workingDirectory .. directory +end +if fs.exists(file) then + print("\27[91mAn object already exists at the specified path.") +end +fs.makeDirectory(file) diff --git a/halyde/config/shell.cfg b/halyde/config/shell.cfg index 2750607..d31972f 100644 --- a/halyde/config/shell.cfg +++ b/halyde/config/shell.cfg @@ -1,6 +1,6 @@ local shellcfg = { - ["startupMessage"] = "\n │\n │ ".._OSVERSION..'\n │ Welcome! Type "help" to get started.\n │\n ', -- message shown on startup - ["prompt"] = "\x1b[92m%s > \x1b[0m", -- shell prompt, %s will be replaced with working directory. example: "%s > " turns to "/current/working/directory > " + ["startupMessage"] = "\n │\n │ ".._OSVERSION..'\n │ %s\n │\n ', -- message shown on startup. %s will be replaced with splash message. + ["prompt"] = "\x1b[92m%s > \x1b[0m", -- shell prompt. %s will be replaced with working directory. ["path"] = { -- default locations where programs will be run from "/halyde/apps/" }, ["aliases"] = { -- shell command aliases @@ -14,8 +14,28 @@ local shellcfg = { ["del"] = "rm", ["delete"] = "rm", ["remove"] = "rm", - [".."] = "cd .." - }, ["defaultWorkingDirectory"] = "/home/" -- the working directory that gets set when halyde starts + [".."] = "cd ..", + ["wget"] = "download", + ["ag"] = "argentum" + }, ["defaultWorkingDirectory"] = "/home/", -- the working directory that gets set when halyde starts + ["splashMessages"] = { -- messages shown on startup + "Made by John Haly- I mean Cerulean Blue.", + 'Welcome! Type "help" to get started.', + "Also try KOCOS!", + "Welcome back, Commander. We have no idea what we're doing.", + "99.9% bug-free. The remaining 0.1% are features.", + "0 days since last error.", + "Everything is fine. The fire is decorative.", + "Please don't feed the background processes.", + "Also has fetch!", + "Anything red is no man's land. Trust me.", + "Machine...", + "Abort, Retry, Fail?", + "What's the deal with /argentum/store?", + "So cutting-edge you can't hold it in your hand.", + "Americans are the reason you see colors on-screen. I'm talking about ANSI escape codes, not politics.", + "Shoutout to Ponali!" + } } return shellcfg diff --git a/halyde/core/boot.lua b/halyde/core/boot.lua index aae57b0..6eb31e2 100644 --- a/halyde/core/boot.lua +++ b/halyde/core/boot.lua @@ -1,7 +1,7 @@ local loadfile = ... local filesystem = loadfile("/halyde/lib/filesystem.lua")(loadfile) -_G._OSVERSION = "Halyde 0.10.1" +_G._OSVERSION = "Halyde 1.0.0" function _G.import(module, ...) local args = table.pack(...) diff --git a/halyde/core/shell.lua b/halyde/core/shell.lua index ed7a14b..4730f7a 100644 --- a/halyde/core/shell.lua +++ b/halyde/core/shell.lua @@ -1,7 +1,7 @@ local shellcfg = import("/halyde/config/shell.cfg") import("/halyde/core/termlib.lua") local event = import("event") ---local ocelot = component.proxy(component.list("ocelot")()) +local ocelot = component.proxy(component.list("ocelot")()) local filesystem = import("filesystem") local gpu = component.proxy(component.list("gpu")()) @@ -55,14 +55,14 @@ function _G.shell.run(command) if not args[1] then return end - if filesystem.exists(args[1]) then + if filesystem.exists(args[1]) and not filesystem.isDirectory(args[1]) then foundfile = true local path = args[1] table.remove(args, 1) runAsCoroutine(path, table.unpack(args)) else for _, item in pairs(shellcfg["path"]) do - if filesystem.exists(item..args[1]) then + if filesystem.exists(item..args[1]) and not filesystem.isDirectory(item .. args[1]) then foundfile = true local path = item..args[1] table.remove(args, 1) @@ -71,10 +71,10 @@ function _G.shell.run(command) else -- try to look for it without the file extension local files = filesystem.list(item) for _, file in pairs(files) do - if args[1] == file:match("(.+)%.[^%.]+$") then + if args[1] == file:match("(.+)%.[^%.]+$") and not filesystem.isDirectory(item .. file) then foundfile = true table.remove(args, 1) - runAsCoroutine(item..file, table.unpack(args)) + runAsCoroutine(item .. file, table.unpack(args)) break end end @@ -86,14 +86,14 @@ function _G.shell.run(command) end end -print(shellcfg["startupMessage"]) +print(shellcfg["startupMessage"]:format(shellcfg.splashMessages[math.random(1, #shellcfg.splashMessages)])) while true do coroutine.yield() -- print(shell.workingDirectory .. " >") --print(shellcfg["prompt"]:format(shell.workingDirectory),false) -- termlib.cursorPosX = #(shell.workingDirectory .. " > ") -- termlib.cursorPosY = termlib.cursorPosY - 1 - local shellCommand = read("shell", shellcfg["prompt"]:format(shell.workingDirectory)) + local shellCommand = read("shell", shellcfg.prompt:format(shell.workingDirectory)) shell.run(shellCommand) gpu.freeAllBuffers() end diff --git a/halyde/core/termlib.lua b/halyde/core/termlib.lua index 10861b4..d2ff0ea 100644 --- a/halyde/core/termlib.lua +++ b/halyde/core/termlib.lua @@ -70,20 +70,23 @@ local function parseCodeNumbers(code) return o end -function _G.print(text, endNewLine, wordWrap) +function _G.print(text, endNewLine, textWrap) -- you don't know how tiring this was just for ANSI escape code support if endNewLine == nil then endNewLine = true end - if wordWrap == nil then - wordWrap = true + 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 = "\27[0m" .. text:gsub("\t", " ") text = tostring(text) readBreak = 0 @@ -95,12 +98,17 @@ function _G.print(text, endNewLine, wordWrap) if #section==0 then return end - gpu.set(termlib.cursorPosX,termlib.cursorPosY,section) - termlib.cursorPosX = termlib.cursorPosX+unicode.wlen(section) - if termlib.cursorPosX>width and wordWrap then - newLine() + while true do + gpu.set(termlib.cursorPosX,termlib.cursorPosY,section) + termlib.cursorPosX = termlib.cursorPosX+unicode.wlen(section) + if unicode.wlen(section) > width and textWrap then + newLine() + else + break + end + section = section:sub(width + 1) end - section="" + section = "" end for i=1,#text do diff --git a/halyde/lib/filesystem.lua b/halyde/lib/filesystem.lua index 542bc94..1051058 100644 --- a/halyde/lib/filesystem.lua +++ b/halyde/lib/filesystem.lua @@ -170,4 +170,13 @@ function filesystem.remove(path) return component.invoke(address, "remove", absPath) end +function filesystem.makeDirectory(path) + checkArg(1, path, "string") + local address, absPath = filesystem.processPath(path) + if not address then + return false + end + return component.invoke(address, "makeDirectory", absPath) +end + return(filesystem) diff --git a/webinstall.lua b/webinstall.lua new file mode 100644 index 0000000..3bcbc63 --- /dev/null +++ b/webinstall.lua @@ -0,0 +1,139 @@ +local component = require("component") +if not component.isAvailable("internet") then + io.stderr.write("This program requires an internet card to run.") + return +end +local internet = component.internet +local computer = require("computer") +local fs = require("filesystem") +local installLocation +local drives = {} +for drive in fs.list("/mnt/") do + table.insert(drives, drive) +end +if #drives == 1 and not component.invoke(component.get(drives[1]:sub(1, 3), "filesystem"), "isReadOnly") then + installLocation = drives[1] +elseif #drives == 1 then + io.stderr.write("All drives are read-only.\nHalyde cannot be installed.") +else + local installDrivesText = "Possible drives to install to:" + for i = 1, #drives do + local address = component.get(drives[i]:sub(1, 3), "filesystem") + local fsComponent = component.proxy(address) + if not fsComponent.isReadOnly() then + local label = fsComponent.getLabel() + if label then + installDrivesText = installDrivesText .. "\n " .. tostring(i) .. ". - " .. label .. "(" .. address:sub(1, 5) .. "...)" + else + installDrivesText = installDrivesText .. "\n " .. tostring(i) .. ". - " .. address:sub(1, 5) .. "..." + end + end + end + io.write(installDrivesText .. "\nPlease select a drive by entering its number or \"q\" to quit. ") + local answer + while true do + answer = io.read() + if tonumber(answer) and tonumber(answer) >= 1 and tonumber(answer) <= #drives then + break + elseif answer == "q" then + return + else + print("Answer invalid, try again.") + end + end + installLocation = "/mnt/" .. drives[tonumber(answer)] +end +if not installLocation then + print("All drives are read-only.\nHalyde cannot be installed.") + return +end +io.write("Are you sure you would like to install Halyde to " .. installLocation .. "? This will erase all data on this disk. [Y/n] ") +if io.read():lower() == "n" then + return +end + +-- installation +local computer = require("computer") +local oldFiles = {} +for oldFile in fs.list(installLocation) do + if oldFile ~= "home/" then + table.insert(oldFiles, oldFile) + end +end +local function getFile(url) + local request, data, tmpdata = nil, "", nil + local status, errorMessage = pcall(function() + request = internet.request(url) + request:finishConnect() + end) + if not status then + return false, errorMessage + end + local responseCode = request:response() + if responseCode and responseCode ~= 200 then + return false, responseCode + end + repeat + tmpdata = request.read(math.huge) + data = data .. (tmpdata or "") + until not tmpdata + return data +end + +local function getFile(path) + if path:sub(1,1) == "/" then + if not fs.exists(path) then + return false, "file does not exist" + end + local handle, data, tmpdata = fs.open(path, "r"), "", nil + repeat + tmpdata = handle:read(math.huge) + data = data .. (tmpdata or "") + until not tmpdata + handle:close() + return data + else + local request, data, tmpdata = nil, "", nil + local status, errorMessage = pcall(function() + request = internet.request(path) + request:finishConnect() + end) + if not status then + return false, errorMessage + end + local responseCode = request:response() + if responseCode and responseCode ~= 200 then + return false, responseCode + end + repeat + tmpdata = request.read(math.huge) + data = data .. (tmpdata or "") + until not tmpdata + return data + end +end +print("a") +local webInstallConfig = getFile("https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/main/argentum.cfg") +print("a") +webInstallConfig = load(webInstallConfig) +print("a") +webInstallConfig = webInstallConfig() +print("a") +installationOrder = {"halyde", "edit", "argentum"} +for i = 1, 3 do + local webInstallConfig = webInstallConfig[installationOrder[i]] + print("a") + if webInstallConfig.directories then + for _, directory in pairs(webInstallConfig.directories) do + print("Creating " .. directory .. "...") + fs.makeDirectory(installLocation .. directory) + end + end + for _, file in pairs(webInstallConfig.files) do + print("Downloading " .. file .. "...") + local handle = fs.open(installLocation .. file, "w") + handle:write(getFile("https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/main/" .. file)) + handle:close() + end +end +computer.shutdown(true)