Skip to content

Instantly share code, notes, and snippets.

@dimitriye98
Last active October 4, 2015 08:09
Show Gist options
  • Save dimitriye98/86963fea038e473df1b6 to your computer and use it in GitHub Desktop.
Save dimitriye98/86963fea038e473df1b6 to your computer and use it in GitHub Desktop.
--------------
-- 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