mirror of
https://github.com/LDDestroier/CC/
synced 2024-11-17 23:34:52 +00:00
372 lines
9.9 KiB
Lua
372 lines
9.9 KiB
Lua
--[[
|
|
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
|
|
}
|