Advertisement
osmarks

crane

Oct 20th, 2018
324
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 13.42 KB | None | 0 0
  1. --[[
  2. License for the LZW compression:
  3. MIT License
  4. Copyright (c) 2016 Rochet2
  5. Permission is hereby granted, free of charge, to any person obtaining a copy
  6. of this software and associated documentation files (the "Software"), to deal
  7. in the Software without restriction, including without limitation the rights
  8. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9. copies of the Software, and to permit persons to whom the Software is
  10. furnished to do so, subject to the following conditions:
  11. The above copyright notice and this permission notice shall be included in all
  12. copies or substantial portions of the Software.
  13. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  16. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  17. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  18. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  19. SOFTWARE.
  20. ]]
  21.  
  22. local util_raw = [[
  23. local function canonicalize(path)
  24.     return fs.combine(path, "")
  25. end
  26.  
  27. local function segments(path)
  28.     if canonicalize(path) == "" then return {} end
  29.     local segs, rest = {}, path
  30.     repeat
  31.         table.insert(segs, 1, fs.getName(rest))
  32.         rest = fs.getDir(rest)
  33.     until rest == ""
  34.     return segs
  35. end
  36.  
  37. local function slice(tab, start, end_)
  38.     return {table.unpack(tab, start, end_)}
  39. end
  40.  
  41. local function compact_serialize(x)
  42.     local t = type(x)
  43.     if t == "number" then
  44.         return tostring(x)
  45.     elseif t == "string" then
  46.         return textutils.serialise(x)
  47.     elseif t == "table" then
  48.         local out = "{"
  49.         for k, v in pairs(x) do
  50.             out = out .. string.format("[%s]=%s,", compact_serialize(k), compact_serialize(v))
  51.         end
  52.         return out .. "}"
  53.     elseif t == "boolean" then
  54.         return tostring(x)
  55.     else
  56.         error("Unsupported type " .. t)
  57.     end
  58. end
  59.  
  60. local function drop_last(t)
  61.     local clone = slice(t)
  62.     local length = #clone
  63.     local v = clone[length]
  64.     clone[length] = nil
  65.     return clone, v
  66. end
  67. ]]
  68.  
  69. local util = loadstring(util_raw .. "\nreturn {segments = segments, slice = slice, drop_last = drop_last, compact_serialize = compact_serialize}")()
  70.  
  71. local runtime = util_raw .. [[
  72. local savepath = ".crane-persistent/" .. fname
  73.  
  74. -- Simple string operations
  75. local function starts_with(s, with)
  76.     return string.sub(s, 1, #with) == with
  77. end
  78. local function ends_with(s, with)
  79.     return string.sub(s, -#with, -1) == with
  80. end
  81. local function contains(s, subs)
  82.     return string.find(s, subs) ~= nil
  83. end
  84.  
  85. local function copy_some_keys(keys)
  86.     return function(from)
  87.         local new = {}
  88.         for _, key_to_copy in pairs(keys) do
  89.             local x = from[key_to_copy]
  90.             if type(x) == "table" then
  91.                 x = copy(x)
  92.             end
  93.             new[key_to_copy] = x
  94.         end
  95.         return new
  96.     end
  97. end
  98.  
  99. local function find_path(image, path)
  100.     local focus = image
  101.     local path = path
  102.     if type(path) == "string" then path = segments(path) end
  103.     for _, seg in pairs(path) do
  104.         if type(focus) ~= "table" then error("Path segment " .. seg .. " is nonexistent or not a directory; full path " .. compact_serialize(path)) end
  105.         focus = focus[seg]
  106.     end
  107.     return focus
  108. end
  109.  
  110. local function get_parent(image, path)
  111.     local init, last = drop_last(segments(path))
  112.     local parent = find_path(image, init) or image
  113.     return parent, last
  114. end
  115.  
  116. -- magic from http://lua-users.org/wiki/SplitJoin
  117. -- split string into lines
  118. local function lines(str)
  119.     local t = {}
  120.     local function helper(line)
  121.         table.insert(t, line)
  122.         return ""
  123.     end
  124.     helper((str:gsub("(.-)\r?\n", helper)))
  125.     return t
  126. end
  127.  
  128. local function make_read_handle(text, binary)
  129.     local lines = lines(text)
  130.     local h = {}
  131.     local line = 0
  132.     function h.close() end
  133.     if not binary then
  134.         function h.readLine() line = line + 1 return lines[line] end
  135.         function h.readAll() return text end
  136.     else
  137.         local remaining = text
  138.         function h.read()
  139.             local by = string.byte(remaining:sub(1, 1))
  140.             remaining = remaining:sub(2)
  141.             return by
  142.         end
  143.     end
  144.     return h
  145. end
  146.  
  147. local function make_write_handle(writefn, binary)
  148.     local h = {}
  149.     function h.close() end
  150.     function h.flush() end
  151.     if not binary then
  152.         function h.write(t) return writefn(t) end
  153.         function h.writeLine(t) return writefn(t .. "\n") end
  154.     else
  155.         function h.write(b) return writefn(string.char(b)) end
  156.     end
  157.     return h
  158. end
  159.  
  160. local function mount_image(i)
  161.     local options = i.options
  162.     local image = i.tree
  163.    
  164.     local filesystem = copy_some_keys {"getName", "combine", "getDir"} (_G.fs)
  165.    
  166.     function filesystem.getFreeSpace()
  167.         return math.huge -- well, it's in-memory...
  168.     end
  169.    
  170.     function filesystem.exists(path)
  171.         return find_path(image, path) ~= nil
  172.     end
  173.    
  174.     function filesystem.isDir(path)
  175.         return type(find_path(image, path)) == "table"
  176.     end
  177.    
  178.     function filesystem.makeDir(path)
  179.         local p, n = get_parent(image, path)
  180.         p[n] = {}
  181.     end
  182.    
  183.     function filesystem.delete(path)
  184.         local p, n = get_parent(image, path)
  185.         p[n] = nil
  186.     end
  187.    
  188.     function filesystem.copy(from, to)
  189.         local pf, nf = get_parent(image, from)
  190.         local contents = pf[nf]
  191.         local pt, nt = get_parent(image, to)
  192.         pt[nt] = contents
  193.     end
  194.    
  195.     function filesystem.move(from, to)
  196.         filesystem.copy(from, to)
  197.         local pf, nf = get_parent(image, from)
  198.         pf[nf] = nil
  199.     end
  200.    
  201.     function filesystem.contents(path)
  202.         return find_path(image, path)
  203.     end
  204.    
  205.     function filesystem.list(path)
  206.         local out = {}
  207.         local dir = find_path(image, path)
  208.         for k, v in pairs(dir) do table.insert(out, k) end
  209.         return out
  210.     end
  211.    
  212.     function filesystem.open(path, mode)
  213.         local parent, childname = get_parent(image, path)
  214.         local node = parent[childname]
  215.         local is_binary = ends_with(mode, "b")
  216.         if starts_with(mode, "r") then
  217.             if type(node) ~= "string" then error(path .. ": not a file!") end
  218.             return make_read_handle(node, is_binary)
  219.         elseif starts_with(mode, "a") or starts_with(mode, "w") then
  220.             local function writer(str)
  221.                 parent[childname] = parent[childname] .. str
  222.                 if options.save_on_change then filesystem.save() end
  223.             end
  224.             if not starts_with(mode, "a") or node == nil then parent[childname] = "" end
  225.             return make_write_handle(writer, is_binary)
  226.         end
  227.     end
  228.    
  229.     function filesystem.find(wildcard)
  230.         -- Taken from Harbor: https://github.com/hugeblank/harbor/blob/master/harbor.lua
  231.         -- Body donated to harbor by gollark, from PotatOS, and apparently indirectly from cclite:
  232.         -- From here: https://github.com/Sorroko/cclite/blob/62677542ed63bd4db212f83da1357cb953e82ce3/src/emulator/native_api.lua
  233.         local function recurse_spec(results, path, spec)
  234.             local segment = spec:match('([^/]*)'):gsub('/', '')
  235.             local pattern = '^' .. segment:gsub('[*]', '.+'):gsub('?', '.') .. '$'
  236.            
  237.             if filesystem.isDir(path) then
  238.                 for _, file in ipairs(filesystem.list(path)) do
  239.                     if file:match(pattern) then
  240.                         local f = filesystem.combine(path, file)
  241.                        
  242.                         if filesystem.isDir(f) then
  243.                             recurse_spec(results, f, spec:sub(#segment + 2))
  244.                         end
  245.                         if spec == segment then
  246.                             table.insert(results, f)
  247.                         end
  248.                     end
  249.                 end
  250.             end
  251.         end
  252.         local results = {}
  253.         recurse_spec(results, '', wildcard)
  254.         return results
  255.     end
  256.    
  257.     function filesystem.getDrive()
  258.         return "crane-vfs-" .. fname
  259.     end
  260.  
  261.     function filesystem.isReadOnly(path)
  262.         return false
  263.     end
  264.  
  265.     local fo = fs.open
  266.     function filesystem.save()
  267.         local f = fo(savepath, "w")
  268.         f.write(compact_serialize(i))
  269.         f.close()
  270.     end
  271.  
  272.     return filesystem
  273. end
  274.  
  275. local function deepmerge(t1, t2)
  276.     local out = {}
  277.     for k, v in pairs(t1) do
  278.         local onother = t2[k]
  279.         if type(v) == "table" and type(onother) == "table" then
  280.             out[k] = deepmerge(v, onother)
  281.         else
  282.             out[k] = v
  283.         end
  284.     end
  285.     for k, v in pairs(t2) do
  286.         if not out[k] then
  287.             out[k] = v
  288.         end
  289.     end
  290.     return out
  291. end
  292.  
  293. local cli_args = {...}
  294.  
  295. local f = fs.open("/rom/apis/io.lua", "r") -- bodge reloading IO library
  296. local IO_API_code = f.readAll()
  297. f.close()
  298.  
  299. local function load_API(code, env, name)
  300.     local e = setmetatable({}, { __index = env })
  301.     load(code, "@" .. name, "t", env)()
  302.     env[name] = e
  303. end
  304.  
  305. local function replacement_require(path)
  306.     return dofile(path)
  307. end
  308.  
  309. local function execute(image, filename)
  310.     local image = image
  311.     if fs.exists(savepath) then
  312.         local f = fs.open(savepath, "r")
  313.         image = deepmerge(image, textutils.unserialise(f.readAll()))
  314.     end
  315.  
  316.     local f = mount_image(image)
  317.  
  318.     local env = setmetatable({ fs = f, rawfs = _G.fs, require = replacement_require,
  319.         os = setmetatable({}, { __index = _ENV.os }), { __index = _ENV })
  320.     load_API(IO_API_code, env, "io")
  321.     local func, err = load(f.contents(filename), "@" .. filename, "t", env)
  322.     if err then error(err)
  323.     else
  324.         env.os.reboot = function() func() end
  325.         return func(unpack(cli_args))
  326.     end
  327. end
  328. ]]
  329.  
  330. -- LZW Compressor
  331. 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
  332.  
  333. 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)(...)]]
  334.  
  335. local function encode_nuls(txt)
  336.     local replace = "\0"
  337.     local temp_replacement = ("__#%d#__"):format(math.random(-131072, 131072))
  338.     local replace_with = "-"
  339.     return txt:gsub(replace, temp_replacement):gsub(replace_with, replace):gsub(temp_replacement, replace_with)
  340. end
  341.  
  342. local function compress_code(c)
  343.     local comp = encode_nuls(compress(c))
  344.     local txt = string.format("%q", comp):gsub("\\(%d%d%d)([^0-9])", function(x, y) return string.format("\\%d%s", tonumber(x), y) end)
  345.     local out = wrapper:format(txt)
  346.     --print(loadstring(out)())
  347.     return out
  348. end
  349.  
  350. local function find_imports(code)
  351.     local imports = {}
  352.  
  353.     for i in code:gmatch "%-%-| CRANE ADD [\"'](.-)[\"']" do
  354.         table.insert(imports, i)
  355.         print("Detected explicit import", i)
  356.     end
  357.     return imports
  358. end
  359.  
  360. local function add(path, content, tree)
  361.     local segs, last = util.drop_last(util.segments(path))
  362.     local deepest = tree
  363.     for k, seg in pairs(segs) do
  364.         if not deepest[seg] then
  365.             deepest[seg] = {}
  366.         end
  367.         deepest = deepest[seg]
  368.     end
  369.     deepest[last] = content
  370. end
  371.  
  372. local function load_from_root(file, tree)
  373.     print("Adding", file)
  374.     if not fs.exists(file) then error(file .. " does not exist.") end
  375.     if fs.isDir(file) then
  376.         for _, f in pairs(fs.list(file)) do
  377.             load_from_root(fs.combine(file, f), tree)
  378.         end
  379.         return
  380.     end
  381.  
  382.     local f = fs.open(file, "r")
  383.     local c = f.readAll()
  384.     f.close()
  385.     add(file, c, tree)
  386.     local imports = find_imports(c)
  387.     for _, i in pairs(imports) do
  388.         load_from_root(i, tree)
  389.     end
  390. end
  391.  
  392. local args = {...}
  393. if #args < 2 then
  394.     error([[Usage:
  395. crane [output] [bundle startup] [other files to bundle] ]])
  396. end
  397.  
  398. local root = args[2]
  399. local ftree = {}
  400.  
  401. for _, wildcard in pairs(util.slice(args, 2)) do
  402.     for _, possibility in pairs(fs.find(wildcard)) do
  403.         load_from_root(possibility, ftree)
  404.     end
  405. end
  406.  
  407.  
  408. local function minify(code)
  409.     local url = "https://osmarks.tk/luamin/"
  410.     http.request(url, code)
  411.     while true do
  412.         local event, result_url, handle = os.pullEvent()
  413.         if event == "http_success" then
  414.             local text = handle.readAll()
  415.             handle.close()
  416.             return text
  417.         elseif event == "http_failure" then
  418.             local text = handle.readAll()
  419.             handle.close()
  420.             error(text)
  421.         end
  422.     end
  423. end
  424.  
  425. ftree[root] = minify(ftree[root])
  426. local serialized_tree = util.compact_serialize({
  427.     tree = ftree,
  428.     options = {
  429.         save_on_change = true
  430.     }
  431. })
  432.  
  433. local function shortest(s1, s2)
  434.     if #s1 < #s2 then return s1 else return s2 end
  435. end
  436.  
  437. local output = minify(([[
  438. local fname = %s
  439. %s
  440. local image = %s
  441. return execute(image, fname)
  442. ]]):format(util.compact_serialize(root), runtime, serialized_tree))
  443.  
  444. local f = fs.open(args[1], "w")
  445. f.write("--| CRANE BUNDLE v2\n" .. shortest(compress_code(output), output))
  446. f.close()
  447.  
  448. print "Done!"
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement