ldd-CC/gitget.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
}