Skip to content

Instantly share code, notes, and snippets.

@duff
Forked from ntalbott/bootstrap.lua
Last active December 12, 2015 08:08
Show Gist options
  • Save duff/4741476 to your computer and use it in GitHub Desktop.
Save duff/4741476 to your computer and use it in GitHub Desktop.
local url = "https://gist.github.com/duff/4741476/raw/gfs.lua"
local body = http.get(url).readAll()
local handle = fs.open("/gfs", "w")
handle.write(body)
handle.close()
require "mockcc"
http._register("https://gist.github.com/duff/4741476/raw/gfs.lua", "gfs body")
require "bootstrap"
assert(fs._at("/gfs"), "gfs program does not exist")
assert(fs._at("/gfs") == "gfs body", "gfs program has the wrong contents")
print "Passed"
--[[
Gist File System
Takes a Gist ID and loads all Lua files into a named directory.
Usage: gfs install <gist id> <name>
gfs refresh <name>
--]]
gfs = {}
local path = "/gists"
function gfs.setup()
if not fs.exists(path) then
fs.makeDir(path)
end
end
function gfs.install(id, name, installTo)
if not id or not name then
gfs.usage()
end
gfs.setup()
local installPath = installTo or fs.combine(path, name)
assert(not fs.exists(installPath), "A gist with that name is already installed")
local body = http.get("https://api.github.com/gists/" .. id).readAll()
local gist = json.decode(body)
assert(gist.files, "File info not found")
fs.makeDir(installPath)
local handle = fs.open(fs.combine(installPath, "id.txt"), "w")
handle.write(id)
handle.close()
for name, details in pairs(gist.files) do
if string.find(name, "%.lua$") then
local newName = string.gsub(name, "%.lua$", "")
handle = fs.open(fs.combine(installPath, newName), "w")
handle.write(details.content)
handle.close()
end
end
end
function gfs.refresh(name)
if not name then
gfs.usage()
end
local installPath = fs.combine(path, name)
local handle = fs.open(fs.combine(installPath, "id.txt"), "r")
assert(handle, "Cannot open file " .. fs.combine(installPath, "id.txt"))
local id = handle.readAll()
assert(id, "Cannot read id.txt")
local tmpPath = fs.combine(path, "_tmp")
gfs.install(id, name, tmpPath)
fs.delete(installPath)
fs.move(tmpPath, installPath)
end
function gfs.go(name)
if not name then
gfs.usage()
end
shell.setDir(fs.combine(path, name))
end
local function build_json()
-- Source: http://hg.prosody.im/trunk/file/tip/util/json.lua
-- License: http://hg.prosody.im/trunk/file/tip/COPYING
local t_insert = table.insert;
local s_char = string.char;
local tonumber = tonumber;
local ipairs = ipairs;
local error = error;
local newproxy = newproxy;
local print = print;
local json = {};
local null = newproxy and newproxy(true) or {};
json.null = null;
local escapes = {
["\""] = "\\\"", ["\\"] = "\\\\", ["\b"] = "\\b",
["\f"] = "\\f", ["\n"] = "\\n", ["\r"] = "\\r", ["\t"] = "\\t"};
local unescapes = {
["\""] = "\"", ["\\"] = "\\", ["/"] = "/",
b = "\b", f = "\f", n = "\n", r = "\r", t = "\t"};
for i=0,31 do
local ch = s_char(i);
if not escapes[ch] then escapes[ch] = ("\\u%.4X"):format(i); end
end
function json.decode(json)
json = json.." "; -- appending a space ensures valid json wouldn't touch EOF
local pos = 1;
local current = {};
local stack = {};
local ch, peek;
local function next()
ch = json:sub(pos, pos);
if ch == "" then error("Unexpected EOF"); end
pos = pos+1;
peek = json:sub(pos, pos);
return ch;
end
local function skipwhitespace()
while ch and (ch == "\r" or ch == "\n" or ch == "\t" or ch == " ") do
next();
end
end
local function skiplinecomment()
repeat next(); until not(ch) or ch == "\r" or ch == "\n";
skipwhitespace();
end
local function skipstarcomment()
next(); next(); -- skip '/', '*'
while peek and ch ~= "*" and peek ~= "/" do next(); end
if not peek then error("eof in star comment") end
next(); next(); -- skip '*', '/'
skipwhitespace();
end
local function skipstuff()
while true do
skipwhitespace();
if ch == "/" and peek == "*" then
skipstarcomment();
elseif ch == "/" and peek == "/" then
skiplinecomment();
else
return;
end
end
end
local readvalue;
local function readarray()
local t = {};
next(); -- skip '['
skipstuff();
if ch == "]" then next(); return t; end
t_insert(t, readvalue());
while true do
skipstuff();
if ch == "]" then next(); return t; end
if not ch then error("eof while reading array");
elseif ch == "," then next();
elseif ch then error("unexpected character in array, comma expected"); end
if not ch then error("eof while reading array"); end
t_insert(t, readvalue());
end
end
local function checkandskip(c)
local x = ch or "eof";
if x ~= c then error("unexpected "..x..", '"..c.."' expected"); end
next();
end
local function readliteral(lit, val)
for c in lit:gmatch(".") do
checkandskip(c);
end
return val;
end
local function readstring()
local s = "";
checkandskip("\"");
while ch do
while ch and ch ~= "\\" and ch ~= "\"" do
s = s..ch; next();
end
if ch == "\\" then
next();
if unescapes[ch] then
s = s..unescapes[ch];
next();
elseif ch == "u" then
local seq = "";
for i=1,4 do
next();
if not ch then error("unexpected eof in string"); end
if not ch:match("[0-9a-fA-F]") then error("invalid unicode escape sequence in string"); end
seq = seq..ch;
end
s = s..s.char(tonumber(seq, 16)); -- FIXME do proper utf-8
next();
else error("invalid escape sequence in string"); end
end
if ch == "\"" then
next();
return s;
end
end
error("eof while reading string");
end
local function readnumber()
local s = "";
if ch == "-" then
s = s..ch; next();
if not ch:match("[0-9]") then error("number format error"); end
end
if ch == "0" then
s = s..ch; next();
if ch:match("[0-9]") then error("number format error"); end
else
while ch and ch:match("[0-9]") do
s = s..ch; next();
end
end
if ch == "." then
s = s..ch; next();
if not ch:match("[0-9]") then error("number format error"); end
while ch and ch:match("[0-9]") do
s = s..ch; next();
end
if ch == "e" or ch == "E" then
s = s..ch; next();
if ch == "+" or ch == "-" then
s = s..ch; next();
if not ch:match("[0-9]") then error("number format error"); end
while ch and ch:match("[0-9]") do
s = s..ch; next();
end
end
end
end
return tonumber(s);
end
local function readmember(t)
skipstuff();
local k = readstring();
skipstuff();
checkandskip(":");
t[k] = readvalue();
end
local function fixobject(obj)
local __array = obj.__array;
if __array then
obj.__array = nil;
for i,v in ipairs(__array) do
t_insert(obj, v);
end
end
local __hash = obj.__hash;
if __hash then
obj.__hash = nil;
local k;
for i,v in ipairs(__hash) do
if k ~= nil then
obj[k] = v; k = nil;
else
k = v;
end
end
end
return obj;
end
local function readobject()
local t = {};
next(); -- skip '{'
skipstuff();
if ch == "}" then next(); return t; end
if not ch then error("eof while reading object"); end
readmember(t);
while true do
skipstuff();
if ch == "}" then next(); return fixobject(t); end
if not ch then error("eof while reading object");
elseif ch == "," then next();
elseif ch then error("unexpected character in object, comma expected"); end
if not ch then error("eof while reading object"); end
readmember(t);
end
end
function readvalue()
skipstuff();
while ch do
if ch == "{" then
return readobject();
elseif ch == "[" then
return readarray();
elseif ch == "\"" then
return readstring();
elseif ch:match("[%-0-9%.]") then
return readnumber();
elseif ch == "n" then
return readliteral("null", null);
elseif ch == "t" then
return readliteral("true", true);
elseif ch == "f" then
return readliteral("false", false);
else
error("invalid character at value start: "..ch);
end
end
error("eof while reading value");
end
next();
return readvalue();
end
return json;
end
json = build_json()
function gfs.usage()
print([=[
Usage: gfs install <gist id> <name>
gfs refresh <name>
gfs go <name>
]=])
error("Command not understood.")
end
if not debug or string.find(debug.getinfo(1).source, arg[0] .. "$") then
local arg = {...}
if gfs[arg[1]] then
gfs[arg[1]](arg[2], arg[3])
else
gfs.usage()
end
end
require "mockcc"
require "gfs"
local function setup()
fs._reset()
http._reset()
end
local function testInstall()
setup()
http._register("https://api.github.com/gists/1", [=[
{
"files": {
"uno.lua": {
"filename": "uno.lua",
"content": "hi"
},
"dos.lua": {
"filename": "dos.lua",
"content": "bye"
},
"skip": {
"filename": "skip",
"content": "no"
}
}
}
]=])
gfs.install("1", "one")
assert(fs._at("/gists/one/id.txt"), "Missing the id file")
assert(fs._read("/gists/one/id.txt") == "1", "The id file is incorrect: " .. fs._read("/gists/one/id.txt"))
assert(fs._at("/gists/one/uno"), "Missing the first file")
assert(fs._read("/gists/one/uno") == "hi", "The first file is incorrect: " .. fs._read("/gists/one/uno"))
assert(fs._at("/gists/one/dos"), "Missing the second file")
assert(fs._read("/gists/one/dos") == "bye", "The second file is incorrect: " .. fs._read("/gists/one/dos"))
assert(not fs._at("/gists/one/skip"), "Did not skip non-Lua file")
end
testInstall()
local function testRefresh()
setup()
fs._write("/gists/two/id.txt", "2")
fs._write("/gists/two/existing", "nice")
fs._write("/gists/two/gone", "ouch")
http._register("https://api.github.com/gists/2", [=[
{
"files": {
"new.lua": {
"content": "sweet"
},
"existing.lua": {
"content": "even better"
}
}
}
]=])
gfs.refresh("two")
assert(fs._at("/gists/two/id.txt"), "Missing the id file")
assert(fs._read("/gists/two/id.txt") == "2", "The id file is incorrect: " .. fs._read("/gists/two/id.txt"))
assert(fs._at("/gists/two/new"), "Missing the new file")
assert(fs._read("/gists/two/new") == "sweet", "The new file is incorrect: " .. fs._read("/gists/two/new"))
assert(fs._at("/gists/two/existing"), "Missing the existing file")
assert(fs._read("/gists/two/existing") == "even better", "The existing file is incorrect: " .. fs._read("/gists/two/existing"))
assert(not fs._at("/gists/two/gone"), "Did not remove the old file")
end
testRefresh()
local function testGo()
setup()
gfs.go("one")
assert(shell._CURRENT == "/gists/one", "Incorrect current directory " .. shell._CURRENT)
end
testGo()
print("Passed")
fs = {
_STORAGE = {},
_reset = function() fs._STORAGE = {} end,
_write = function(path, content) fs._STORAGE[path] = content end,
open = function(path, mode)
local contents = ""
return {
write = function(content)
assert(mode == "w", "Attempting to write mode of " .. mode)
contents = contents .. content
end,
readAll = function()
assert(mode == "r", "Attempting to read mode of " .. mode)
return fs._STORAGE[path]
end,
close = function() fs._STORAGE[path] = contents end
}
end,
exists = function(path) return fs._STORAGE[path] end,
makeDir = function(path) fs._STORAGE[path] = {} end,
combine = function(first, last) return first .. "/" .. last end,
delete = function(path)
for fullpath, contents in pairs(fs._STORAGE) do
if string.find(fullpath, "^" .. path) then
fs._STORAGE[fullpath] = nil
end
end
end,
move = function(from, to)
for fullpath, contents in pairs(fs._STORAGE) do
if string.find(fullpath, "^" .. from) then
local newpath = to .. string.gsub(fullpath, "^" .. from, "")
assert(not fs._STORAGE[newpath], newpath .. " already exists")
fs._STORAGE[newpath] = fs._STORAGE[fullpath]
fs._STORAGE[fullpath] = nil
end
end
end
}
fs._read = fs.exists
fs._at = fs.exists
http = {
_STORAGE = {},
_reset = function() fs._STORAGE = {} end,
_register = function(url, contents)
http._STORAGE[url] = contents
end,
get = function(url)
if not http._STORAGE[url] then error("Unknown url " .. url) end
return {
readAll = function() return http._STORAGE[url] end
}
end
}
shell = {
_CURRENT = "/",
setDir = function(path) shell._CURRENT = path end
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment