--[[ Gitget for ComputerCraft A simple GitHub repo downloader! pastebin get TZd5PYgz gitget --]] local verbose = true local programOutput local function interpretArgs(tInput, tArgs) local output = {} local errors = {} local usedEntries = {} for aName, aType in pairs(tArgs) do output[aName] = false for i = 1, #tInput do if not usedEntries[i] then if tInput[i] == aName and not output[aName] then if aType then usedEntries[i] = true if type(tInput[i+1]) == aType or type(tonumber(tInput[i+1])) == aType then usedEntries[i+1] = true if aType == "number" then output[aName] = tonumber(tInput[i+1]) else output[aName] = tInput[i+1] end else output[aName] = nil errors[1] = errors[1] and (errors[1] + 1) or 1 errors[aName] = "expected " .. aType .. ", got " .. type(tInput[i+1]) end else usedEntries[i] = true output[aName] = true end end end end end for i = 1, #tInput do if not usedEntries[i] then output[#output+1] = tInput[i] end end return output, errors end local argData = { ["-b"] = "string", -- string, branch ["-s"] = false, -- boolean, silent ["-y"] = false, -- boolean, automatically accept to overwrite } -- this token has only enough permissions to download files, so don't get any ideas local token = "0f7e97e6524dcb03f79978ff88235a510f5ff4ae" local argList = interpretArgs({...}, argData) local reponame = argList[1] local repopath = argList[2] or "" local outpath = argList[3] or "" local branch = argList["-b"] or "master" local silent = argList["-s"] or false local autoOverwrite = argList["-y"] if outpath and shell then if outpath:sub(1,1) ~= "/" then outpath = fs.combine(shell.dir(), outpath) end end --thank you ElvishJerricco local controls = {["\n"]="\\n", ["\r"]="\\r", ["\t"]="\\t", ["\b"]="\\b", ["\f"]="\\f", ["\""]="\\\"", ["\\"]="\\\\"} local function isArray(t) local max = 0 for k,v in pairs(t) do if type(k) ~= "number" then return false elseif k > max then max = k end end return max == #t end local whites = {['\n']=true; ['\r']=true; ['\t']=true; [' ']=true; [',']=true; [':']=true} function removeWhite(str) while whites[str:sub(1, 1)] do str = str:sub(2) end return str end local function encodeCommon(val, pretty, tabLevel, tTracking) local str = "" local function tab(s) str = str .. ("\t"):rep(tabLevel) .. s end local function arrEncoding(val, bracket, closeBracket, iterator, loopFunc) str = str .. bracket if pretty then str = str .. "\n" tabLevel = tabLevel + 1 end for k,v in iterator(val) do tab("") loopFunc(k,v) str = str .. "," if pretty then str = str .. "\n" end end if pretty then tabLevel = tabLevel - 1 end if str:sub(-2) == ",\n" then str = str:sub(1, -3) .. "\n" elseif str:sub(-1) == "," then str = str:sub(1, -2) end tab(closeBracket) end if type(val) == "table" then assert(not tTracking[val], "Cannot encode a table holding itself recursively") tTracking[val] = true if isArray(val) then arrEncoding(val, "[", "]", ipairs, function(k,v) str = str .. encodeCommon(v, pretty, tabLevel, tTracking) end) else arrEncoding(val, "{", "}", pairs, function(k,v) assert(type(k) == "string", "JSON object keys must be strings", 2) str = str .. encodeCommon(k, pretty, tabLevel, tTracking) str = str .. (pretty and ": " or ":") .. encodeCommon(v, pretty, tabLevel, tTracking) end) end elseif type(val) == "string" then str = '"' .. val:gsub("[%c\"\\]", controls) .. '"' elseif type(val) == "number" or type(val) == "boolean" then str = tostring(val) else error("JSON only supports arrays, objects, numbers, booleans, and strings", 2) end return str end local function encode(val) return encodeCommon(val, false, 0, {}) end local function encodePretty(val) return encodeCommon(val, true, 0, {}) end local decodeControls = {} for k,v in pairs(controls) do decodeControls[v] = k end local function parseBoolean(str) if str:sub(1, 4) == "true" then return true, removeWhite(str:sub(5)) else return false, removeWhite(str:sub(6)) end end local function parseNull(str) return nil, removeWhite(str:sub(5)) end local numChars = {['e']=true; ['E']=true; ['+']=true; ['-']=true; ['.']=true} local function parseNumber(str) local i = 1 while numChars[str:sub(i, i)] or tonumber(str:sub(i, i)) do i = i + 1 end local val = tonumber(str:sub(1, i - 1)) str = removeWhite(str:sub(i)) return val, str end local function parseString(str) str = str:sub(2) local s = "" while str:sub(1,1) ~= "\"" do local next = str:sub(1,1) str = str:sub(2) assert(next ~= "\n", "Unclosed string") if next == "\\" then local escape = str:sub(1,1) str = str:sub(2) next = assert(decodeControls[next..escape], "Invalid escape character") end s = s .. next end return s, removeWhite(str:sub(2)) end local parseValue, parseMember local function parseArray(str) str = removeWhite(str:sub(2)) local val = {} local i = 1 while str:sub(1, 1) ~= "]" do local v = nil v, str = parseValue(str) val[i] = v i = i + 1 str = removeWhite(str) end str = removeWhite(str:sub(2)) return val, str end local function parseObject(str) str = removeWhite(str:sub(2)) local val = {} while str:sub(1, 1) ~= "}" do local k, v = nil, nil k, v, str = parseMember(str) val[k] = v str = removeWhite(str) end str = removeWhite(str:sub(2)) return val, str end function parseMember(str) local k = nil k, str = parseValue(str) local val = nil val, str = parseValue(str) return k, val, str end function parseValue(str) local fchar = str:sub(1, 1) if fchar == "{" then return parseObject(str) elseif fchar == "[" then return parseArray(str) elseif tonumber(fchar) ~= nil or numChars[fchar] then return parseNumber(str) elseif str:sub(1, 4) == "true" or str:sub(1, 5) == "false" then return parseBoolean(str) elseif fchar == "\"" then return parseString(str) elseif str:sub(1, 4) == "null" then return parseNull(str) end return nil end local function decode(str) str = removeWhite(str) t = parseValue(str) return t end local writeToFile = function(filename, contents) local file = fs.open(filename, "w") file.write(contents) file.close() end local sWrite = function(str) if not silent then return write(str) end end local sPrint = function(str) return sWrite(str .. "\n") end -- downloads, but does not write, files from GitHub local function downloadFromGitHub(reponame, repopath, branch, options) options = options or {} branch = branch or "master" options.output = output or {} local oldTextColor = term.getTextColor and term.getTextColor() or colors.white term.setTextColor(term.isColor() and colors.green or colors.lightGray) local headers = { ["Authorization"] = "token " .. token } local iRepoPath, iPath options.loopCount = (options.loopCount or 1) if options.loopCount == 1 then options.initialRepoPath = options.initialRepoPath or repopath end local jason = http.get("https://api.github.com/repos/" .. reponame .. "/contents/" .. (repopath or "") .. "/?ref=" .. branch, headers) if not jason then printError("A file/folder couldn't be downloaded.") else local repo = decode(jason.readAll()) for k,v in pairs(repo) do if v.message then return false else if options.initialRepoPath then iRepoPath = repopath:sub(#options.initialRepoPath + 2) else iRepoPath = repopath end iPath = fs.combine(iRepoPath .. "/" .. (options.filepath or ""), v.name) if v.type == "file" then if options.verbose then sPrint("'" .. fs.combine(repopath, v.name) .. "'") end if options.doWrite then writeToFile(iPath, http.get(v.download_url).readAll()) options.output[iPath] = true else options.output[iPath] = http.get(v.download_url).readAll() end os.queueEvent("gitget_got", v.name, repopath, v.download_url) elseif v.type == "dir" then if options.verbose then sPrint("'" .. fs.combine(repopath, v.name) .. "/'") end options.loopCount = options.loopCount + 1 options.output = downloadFromGitHub(reponame, fs.combine(repopath, v.name), branch, options) options.loopCount = options.loopCount - 1 end end end end term.setTextColor(oldTextColor) return options.output end -- downloads and writes files from GitHub local function getFromGitHub(reponame, repopath, filepath, branch, verbose) local files = downloadFromGitHub(reponame, repopath, branch, { doWrite = true, filepath = filepath, verbose = verbose, }) local amnt = 0 for k,v in pairs(files) do amnt = amnt + 1 end sPrint("Complete.") return files, amnt end local displayHelp = function() local progname = shell and fs.getName(shell.getRunningProgram()) or "gitget" sPrint(progname .. " [owner/repo] [repopath] [output dir]") sPrint(" -b [branch]") sPrint(" -s | runs silent unless asking to overwrite") sPrint(" -y | overwrites without asking") end if not (reponame and repopath and outpath) then displayHelp() else if fs.exists(outpath) and not fs.isDir(outpath) then if autoOverwrite then fs.delete(outpath) sPrint("Overwritten '" .. outpath .. "'.") else print("'" .. outpath .. "' already exists!") print("Overwrite? (Y/N)") local evt,key while true do evt, key = os.pullEvent("key") if key == keys.y then if (not fs.isDir(outpath)) then fs.delete(outpath) end break elseif key == keys.n then print("Abort.") coroutine.yield() return end end end end repopath = (repopath == "*") and "" or repopath local oldtxt = (term.getTextColor and term.getTextColor()) or colors.white sPrint("Downloading...") local files, amnt = getFromGitHub(reponame, repopath, outpath, branch, verbose) term.setTextColor(oldtxt) sPrint("Wrote to \"/" .. fs.combine("", outpath) .. "\" (" .. amnt .. " files)") end return { getFromGitHub = getFromGitHub, downloadFromGitHub = downloadFromGitHub }