Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- --[[
- License for the LZW compression:
- MIT License
- Copyright (c) 2016 Rochet2
- Permission is hereby granted, free of charge, to any person obtaining a copy
- of this software and associated documentation files (the "Software"), to deal
- in the Software without restriction, including without limitation the rights
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- copies of the Software, and to permit persons to whom the Software is
- furnished to do so, subject to the following conditions:
- The above copyright notice and this permission notice shall be included in all
- copies or substantial portions of the Software.
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- SOFTWARE.
- ]]
- local util_raw = [[
- local function canonicalize(path)
- return fs.combine(path, "")
- end
- local function segments(path)
- if canonicalize(path) == "" then return {} end
- local segs, rest = {}, path
- repeat
- table.insert(segs, 1, fs.getName(rest))
- rest = fs.getDir(rest)
- until rest == ""
- return segs
- end
- local function slice(tab, start, end_)
- return {table.unpack(tab, start, end_)}
- end
- local function compact_serialize(x)
- local t = type(x)
- if t == "number" then
- return tostring(x)
- elseif t == "string" then
- return textutils.serialise(x)
- elseif t == "table" then
- local out = "{"
- for k, v in pairs(x) do
- out = out .. string.format("[%s]=%s,", compact_serialize(k), compact_serialize(v))
- end
- return out .. "}"
- elseif t == "boolean" then
- return tostring(x)
- else
- error("Unsupported type " .. t)
- end
- end
- local function drop_last(t)
- local clone = slice(t)
- local length = #clone
- local v = clone[length]
- clone[length] = nil
- return clone, v
- end
- ]]
- local util = loadstring(util_raw .. "\nreturn {segments = segments, slice = slice, drop_last = drop_last, compact_serialize = compact_serialize}")()
- local runtime = util_raw .. [[
- local savepath = ".crane-persistent/" .. fname
- -- Simple string operations
- local function starts_with(s, with)
- return string.sub(s, 1, #with) == with
- end
- local function ends_with(s, with)
- return string.sub(s, -#with, -1) == with
- end
- local function contains(s, subs)
- return string.find(s, subs) ~= nil
- end
- local function copy_some_keys(keys)
- return function(from)
- local new = {}
- for _, key_to_copy in pairs(keys) do
- local x = from[key_to_copy]
- if type(x) == "table" then
- x = copy(x)
- end
- new[key_to_copy] = x
- end
- return new
- end
- end
- local function find_path(image, path)
- local focus = image
- local path = path
- if type(path) == "string" then path = segments(path) end
- for _, seg in pairs(path) do
- if type(focus) ~= "table" then error("Path segment " .. seg .. " is nonexistent or not a directory; full path " .. compact_serialize(path)) end
- focus = focus[seg]
- end
- return focus
- end
- local function get_parent(image, path)
- local init, last = drop_last(segments(path))
- local parent = find_path(image, init) or image
- return parent, last
- end
- -- magic from http://lua-users.org/wiki/SplitJoin
- -- split string into lines
- local function lines(str)
- local t = {}
- local function helper(line)
- table.insert(t, line)
- return ""
- end
- helper((str:gsub("(.-)\r?\n", helper)))
- return t
- end
- local function make_read_handle(text, binary)
- local lines = lines(text)
- local h = {}
- local line = 0
- function h.close() end
- if not binary then
- function h.readLine() line = line + 1 return lines[line] end
- function h.readAll() return text end
- else
- local remaining = text
- function h.read()
- local by = string.byte(remaining:sub(1, 1))
- remaining = remaining:sub(2)
- return by
- end
- end
- return h
- end
- local function make_write_handle(writefn, binary)
- local h = {}
- function h.close() end
- function h.flush() end
- if not binary then
- function h.write(t) return writefn(t) end
- function h.writeLine(t) return writefn(t .. "\n") end
- else
- function h.write(b) return writefn(string.char(b)) end
- end
- return h
- end
- local function mount_image(i)
- local options = i.options
- local image = i.tree
- local filesystem = copy_some_keys {"getName", "combine", "getDir"} (_G.fs)
- function filesystem.getFreeSpace()
- return math.huge -- well, it's in-memory...
- end
- function filesystem.exists(path)
- return find_path(image, path) ~= nil
- end
- function filesystem.isDir(path)
- return type(find_path(image, path)) == "table"
- end
- function filesystem.makeDir(path)
- local p, n = get_parent(image, path)
- p[n] = {}
- end
- function filesystem.delete(path)
- local p, n = get_parent(image, path)
- p[n] = nil
- end
- function filesystem.copy(from, to)
- local pf, nf = get_parent(image, from)
- local contents = pf[nf]
- local pt, nt = get_parent(image, to)
- pt[nt] = contents
- end
- function filesystem.move(from, to)
- filesystem.copy(from, to)
- local pf, nf = get_parent(image, from)
- pf[nf] = nil
- end
- function filesystem.contents(path)
- return find_path(image, path)
- end
- function filesystem.list(path)
- local out = {}
- local dir = find_path(image, path)
- for k, v in pairs(dir) do table.insert(out, k) end
- return out
- end
- function filesystem.open(path, mode)
- local parent, childname = get_parent(image, path)
- local node = parent[childname]
- local is_binary = ends_with(mode, "b")
- if starts_with(mode, "r") then
- if type(node) ~= "string" then error(path .. ": not a file!") end
- return make_read_handle(node, is_binary)
- elseif starts_with(mode, "a") or starts_with(mode, "w") then
- local function writer(str)
- parent[childname] = parent[childname] .. str
- if options.save_on_change then filesystem.save() end
- end
- if not starts_with(mode, "a") or node == nil then parent[childname] = "" end
- return make_write_handle(writer, is_binary)
- end
- end
- function filesystem.find(wildcard)
- -- Taken from Harbor: https://github.com/hugeblank/harbor/blob/master/harbor.lua
- -- Body donated to harbor by gollark, from PotatOS, and apparently indirectly from cclite:
- -- From here: https://github.com/Sorroko/cclite/blob/62677542ed63bd4db212f83da1357cb953e82ce3/src/emulator/native_api.lua
- local function recurse_spec(results, path, spec)
- local segment = spec:match('([^/]*)'):gsub('/', '')
- local pattern = '^' .. segment:gsub('[*]', '.+'):gsub('?', '.') .. '$'
- if filesystem.isDir(path) then
- for _, file in ipairs(filesystem.list(path)) do
- if file:match(pattern) then
- local f = filesystem.combine(path, file)
- if filesystem.isDir(f) then
- recurse_spec(results, f, spec:sub(#segment + 2))
- end
- if spec == segment then
- table.insert(results, f)
- end
- end
- end
- end
- end
- local results = {}
- recurse_spec(results, '', wildcard)
- return results
- end
- function filesystem.getDrive()
- return "crane-vfs-" .. fname
- end
- function filesystem.isReadOnly(path)
- return false
- end
- local fo = fs.open
- function filesystem.save()
- local f = fo(savepath, "w")
- f.write(compact_serialize(i))
- f.close()
- end
- return filesystem
- end
- local function deepmerge(t1, t2)
- local out = {}
- for k, v in pairs(t1) do
- local onother = t2[k]
- if type(v) == "table" and type(onother) == "table" then
- out[k] = deepmerge(v, onother)
- else
- out[k] = v
- end
- end
- for k, v in pairs(t2) do
- if not out[k] then
- out[k] = v
- end
- end
- return out
- end
- local cli_args = {...}
- local f = fs.open("/rom/apis/io.lua", "r") -- bodge reloading IO library
- local IO_API_code = f.readAll()
- f.close()
- local function load_API(code, env, name)
- local e = setmetatable({}, { __index = env })
- load(code, "@" .. name, "t", env)()
- env[name] = e
- end
- local function replacement_require(path)
- return dofile(path)
- end
- local function execute(image, filename)
- local image = image
- if fs.exists(savepath) then
- local f = fs.open(savepath, "r")
- image = deepmerge(image, textutils.unserialise(f.readAll()))
- end
- local f = mount_image(image)
- local env = setmetatable({ fs = f, rawfs = _G.fs, require = replacement_require,
- os = setmetatable({}, { __index = _ENV.os }), { __index = _ENV })
- load_API(IO_API_code, env, "io")
- local func, err = load(f.contents(filename), "@" .. filename, "t", env)
- if err then error(err)
- else
- env.os.reboot = function() func() end
- return func(unpack(cli_args))
- end
- end
- ]]
- -- LZW Compressor
- local a=string.char;local type=type;local b=string.sub;local c=table.concat;local d={}local e={}for f=0,255 do local g,h=a(f),a(f,0)d[g]=h;e[h]=g end;local function i(j,k,l,m)if l>=256 then l,m=0,m+1;if m>=256 then k={}m=1 end end;k[j]=a(l,m)l=l+1;return k,l,m end;local function compress(n)if type(n)~="string"then return nil,"string expected, got "..type(n)end;local o=#n;if o<=1 then return"u"..n end;local k={}local l,m=0,1;local p={"c"}local q=1;local r=2;local s=""for f=1,o do local t=b(n,f,f)local u=s..t;if not(d[u]or k[u])then local v=d[s]or k[s]if not v then return nil,"algorithm error, could not fetch word"end;p[r]=v;q=q+#v;r=r+1;if o<=q then return"u"..n end;k,l,m=i(u,k,l,m)s=t else s=u end end;p[r]=d[s]or k[s]q=q+#p[r]r=r+1;if o<=q then return"u"..n end;return c(p)end
- local wrapper = [[local function y(b)local c="-"local d="__#"..math.random(0,10000)local e="\0";return b:gsub(c,d):gsub(e,c):gsub(d,e)end;local z="decompression failure; please redownload or contact developer";local a=string.char;local type=type;local b=string.sub;local c=table.concat;local d={}local e={}for f=0,255 do local g,h=a(f),a(f,0)d[g]=h;e[h]=g end;local function i(j,k,l,m)if l>=256 then l,m=0,m+1;if m>=256 then k={}m=1 end end;k[j]=a(l,m)l=l+1;return k,l,m end;local function n(j,k,l,m)if l>=256 then l,m=0,m+1;if m>=256 then k={}m=1 end end;k[a(l,m)]=j;l=l+1;return k,l,m end;local function dec(o)if type(o)~="string"then return nil,z end;if#o<1 then return nil,z end;local p=b(o,1,1)if p=="u"then return b(o,2)elseif p~="c"then return nil,z end;o=b(o,2)local q=#o;if q<2 then return nil,z end;local k={}local l,m=0,1;local r={}local s=1;local t=b(o,1,2)r[s]=e[t]or k[t]s=s+1;for f=3,q,2 do local u=b(o,f,f+1)local v=e[t]or k[t]if not v then return nil,z end;local w=e[u]or k[u]if w then r[s]=w;s=s+1;k,l,m=n(v..b(w,1,1),k,l,m)else local x=v..b(v,1,1)r[s]=x;s=s+1;k,l,m=n(x,k,l,m)end;t=u end;return c(r)end;local o,e=dec(y(%s));if e then error(e) end;load(o,"@loader","t",_ENV)(...)]]
- local function encode_nuls(txt)
- local replace = "\0"
- local temp_replacement = ("__#%d#__"):format(math.random(-131072, 131072))
- local replace_with = "-"
- return txt:gsub(replace, temp_replacement):gsub(replace_with, replace):gsub(temp_replacement, replace_with)
- end
- local function compress_code(c)
- local comp = encode_nuls(compress(c))
- local txt = string.format("%q", comp):gsub("\\(%d%d%d)([^0-9])", function(x, y) return string.format("\\%d%s", tonumber(x), y) end)
- local out = wrapper:format(txt)
- --print(loadstring(out)())
- return out
- end
- local function find_imports(code)
- local imports = {}
- for i in code:gmatch "%-%-| CRANE ADD [\"'](.-)[\"']" do
- table.insert(imports, i)
- print("Detected explicit import", i)
- end
- return imports
- end
- local function add(path, content, tree)
- local segs, last = util.drop_last(util.segments(path))
- local deepest = tree
- for k, seg in pairs(segs) do
- if not deepest[seg] then
- deepest[seg] = {}
- end
- deepest = deepest[seg]
- end
- deepest[last] = content
- end
- local function load_from_root(file, tree)
- print("Adding", file)
- if not fs.exists(file) then error(file .. " does not exist.") end
- if fs.isDir(file) then
- for _, f in pairs(fs.list(file)) do
- load_from_root(fs.combine(file, f), tree)
- end
- return
- end
- local f = fs.open(file, "r")
- local c = f.readAll()
- f.close()
- add(file, c, tree)
- local imports = find_imports(c)
- for _, i in pairs(imports) do
- load_from_root(i, tree)
- end
- end
- local args = {...}
- if #args < 2 then
- error([[Usage:
- crane [output] [bundle startup] [other files to bundle] ]])
- end
- local root = args[2]
- local ftree = {}
- for _, wildcard in pairs(util.slice(args, 2)) do
- for _, possibility in pairs(fs.find(wildcard)) do
- load_from_root(possibility, ftree)
- end
- end
- local function minify(code)
- local url = "https://osmarks.tk/luamin/"
- http.request(url, code)
- while true do
- local event, result_url, handle = os.pullEvent()
- if event == "http_success" then
- local text = handle.readAll()
- handle.close()
- return text
- elseif event == "http_failure" then
- local text = handle.readAll()
- handle.close()
- error(text)
- end
- end
- end
- ftree[root] = minify(ftree[root])
- local serialized_tree = util.compact_serialize({
- tree = ftree,
- options = {
- save_on_change = true
- }
- })
- local function shortest(s1, s2)
- if #s1 < #s2 then return s1 else return s2 end
- end
- local output = minify(([[
- local fname = %s
- %s
- local image = %s
- return execute(image, fname)
- ]]):format(util.compact_serialize(root), runtime, serialized_tree))
- local f = fs.open(args[1], "w")
- f.write("--| CRANE BUNDLE v2\n" .. shortest(compress_code(output), output))
- f.close()
- print "Done!"
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement