component = require("component") event = require("event") fs = require("filesystem") process = require("process") serial = require("serialization") shell = require("shell") term = require("term") wget = loadfile("/bin/wget.lua") gpu = component.gpu if not component.isAvailable("internet") io.stderr\write("This program requires an internet card to run.") return internet = require("internet") githubContentRoot = "https://raw.githubusercontent.com/" defaultRepoLocationConfig = "{URL_BASE}/repos.cfg" localInstalledPackagesFile = "/etc/spdata.svd" localConfigFile = "/etc/spm.cfg" class Repository new: (name, src, isGithub) => @name = name @src = src @isGithub = isGithub getRepoPath: => if @isGithub return @src .. "/master" @src args, options = shell.parse(...) printUsage = -> print("Sothr Package Manager, use this to browse through and download sothr packaged programs easily") print("Usage:") print("'spm list [-i]' to get a list of all the available program packages") print("'spm list [-i] ' to get a list of available packages containing the specified substring") print(" -i: Only list already installed packages") print("'spm info ' to get further information about a program package") print("'spm install|add [-f] [[-nm][-m]] [path]' to download a package to a directory on your system (or /usr by default)") print("'spm update|upgrade ' to update an already installed package") print("'spm update|upgrade all' to update every already installed package") print("'spm uninstall|remove ' to remove a package from your system") print("'spm copy ' to copy package files to another location") print(" -f: Force creation of directories and overwriting of existing files.") -- Make an internet http/s request for data getContent = (url) -> content = "" result, response = pcall(internet.request, url) if not result return nil for chunk in response content = content .. chunk return content -- Request repository information getRepositories = (repoLocation) -> print("Retrieving Repository " .. repoLocation .. " ...") success, raw_repositories = pcall(getContent, repoLocation) if not success io.stderr\write("Could not connect to the Internet. Please ensure you have an Internet connection.") return -1 deserialized_repositories = serial.unserialize(raw_repositories) repositories = {} for repositoryName, repository in pairs(deserialized_repositories) if repository.link != nil linked_repositories = getRepositories(repository.link) for linkedRepositoryName, linkedRepository in pairs(linked_repositories) repositories[linkedRepositoryName] = linkedRepository continue if repository.src == nil and repository.repo == nil print("Repository malformed/unsupported: " .. repositoryName) continue src = repository.src isGithub = false if src == nil src = githubContentRoot .. repository.repo isGithub = true repositories[repositoryName] = Repository(repositoryName, src, isGithub) repositories -- Get Packages for src getPackages = (src) -> success, packages = pcall(getContent, src .. "/programs.cfg") if not success or not packages return -1 return serial.unserialize(packages) --For sorting table values by alphabet compare = (a, b) -> for i = 1, math.min(#a, #b) if a\sub(i, i) ~= b\sub(i, i) return a\sub(i, i) < b\sub(i, i) return #a < #b -- Download a file from a remote location downloadFile = (url, path, force) -> if options.f or force wget("-fq", url, path) return true else wget("-q", url, path) return true return false -- Read serialized table data from a file readFromFile = (path) -> if not fs.exists(fs.path(path)) fs.makeDirectory(fs.path(path)) if not fs.exists(path) return { -1 } file, msg = io.open(path, "rb") if not file io.stderr\write("Error while trying to read file at " .. path .. ": " .. msg) return serializedTableData = file\read("*a") file\close() return serial.unserialize(serializedTableData) or { -1 } -- Read the locally installed package list readFromLocalInstallFile = -> return readFromFile(localInstalledPackagesFile) -- Read the local configuration file readLocalConfigFile = -> path = localConfigFile if not fs.exists(path) return { -1 } else return readFromFile(path) -- Save the installed packages table to a local file saveToInstalledPackagesFile = (installedPackages) -> file, msg = io.open(localInstalledPackagesFile, "wb") if not file io.stderr\write("Error while trying to save package names: " .. msg) return sPacks = serial.serialize(installedPackages) file\write(sPacks) file\close() -- List all packages listPackages = (filter) -> filter = filter or false if filter filter = string.lower(filter) packageTable = {} print("Receiving Package list...") if not options.i success, repositories = pcall(getRepositories, defaultRepoLocationConfig) if not success or repositories == -1 io.stderr\write("Unable to connect to the Internet.\n") return elseif repositories == nil print("Error while trying to receive repository list") return for _, repository in pairs(repositories) print("Checking Repository " .. repository.src) packages = getPackages(repository\getRepoPath) if packages == nil io.stderr\write("Error while trying to receive package list for " .. repository.src .. "\n") return elseif type(packages) == "table" for package in pairs(packages) if not package.hidden table.insert(packageTable, package) else localPackageTable = {} localPackages = readFromLocalInstallFile() for localPackage in pairs(localPackages) table.insert(localPackageTable, localPackage) packageTable = localPackageTable if filter packages = {} for _, package in ipairs(packageTable) if (#package >= #filter) and string.find(package, filter, 1, true) ~= nil table.insert(packages, package) packageTable = packages table.sort(packageTable, compare) return packageTable -- Pretty print a package table printPackages = (packages) -> if packages == nil or not packages[1] print("No package matching specified filter found.") return term.clear() xRes, yRes = gpu.getResolution() print("--SPM {ENV} Package list--") xCur, yCur = term.getCursor() for _, package in ipairs(packages) term.write(package .. "\n") yCur = yCur + 1 if yCur > yRes - 1 term.write("[Press any key to continue]") event = event.pull("key_down") if event term.clear() print("--SPM {ENV} Package list--") xCur, yCur = term.getCursor() -- Retrieve package information getInformation = (requestedPackage) -> success, repositories = pcall(getRepositories, defaultRepoLocationConfig) if not success or repositories == -1 io.stderr\write("Unable to connect to the Internet.\n") return for _, repository in pairs(repositories) packages = getPackages(repository\getRepoPath) if packages == nil io.stderr\write("Error while trying to receive package list for " .. repository.src .. "\n") elseif type(packages) == "table" for package in pairs(packages) if package == requestedPackage return packages[package], repository.src return nil -- uhhhhhh provideInfo = (pack) -> if not pack printUsage() return pack = string.lower(pack) info = getInformation(pack) if not info print("Package does not exist") return done = false print("--Information about package '" .. pack .. "'--") if info.name print("Name: " .. info.name) done = true if info.version print("Version: " .. info.version) done = true if info.description print("Description: " .. info.description) done = true if info.authors print("Authors: " .. info.authors) done = true if info.note print("Note: " .. info.note) done = true if not done print("No information provided.") installPackage = (requestedPackage, installPath, doUpdate) -> doUpdate = doUpdate or false if not requestedPackage printUsage() return if not installPath and not doUpdate localConfig = readLocalConfigFile() installPath = localConfig.path or "/usr" print("Installing package to " .. installPath .. "...") elseif not doUpdate installPath = shell.resolve(installPath) print("Installing package to " .. installPath .. "...") requestedPackage = string.lower(requestedPackage) locallyInstalledPackages = readFromLocalInstallFile() if not locallyInstalledPackages io.stderr\write("Error while trying to read local package names") return elseif locallyInstalledPackages[1] == -1 table.remove(locallyInstalledPackages, 1) packageInfo, repositoryPath = getInformation(requestedPackage) if not packageInfo print("Package does not exist") return if doUpdate print("Updating package " .. requestedPackage) installPath = nil for filePath, fileInstallPath in pairs(packageInfo.files) for installedFilePath, fileLocalInstallPath in pairs(locallyInstalledPackages[requestedPackage]) if installedFilePath == filePath installPath = string.gsub(fs.path(fileLocalInstallPath), fileInstallPath .. "/?$", "/") break if installPath break installPath = shell.resolve(string.gsub(installPath, "^/?", "/"), nil) if not doUpdate and fs.exists(installPath) if not fs.isDirectory(installPath) if options.f installPath = fs.concat(fs.path(installPath), requestedPackage) fs.makeDirectory(installPath) else print("Path points to a file, needs to be a directory.") return elseif not doUpdate if options.f fs.makeDirectory(installPath) else print("Directory does not exist.") return if locallyInstalledPackages[requestedPackage] and (not doUpdate) print("Package has already been installed") return elseif not locallyInstalledPackages[requestedPackage] and doUpdate print("Package has not been installed.") print("If it has, uninstall it manually and reinstall it.") return if doUpdate term.write("Removing old files...") for installedFilePath, fileLocalInstallPath in pairs(locallyInstalledPackages[requestedPackage]) fs.remove(fileLocalInstallPath) term.write("Done.\n") locallyInstalledPackages[requestedPackage] = {} term.write("Installing Files...") for filePath, requestedInstallPath in pairs(packageInfo.files) installedPath = nil if string.find(requestedInstallPath, "^//") localInstalledPath = string.sub(requestedInstallPath, 2) if not fs.exists(localInstalledPath) fs.makeDirectory(localInstalledPath) installedPath = fs.concat(localInstalledPath, string.gsub(filePath, ".+(/.-)$", "%1"), nil) else localInstalledPath = fs.concat(installPath, requestedInstallPath) if not fs.exists(localInstalledPath) fs.makeDirectory(localInstalledPath) installedPath = fs.concat(installPath, requestedInstallPath, string.gsub(filePath, ".+(/.-)$", "%1"), nil) downloadPath = nil if (packageInfo.minified or options.m) and not options.nm downloadPath = repositoryPath .. "/" .. string.gsub(filePath, "(.+)%.lua$", "%1") .. ".min.lua" else downloadPath = repositoryPath .. "/" .. filePath success = pcall(downloadFile, downloadPath, installedPath) if success locallyInstalledPackages[requestedPackage][filePath] = installedPath if packageInfo.dependencies term.write("Done.\nInstalling Dependencies...") for packageDependency, dependencyPath in pairs(packageInfo.dependencies) installedDependencyPath if string.find(dependencyPath, "^//") installedDependencyPath = string.sub(dependencyPath, 2) else installedDependencyPath = fs.concat(installPath, dependencyPath, string.gsub(packageDependency, ".+(/.-)$", "%1"), nil) if string.lower(string.sub(packageDependency, 1, 4)) == "http" success = pcall(downloadFile, packageDependency, installedDependencyPath) if success locallyInstalledPackages[requestedPackage][packageDependency] = installedDependencyPath else dependencyInfo = getInformation(string.lower(packageDependency)) if not dependencyInfo term.write("\nDependency package " .. packageDependency .. " does not exist.") installPackage(string.lower(packageDependency), fs.concat(installPath, dependencyPath)) term.write("Done.\n") saveToInstalledPackagesFile(locallyInstalledPackages) print("Successfully installed package " .. requestedPackage) copyPackage = (requestedPackage, copyLocation) -> packageInformation, repositoryLocation = getInformation(requestedPackage) if not packageInformation print("Package does not exist") return locallyInstalledFiles = readFromLocalInstallFile() if not locallyInstalledFiles io.stderr\write("Error while trying to read package names") return elseif locallyInstalledFiles[1] == -1 table.remove(locallyInstalledFiles, 1) if not locallyInstalledFiles[requestedPackage] print("Package has not been installed.") print("It must be installed to be copied.") return term.write("Copying package files...") for installedFilePath, fileLocalInstallPath in pairs(locallyInstalledFiles[requestedPackage]) fs.copy(fileLocalInstallPath, copyLocation .. fileLocalInstallPath) if packageInfo.dependencies term.write("Done\nCopying Dependencies...") for packageDependency, _ in pairs(packageInfo.dependencies) if not locallyInstalledFiles[packageDependency] print("Dependency has not been installed.") print("If it has, it is not recognized. Reinstall the parent package: " .. requestedPackage) return copyPackage(packageDependency, copyLocation) term.write("Done.\n") print("Successfully copied package " .. requestedPackage) uninstallPackage = (requestedPackage) -> packageInformation, repositoryLocation = getInformation(requestedPackage) if not packageInformation print("Package does not exist") return locallyInstalledFiles = readFromLocalInstallFile() if not locallyInstalledFiles io.stderr\write("Error while trying to read package names") return elseif locallyInstalledFiles[1] == -1 table.remove(locallyInstalledFiles, 1) if not locallyInstalledFiles[requestedPackage] print("Package has not been installed.") print("If it has, you have to remove it manually.") return term.write("Removing package files...") for installedFilePath, fileLocalInstallPath in pairs(locallyInstalledFiles[requestedPackage]) fs.remove(fileLocalInstallPath) term.write("Done\nRemoving references...") locallyInstalledFiles[requestedPackage] = nil saveToInstalledPackagesFile(locallyInstalledFiles) term.write("Done.\n") print("Successfully uninstalled package " .. requestedPackage) updatePackage = (requestedPackage) -> if requestedPackage == "all" print("Updating everything...") locallyInstalledPackages = readFromLocalInstallFile() if not locallyInstalledPackages io.stderr\write("Error while trying to read package names") return elseif locallyInstalledPackages[1] == -1 table.remove(locallyInstalledPackages, 1) done = false for localPackage in pairs(locallyInstalledPackages) installPackage(localPackage, nil, true) done = true if not done print("No package has been installed so far.") else installPackage(args[2], nil, true) switch args[1] when "list" printPackages(listPackages(args[2])) when "info" provideInfo(args[2]) when "install", "add" installPackage(args[2], args[3], false) when "update", "upgrade" updatePackage(args[2]) when "uninstall", "remove" uninstallPackage(args[2]) when "copy" copyPackage(args[2], args[3]) else printUsage()