mirror of
				https://github.com/LDDestroier/CC/
				synced 2025-10-30 23:12:59 +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
 | |
| }
 | 
