-
-
Save duff/4741476 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
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() |
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
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" |
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
--[[ | |
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 |
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
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") |
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
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