Last active
October 4, 2015 08:09
-
-
Save dimitriye98/86963fea038e473df1b6 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-------------- | |
-- JSON Lib -- | |
-------------- | |
-- JSON4Lua: JSON encoding / decoding support for the Lua language. | |
-- json Module. | |
-- Author: Craig Mason-Jones | |
-- Homepage: http://json.luaforge.net/ | |
-- Version: 0.9.40 | |
-- This module is released under the MIT License (MIT). | |
-- edited for brevity | |
local base = _G | |
local decode_scanArray | |
local decode_scanComment | |
local decode_scanConstant | |
local decode_scanNumber | |
local decode_scanObject | |
local decode_scanString | |
local decode_scanWhitespace | |
local encodeString | |
local isArray | |
local isEncodable | |
local function encode (v) | |
-- Handle nil values | |
if v==nil then | |
return "null" | |
end | |
local vtype = base.type(v) | |
-- Handle strings | |
if vtype=='string' then | |
return '"' .. encodeString(v) .. '"' -- Need to handle encoding in string | |
end | |
-- Handle booleans | |
if vtype=='number' or vtype=='boolean' then | |
return base.tostring(v) | |
end | |
-- Handle tables | |
if vtype=='table' then | |
local rval = {} | |
-- Consider arrays separately | |
local bArray, maxCount = isArray(v) | |
if bArray then | |
for i = 1,maxCount do | |
table.insert(rval, encode(v[i])) | |
end | |
else -- An object, not an array | |
for i,j in base.pairs(v) do | |
if isEncodable(i) and isEncodable(j) then | |
table.insert(rval, '"' .. encodeString(i) .. '":' .. encode(j)) | |
end | |
end | |
end | |
if bArray then | |
return '[' .. table.concat(rval,',') ..']' | |
else | |
return '{' .. table.concat(rval,',') .. '}' | |
end | |
end | |
-- Handle null values | |
if vtype=='function' and v==null then | |
return 'null' | |
end | |
base.assert(false,'encode attempt to encode unsupported type ' .. vtype .. ':' .. base.tostring(v)) | |
end | |
local function decode(s, startPos) | |
startPos = startPos and startPos or 1 | |
startPos = decode_scanWhitespace(s,startPos) | |
base.assert(startPos<=string.len(s), 'Unterminated JSON encoded object found at position in [' .. s .. ']') | |
local curChar = string.sub(s,startPos,startPos) | |
-- Object | |
if curChar=='{' then | |
return decode_scanObject(s,startPos) | |
end | |
-- Array | |
if curChar=='[' then | |
return decode_scanArray(s,startPos) | |
end | |
-- Number | |
if string.find("+-0123456789.e", curChar, 1, true) then | |
return decode_scanNumber(s,startPos) | |
end | |
-- String | |
if curChar==[["]] or curChar==[[']] then | |
return decode_scanString(s,startPos) | |
end | |
if string.sub(s,startPos,startPos+1)=='/*' then | |
return decode(s, decode_scanComment(s,startPos)) | |
end | |
-- Otherwise, it must be a constant | |
return decode_scanConstant(s,startPos) | |
end | |
local function null() | |
return null -- so json.null() will also return null ;-) | |
end | |
function decode_scanArray(s,startPos) | |
local array = {} -- The return value | |
local stringLen = string.len(s) | |
base.assert(string.sub(s,startPos,startPos)=='[','decode_scanArray called but array does not start at position ' .. startPos .. ' in string:\n'..s ) | |
startPos = startPos + 1 | |
-- Infinite loop for array elements | |
repeat | |
startPos = decode_scanWhitespace(s,startPos) | |
base.assert(startPos<=stringLen,'JSON String ended unexpectedly scanning array.') | |
local curChar = string.sub(s,startPos,startPos) | |
if (curChar==']') then | |
return array, startPos+1 | |
end | |
if (curChar==',') then | |
startPos = decode_scanWhitespace(s,startPos+1) | |
end | |
base.assert(startPos<=stringLen, 'JSON String ended unexpectedly scanning array.') | |
object, startPos = decode(s,startPos) | |
table.insert(array,object) | |
until false | |
end | |
function decode_scanComment(s, startPos) | |
base.assert( string.sub(s,startPos,startPos+1)=='/*', "decode_scanComment called but comment does not start at position " .. startPos) | |
local endPos = string.find(s,'*/',startPos+2) | |
base.assert(endPos~=nil, "Unterminated comment in string at " .. startPos) | |
return endPos+2 | |
end | |
function decode_scanConstant(s, startPos) | |
local consts = { ["true"] = true, ["false"] = false, ["null"] = nil } | |
local constNames = {"true","false","null"} | |
for i,k in base.pairs(constNames) do | |
--print ("[" .. string.sub(s,startPos, startPos + string.len(k) -1) .."]", k) | |
if string.sub(s,startPos, startPos + string.len(k) -1 )==k then | |
return consts[k], startPos + string.len(k) | |
end | |
end | |
base.assert(nil, 'Failed to scan constant from string ' .. s .. ' at starting position ' .. startPos) | |
end | |
function decode_scanNumber(s,startPos) | |
local endPos = startPos+1 | |
local stringLen = string.len(s) | |
local acceptableChars = "+-0123456789.e" | |
while (string.find(acceptableChars, string.sub(s,endPos,endPos), 1, true) | |
and endPos<=stringLen | |
) do | |
endPos = endPos + 1 | |
end | |
local stringValue = 'return ' .. string.sub(s,startPos, endPos-1) | |
local stringEval = base.loadstring(stringValue) | |
base.assert(stringEval, 'Failed to scan number [ ' .. stringValue .. '] in JSON string at position ' .. startPos .. ' : ' .. endPos) | |
return stringEval(), endPos | |
end | |
function decode_scanObject(s,startPos) | |
local object = {} | |
local stringLen = string.len(s) | |
local key, value | |
base.assert(string.sub(s,startPos,startPos)=='{','decode_scanObject called but object does not start at position ' .. startPos .. ' in string:\n' .. s) | |
startPos = startPos + 1 | |
repeat | |
startPos = decode_scanWhitespace(s,startPos) | |
base.assert(startPos<=stringLen, 'JSON string ended unexpectedly while scanning object.') | |
local curChar = string.sub(s,startPos,startPos) | |
if (curChar=='}') then | |
return object,startPos+1 | |
end | |
if (curChar==',') then | |
startPos = decode_scanWhitespace(s,startPos+1) | |
end | |
base.assert(startPos<=stringLen, 'JSON string ended unexpectedly scanning object.') | |
-- Scan the key | |
key, startPos = decode(s,startPos) | |
base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key) | |
startPos = decode_scanWhitespace(s,startPos) | |
base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key) | |
base.assert(string.sub(s,startPos,startPos)==':','JSON object key-value assignment mal-formed at ' .. startPos) | |
startPos = decode_scanWhitespace(s,startPos+1) | |
base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key) | |
value, startPos = decode(s,startPos) | |
object[key]=value | |
until false -- infinite loop while key-value pairs are found | |
end | |
function decode_scanString(s,startPos) | |
base.assert(startPos, 'decode_scanString(..) called without start position') | |
local startChar = string.sub(s,startPos,startPos) | |
base.assert(startChar==[[']] or startChar==[["]],'decode_scanString called for a non-string') | |
local escaped = false | |
local endPos = startPos + 1 | |
local bEnded = false | |
local stringLen = string.len(s) | |
repeat | |
local curChar = string.sub(s,endPos,endPos) | |
-- Character escaping is only used to escape the string delimiters | |
if not escaped then | |
if curChar==[[\]] then | |
escaped = true | |
else | |
bEnded = curChar==startChar | |
end | |
else | |
-- If we're escaped, we accept the current character come what may | |
escaped = false | |
end | |
endPos = endPos + 1 | |
base.assert(endPos <= stringLen+1, "String decoding failed: unterminated string at position " .. endPos) | |
until bEnded | |
local stringValue = 'return ' .. string.sub(s, startPos, endPos-1) | |
local stringEval = base.loadstring(stringValue) | |
base.assert(stringEval, 'Failed to load string [ ' .. stringValue .. '] in JSON4Lua.decode_scanString at position ' .. startPos .. ' : ' .. endPos) | |
return stringEval(), endPos | |
end | |
function decode_scanWhitespace(s,startPos) | |
local whitespace=" \n\r\t" | |
local stringLen = string.len(s) | |
while ( string.find(whitespace, string.sub(s,startPos,startPos), 1, true) and startPos <= stringLen) do | |
startPos = startPos + 1 | |
end | |
return startPos | |
end | |
function encodeString(s) | |
s = string.gsub(s,'\\','\\\\') | |
s = string.gsub(s,'"','\\"') | |
s = string.gsub(s,'\n','\\n') | |
s = string.gsub(s,'\t','\\t') | |
return s | |
end | |
function isArray(t) | |
-- Next we count all the elements, ensuring that any non-indexed elements are not-encodable | |
-- (with the possible exception of 'n') | |
local maxIndex = 0 | |
for k,v in base.pairs(t) do | |
if (base.type(k)=='number' and math.floor(k)==k and 1<=k) then -- k,v is an indexed pair | |
if (not isEncodable(v)) then return false end -- All array elements must be encodable | |
maxIndex = math.max(maxIndex,k) | |
else | |
if (k=='n') then | |
if v ~= table.getn(t) then return false end -- False if n does not hold the number of elements | |
else -- Else of (k=='n') | |
if isEncodable(v) then return false end | |
end -- End of (k~='n') | |
end -- End of k,v not an indexed pair | |
end -- End of loop across all pairs | |
return true, maxIndex | |
end | |
function isEncodable(o) | |
local t = base.type(o) | |
return (t=='string' or t=='boolean' or t=='number' or t=='nil' or t=='table') or (t=='function' and o==null) | |
end | |
------------------ | |
-- Begin CC-Get -- | |
------------------ | |
local args = {...} | |
function resolveDir(path) | |
local pathAccum = "" | |
for dir in path:gmatch("[^/]+") do | |
if dir ~= "" then | |
pathAccum = fs.combine(pathAccum, path) | |
if not fs.isDir(path) then | |
if fs.exists(path) then | |
fs.delete(path) | |
end | |
fs.makeDir(path) | |
end | |
end | |
end | |
return pathAccum | |
end | |
function resolveFile(path) | |
local dir, name = path:match("^(.-)/?([^/]*)$") | |
if dir ~= "" then | |
dir = resolveDir(dir) | |
end | |
path = fs.combine(dir, name) | |
if fs.isDir(path) then | |
fs.delete(path) | |
end | |
return path | |
end | |
do | |
local function indenter(indent, str) | |
return string.rep(" ", indent)..str | |
end | |
local function serializeImpl(val, indent, flat) | |
if type(val) == "string" then | |
return string.format("%q", val) | |
elseif type(val) == "function" then | |
return "load(\""..string.dump(val).."\")" | |
elseif type(val) == "table" then | |
indent = indent or 0 | |
local inIndent = flat and 0 or indent + 1 | |
local serializedTbl | |
local serializedArray = {} | |
if #val > 0 then | |
for i,v in ipairs(val) do | |
serializedArray[i] = serializeImpl(v) | |
end | |
serializedTbl = {table.concat(serializedArray, ", ")} | |
else | |
serializedTbl = {} | |
end | |
for i,v in pairs(val) do | |
if not serializedArray[i] then | |
local index | |
if type(i) == "string" and not i:sub(1, 2) == "0x" and i:match("^%w*$") then | |
index = indenter(inIndent, i) | |
else | |
index = indenter(inIndent, "["..serializeImpl(i, inIndent).."]") | |
end | |
table.insert(serializedTbl, index.." = "..serializeImpl(v, inIndent)) | |
end | |
end | |
if flat then | |
return "{ "..table.concat(serializedTbl, "; ").."}" | |
else | |
return "{\n"..table.concat(serializedTbl, ";\n").."\n"..indenter(indent, "}") | |
end | |
else | |
return tostring(val) | |
end | |
end | |
function serialize(val) | |
return serializeImpl(val) | |
end | |
end | |
local loadedConfigs = {} | |
function loadConfigFile(path) | |
local fullPath = resolveFile(fs.combine("/.cc-get", path)) | |
if loadedConfigs[fullPath] then | |
return loadedConfigs[fullPath] | |
end | |
local cfgTbl | |
if fs.exists(fullPath) then | |
local file = fs.open(fullPath, "r") | |
local config = file.readAll() | |
file.close() | |
cfgTbl = load("return "..config)() | |
end | |
if type(cfgTbl) ~= "table" then | |
cfgTbl = {} | |
end | |
loadedConfigs[fullPath] = cfgTbl | |
return cfgTbl | |
end | |
function flushConfigs() | |
for i,v in pairs(loadedConfigs) do | |
local handle = fs.open(resolveFile(i), "w") | |
handle.write(serialize(v)) | |
handle.close() | |
end | |
end | |
------------------------ | |
-- Github Getter Code -- | |
------------------------ | |
do | |
local function listDir(repo, branch, path) | |
local filetypes, paths, names, urls = {}, {}, {}, {} | |
if path ~= "" then path = path.."/" end | |
local response = http.get("https://api.github.com/repos/"..repo.."/contents/"..path.."?ref="..branch) | |
if response then | |
response = response.readAll() | |
if response ~= nil then | |
response = decode(response) | |
end | |
else | |
return nil | |
end | |
local out = {} | |
for i, file in pairs(response) do | |
out[i] = { | |
file.type, | |
file.name, | |
file.type == "dir" and file.path or file.download_url | |
} | |
end | |
return out | |
end | |
local function downloadFile(url, path) | |
print("Downloading file: "..url) | |
local content = http.get(url) | |
if not content then return false end | |
local handle = fs.open(resolveFile(path), "w") | |
handle.write(content.readAll()) | |
handle.close() | |
return true | |
end | |
local function downloadDir(repo, branch, path, outPath) | |
local listing = listDir(repo, branch, path) | |
for _, v in ipairs(listing) do | |
local type, name, resource = v[1], v[2], v[3] | |
if type == "file" then | |
downloadFile(resource, fs.combine(outPath, name)) | |
elseif type == "dir" then | |
downloadDir(repo, branch, resource, fs.combine(outPath, name)) | |
end | |
end | |
end | |
function downloadRepo(repo, branch, path) | |
downloadDir(repo, branch, "", path) | |
end | |
end | |
local STUB = "/.cc-get" | |
local REPO_STUB = STUB.."/repos" | |
local PKG_STUB = STUB.."/packages" | |
local commands = {} | |
commands["update"] = function() | |
fs.delete(resolveDir(REPO_STUB)) | |
local repos = loadConfigFile("repolist") | |
for _, v in ipairs(repos) do | |
downloadRepo(v[1], v[2], resolveDir(fs.combine(REPO_STUB, v[1]))) | |
end | |
end | |
commands["add-repo"] = function(repo, branch) | |
local repos = loadConfigFile("repolist") | |
table.insert(repos, {repo, branch or "master"}) | |
end | |
function findPackage(package) | |
local repo = package:match("$(.-)/[^/]*^") | |
if repo then | |
local expRepo = fs.combine(REPO_STUB, repo) | |
if not fs.exists(expRepo) or not fs.isDir(expRepo) then return nil end | |
local expPack = fs.combine(REPO_STUB, package) | |
if fs.exists(expPack) and not fs.isDir(expPack) then | |
return expPack | |
else | |
return nil | |
end | |
else | |
for _, repo in ipairs(fs.list(REPO_STUB)) do | |
local package = findPackage(fs.combine(fs.combine(REPO_STUB, repo), package)) | |
if package then return package end | |
end | |
return nil | |
end | |
end | |
function parsePkgFile(package) | |
local handle = fs.open(resolveFile(package), "r") | |
local pkg = {} | |
for line in handle.lines() do | |
local directive = {} | |
local lookbehind = {} | |
local escaped = false | |
for char in line:gmatch(".") do | |
if escaped then | |
table.insert(lookbehind, char) | |
elseif char == "\\" then | |
escaped = true | |
elseif char:match("%S") then | |
table.insert(lookbehind, char) | |
else | |
table.insert(directive, table.concat(lookbehind)) | |
end | |
end | |
table.insert(pkg, directive) | |
end | |
handle.close() | |
return pkg | |
end | |
local directiveHandlers = {} | |
function directiveHandlers.extern(name) | |
return install(name) | |
end | |
function directiveHandlers.pastebin(key, path) | |
local contentHandle = http.get("http://pastebin.com/raw.php?i="..key) | |
if not contentHandle then | |
print("No such pastebin `"..key.."'") | |
error() | |
end | |
local nameHandle = http.get("http://pastebin.com/"..key) -- Begin fugly hacks | |
local name = nameHandle.readAll():match("<h1>(.-)</h1>") | |
nameHandle.close() | |
local fileHandle = fs.open(resolveFile(fs.combine(path, name)), "w") | |
fileHandle.write(contentHandle.readAll()) | |
contentHandle.close() | |
fileHandle.close() | |
end | |
function directiveHandlers.gist(key, path) | |
local handle = http.get("https://api.github.com/gists/"..key) | |
local response = handle.readAll() | |
handle.close() | |
if not response then | |
print("No such gist `"..key.."'") | |
error() | |
end | |
response = decode(response) | |
for name, data in pairs(response.files) do | |
local fileHandle = fs.open(resolveFile(fs.combine(path, name)), "w") | |
if data.truncated then | |
local contentHandle = http.get(data.raw_url) | |
if not contentHandle then error("Gist API Error") end | |
fileHandle.write(contentHandle.readAll()) | |
contentHandle.close() | |
else | |
fileHandle.write(data.content) | |
end | |
fileHandle.close() | |
end | |
end | |
function directiveHandlers.github(repo, path) | |
local repoStub, branch = repo:match("(.*)|(.-)") | |
if repoStub then | |
repo = repoStub | |
end | |
downloadRepo(repo, branch, resolveDir(fs.combine(fs.combine(path, repo), branch))) | |
end | |
function install(package) | |
local pkgFile = findPackage(package) | |
if not pkgFile then | |
print("No such package `"..package.."'") | |
error() | |
end | |
local pkg = parsePkgFile(package) | |
local okay, err | |
for i, directive in ipairs(pkg) do | |
local dirName = table.remove(directive, 1) | |
if not directiveHandlers[dirName] then | |
print("Malformed directive in package file `"..pkgFile.."' at line "..i) | |
error() | |
end | |
okay, err = pcall(directiveHandlers[dirName], unpack(directive), resolveDir(fs.combine(PKG_STUB, package))) | |
end | |
if not okay then | |
fs.delete(resolveDir(fs.combine(PKG_STUB, package))) | |
error(err) | |
end | |
end | |
commands["install"] = function(...) | |
for _, v in ipairs{...} do | |
install(v) | |
end | |
end | |
function main(...) | |
local args = {...} | |
local command = table.remove(args, 1) | |
commands[command](unpack(args)) | |
flushConfigs() | |
end | |
main(...) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment