ComputerCraft Archive

scriptManager

computer utility mc-cc-scripts github

Description

@class SCMScript

Installation

Copy one of these commands into your ComputerCraft terminal:

wget:wget https://raw.githubusercontent.com/mc-cc-scripts/script-manager/master/libs/scm/scriptManager.lua scriptmanager
Archive:wget https://cc.shobie.xyz/cc/get/gh-mc-cc-scripts-script-manager-libs-scm-scriptmanager scriptmanager
Quick Install: wget https://cc.shobie.xyz/cc/get/gh-mc-cc-scripts-script-manager-libs-scm-scriptmanager scriptManager

Usage

Run: scriptManager

Tags

none

Source

View Original Source

Code Preview

--- @class SCMScript
---@field name string
---@field type string<"local"|"program"|"library">
---@field sourceName string
---@field source table<string, string>

---@class SCMScriptManager
local ScriptManager = {
    ---@type SCMScript[]
    scripts = {}
}
SCM.ScriptManager = ScriptManager
do
    local config = function()
        return SCM.Config:getAll()
    end
    local log = function(...) SCM.Log:log(...) end

    function ScriptManager:loadScripts()
        local file = fs.open(config()["configDirectory"] .. config()["scriptFile"], "r")
        if not file then
            self:saveScripts()
        else
            self.scripts = textutils.unserializeJSON(file.readAll() or "")
            file.close()
            if not self.scripts then
                self.scripts = {}
            end
        end
    end

    function ScriptManager:saveScripts()
        local file = fs.open(config()["configDirectory"] .. config()["scriptFile"], "w")
        if not file then
            os.execute("mkdir " .. config["configDirectory"])
            file = fs.open(config["configDirectory"] .. config["scriptFile"], "w")
        end
        file.write("" .. textutils.serializeJSON(self.scripts))
        file.close()
    end

    ---@param script table | nil
    ---@param success boolean
    ---@return boolean
    function ScriptManager:addScript(script, success)
        if not success or not script then return false end
        log("Adding script" .. script.name .. "...")
        local scriptExists = false

        -- Check if script already exists, then update
        for i = 1, #self.scripts, 1 do
            if self.scripts[i].name == script.name and self.scripts[i].type == script.type then
                scriptExists = true
                if self.scripts[i].source[script.sourceName] then
                    self.scripts[i].source[script.sourceName] = script.source[script.sourceName]
                    self:saveScripts()
                    return true
                end
            end
        end
        if not scriptExists then
            log("Script added: " .. script.name)
            table.insert(self.scripts, script)
        else
            log("Script already exists.")
            return false
        end

        self:saveScripts()

        SCM.Autocomplete:addScriptToAutoComplete(script)
        SCM.Autocomplete:prepareAutocomplete()
        SCM.Autocomplete:updateAutocomplete()

        return true
    end

    ---@param name string
    ---@param sourceName string | nil
    ---@return boolean
    function ScriptManager:updateScript(name, sourceName)
        if not sourceName then sourceName = "default" end

        local updateObj = {
            ["name"] = name,
            ["type"] = nil,
            ["sourceName"] = sourceName,
            ["source"] = {}
        }

        for i = 1, #self.scripts, 1 do
            if self.scripts[i].name == name then
                updateObj.source[sourceName] = self.scripts[i].source[sourceName]
                updateObj.type = self.scripts[i].type
            end
        end

        if updateObj.source[sourceName] and updateObj.type then
            self:removeScript(name, true)
            SCM.Net:download(updateObj.source[sourceName], updateObj.type, updateObj)
            return true
        end

        return false
    end

    function ScriptManager:updateAllScripts()
        for i = 1, #self.scripts, 1 do
            self:updateScript(self.scripts[i].name, "default")
        end
    end

    ---@param name string
    ---@param keepScriptConfig boolean | nil
    function ScriptManager:removeScript(name, keepScriptConfig)
        log("Removing script: " .. name)
        local o = {}
        local scriptType = nil

        for i = 1, #self.scripts, 1 do
            if self.scripts[i].name ~= name and keepScriptConfig ~= true then
                table.insert(o, self.scripts[i])
            else
                scriptType = self.scripts[i].type
            end
        end
        if keepScriptConfig ~= true then
            self.scripts = o
            self:saveScripts()
        end

        -- delete file
        local scriptDir = config()[scriptType .. "Directory"]
        if scriptType and (
                fs.exists(scriptDir .. name .. ".lua") or
                fs.exists(scriptDir .. name .. config()[scriptType .. "Suffix"])
            ) then
            fs.delete(scriptDir .. name .. config()[scriptType .. "Suffix"])
            if scriptType == "library" then
                fs.delete(scriptDir .. name .. ".lua")
            end
        end
        if scriptType == "program" then
            fs.delete(name .. ".lua")
        end

        -- update autocomplete
        SCM.Autocomplete:prepareAutocomplete()
        SCM.Autocomplete:updateAutocomplete()
    end

    function ScriptManager:removeAllScripts()
        local tmpScripts = {}
        for i = 1, #self.scripts, 1 do
            table.insert(tmpScripts, self.scripts[i].name)
        end

        for i = 1, #tmpScripts, 1 do
            self:removeScript(tmpScripts[i])
        end
    end

    ---@param name string
    ---@param localPath string | nil | unknown
    function ScriptManager:checkRequirements(name, localPath)
        log("Checking requirements of " .. (localPath or name) .. "...")
        local file
        if localPath then
            file = fs.open(localPath, "r")
            if not file then
                file = fs.open('./' .. localPath .. ".lua", "r")
            end
        elseif fs.open("./" .. config()["libraryDirectory"] .. name .. config()["librarySuffix"] .. "/" .. name .. ".lua", "r") then
            file = fs.open("./" .. config()["libraryDirectory"]
                .. name .. config()["librarySuffix"]
                .. "/" .. name .. ".lua", "r")
        else
            file = fs.open("./" .. config()["libraryDirectory"] .. name .. ".lua", "r")
        end
        if not file then log('File ' .. name .. ' not found') end

        -- Find requirements by searching for comment --@requires name
        local requires = {}
        while true do
            local line = file.read()
            if not line then break end

            local find = string.find(line, "--@requires")
            if find then
                line = string.sub(line, find + 12)
                local lineEnd = string.find(line, " ")

                local scriptName = nil
                if lineEnd then
                    scriptName = string.sub(line, 0, lineEnd - 1)
                else
                    scriptName = string.sub(line, 0)
                end

                requires[#requires + 1] = scriptName
            end
        end
        file.close()

        -- Install missing requirements
        for i = 1, #requires do
            local n = requires[i] --[[@as string]]
            local tmpName, tmpCode = self:splitNameCode(n)
            if tmpCode then n = tmpName --[[@as string]] end

            log("Trying to install " .. n .. "...")

            local scriptExists = false
            for j = 1, #self.scripts, 1 do
                if self.scripts[j].name == n then
                    scriptExists = true
                end
            end

            if not scriptExists then
                if tmpCode then
                    SCM.Net:download(tmpName .. "@" .. tmpCode, "library")
                else
                    SCM.Net:download(n, "library")
                end
            else
                log(n .. " already exists.")
            end

            self:checkRequirements(n --[[@as string]])
        end
    end

    --- used when no script with the name was found online
    --- searches locally for the script
    ---@param name string
    ---@return any | nil
    local function fallbackRequire(name)
        log(name .. " not found online, try to find locally")
        --- if script does not exist
        local possiblePath = {
            name,
            config()["libraryDirectory"] .. name,
            config()["libraryDirectory"] .. name .. "/" .. name,
            config()["libraryDirectory"] .. name .. "/" .. "init.lua"
        }
        local script
        local success
        ---TryFunction for Require
        ---@param path string
        ---@return any
        local function tryRequire(path)
            return require(path)
        end

        for _, path in pairs(possiblePath) do
            success, script = pcall(tryRequire, path)
            if success then
                ScriptManager:checkRequirements(name, path)
                return script
            end
        end
        log("Could not load " .. name)
        return nil
    end

    ---@param name string
    ---@return any
    function ScriptManager:load(name)
        log("Loading " .. name .. "...")
        local scriptExists = false
        for i = 1, #self.scripts, 1 do
            if self.scripts[i].name == name then
                scriptExists = true
            end
        end
        if not scriptExists then
            SCM.Net:download(name, "library")
        end

        scriptExists = false
        for i = 1, #self.scripts, 1 do
            if self.scripts[i].name == name then
                scriptExists = true
            end
        end

        if scriptExists then
            self:checkRequirements(name)
            local path = "./" .. config()["libraryDirectory"] .. name
            local script = require(path)
            return script
        end

        return fallbackRequire(name)
    end

    ---@param str string
    ---@return string | nil
    ---@return string | nil
    function ScriptManager:splitNameCode(str)
        local separator = string.find(str, "@")

        if separator then
            local name = string.sub(str, 1, separator - 1)
            local code = string.sub(str, separator + 1)
            return name, code
        end

        return nil, nil
    end
end

return ScriptManager