Advertisement
MKlegoman357

Pack

Jul 19th, 2015
566
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 19.05 KB | None | 0 0
  1. --[[
  2.   Copyright (c) 2015 Mantas KlimaĊĦauskas (MKlegoman357)
  3.  
  4.   Permission is hereby granted, free of charge, to any person obtaining a copy
  5.   of this software and associated documentation files (the "Software"), to
  6.   deal in the Software without restriction, including without limitation the
  7.   rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  8.   sell copies of the Software, and to permit persons to whom the Software is
  9.   furnished to do so, subject to the following conditions:
  10.  
  11.   The above copyright notice and this permission notice shall be included in
  12.   all copies or substantial portions of the Software.
  13.  
  14.   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15.   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16.   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17.   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  18.   WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  19.   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  20. ]]
  21.  
  22. local args = {...}
  23. local helpArgs = (function ()
  24.   local help = {"help", "?", "-help", "-h"}
  25.   for i, arg in ipairs(help) do help[arg] = true; help[i] = nil; end
  26.   return help
  27. end)()
  28. local x, y = term.getCursorPos()
  29.  
  30. local CLAMSHELL = type(shell.version) == "function" and type(shell.version()) == "string" and not not shell.version():match("ClamShell")
  31.  
  32. local function getUsage (str)
  33.   str = str or ""
  34.   local l = function (t) str = str .. t .. "\n" end
  35.  
  36.   l("Usage:")
  37.   l(fs.getName(shell.getRunningProgram()) .. " <main file> <destination file> [--pack-all | [[-i] folders/files/%self%[;(r|w|rw)]] [-e folders/files] [-p path;(r|w|rw)] [-r path] [--include-self]]")
  38.  
  39.   return str
  40. end
  41.  
  42. local function getHelp (str)
  43.   str = str or ""
  44.   local l = function (t) str = str .. (t or "") .. "\n" end
  45.  
  46.   l("Packs files and folders into a runnable package. The resulting package, when ran, will override the fs API to include all the packed files and folders and restore it when the package stops or errors.")
  47.   l()
  48.   l("Required arguments:")
  49.   l("* <main file> - the Lua program that will be ran when you run the resulting file. This program will act as if it is the <destination file>.")
  50.   l("* <destination file> - the path of the resulting runnable file package.")
  51.   l()
  52.   l("Optional arguments:")
  53.   l("* --pack-all - will pack all the files in the current directory. This *must* be the third argument. After this argument no other arguments will be accepted.")
  54.   l("* [-i] folder(s)/file(s)/%self%[;r|w|rw] - the 'include' list of paths separated by spaces ( ) or colons (:). If one of the paths is '%self%' then this program will be included in the resulting package. To change the permissions of a path group separated by colons (:) add a semicolon (;) at the end of a list and then either 'r' - read-only, 'w' - write-only or 'rw' - read and write (default). The '-i' is optional if the file list is the third argument.")
  55.   l("* -e folder(s)/file(s) - the 'exclude' list of paths separated by spaces ( ) or colons (:).")
  56.   l("* -p path;(r|w|rw) - list of resulting paths separated by spaces ( ) or colons (:) indicating their permissions.")
  57.   l("* -r path - the root directory in which to virtually place packed files.")
  58.   l("* --include-self - will include this program in the resulting package.")
  59.   l()
  60.   l("Notes:")
  61.   l("* This program works with binary files.")
  62.   l("* include, exclude and permissions paths allow the use of wildcards (*). ex.: -i /foo/*.lua -e /bar/*/foo")
  63.   l("* You can use -include, -exclude, -perms (or -permissions) and -root instead of -i, -e, -p and -r.")
  64.  
  65.   return str
  66. end
  67.  
  68. if helpArgs[args[1]] then
  69.   if CLAMSHELL then
  70.     print(getUsage() .. "\n" .. getHelp())
  71.   else
  72.     textutils.pagedPrint(getUsage() .. "\n" .. getHelp(), y - 3)
  73.   end
  74.  
  75.   return
  76. elseif #args < 2 then
  77.   if term.isColor() then
  78.     term.setTextColor(colors.red)
  79.   end
  80.  
  81.   if CLAMSHELL then
  82.     print(getUsage())
  83.   else
  84.     textutils.pagedPrint(getUsage(), y - 3)
  85.   end
  86.  
  87.   error()
  88. end
  89.  
  90. --// Helpers \\--
  91.  
  92. local function yield ()
  93.   os.queueEvent("PACK_YIELD"); os.pullEvent()
  94. end
  95.  
  96. local function cleanPath (path)
  97.   return ("/" .. path .. "/"):gsub("[/\\]+", "/"):gsub("/([^/]*)/%.%./", "/"):match("^/?(.-)/?$")
  98. end
  99.  
  100. local function isin (val, tab)
  101.   for k, v in pairs(tab) do
  102.     if v == val then
  103.       return true, k
  104.     end
  105.   end
  106.  
  107.   return false
  108. end
  109.  
  110. local function bet (n, n1, n2)
  111.   return n >= n1 and n <= n2
  112. end
  113.  
  114. local special = {
  115.   "[", "]", ".", "+", "-", "?", "%", "*", "(", ")"
  116. }
  117.  
  118. local function find (search, files)
  119.   search = cleanPath(search)
  120.  
  121.   local list = {}
  122.   local path = search:match("[^/]*")
  123.   local pattern = "^" .. path:gsub(".", function (c) return c == "*" and "[^/]*" or isin(c, special) and "%" .. c or c end) .. "$"
  124.   local isLast = path == search
  125.  
  126.   for name, file in pairs(files) do
  127.     if name:find(pattern) then
  128.       if isLast then
  129.         list[#list + 1] = name
  130.       elseif type(file) == "table" then
  131.         for i, found in ipairs(find(search:sub(#path + 2), file)) do
  132.           list[#list + 1] = name .. "/" .. found
  133.         end
  134.       end
  135.     end
  136.   end
  137.  
  138.   return list
  139. end
  140.  
  141. local function addPerm (path, perm, perms)
  142.   path = cleanPath(path)
  143.  
  144.   for file in pairs(perms) do
  145.     if path:sub(1, #file) == file and (path .. "/"):sub(#file + 1, #file + 1) == "/" then
  146.       return
  147.     end
  148.   end
  149.  
  150.   perms[path] = perm
  151. end
  152.  
  153. local function toByte (byte)
  154.   return "\\" .. byte
  155. end
  156.  
  157. local escape = {
  158.   ["\n"] = "\\n";
  159.   ["\r"] = "\\r";
  160.   ["\t"] = "\\t";
  161.   ["\b"] = "\\b";
  162.   ["'"] = "\\'";
  163.   ["\\"] = "\\\\";
  164. }
  165.  
  166. local function buildFileList (path, exclude)
  167.   local result
  168.  
  169.   if not exclude[path] then
  170.     yield()
  171.  
  172.     if fs.isDir(path) then
  173.       result = {}
  174.  
  175.       for i, file in ipairs(fs.list(path)) do
  176.         result[file] = buildFileList(fs.combine(path, file), exclude)
  177.       end
  178.     elseif fs.exists(path) then
  179.       local file = fs.open(path, "rb")
  180.       local prevEsc = false
  181.  
  182.       result = ""
  183.  
  184.       if file then
  185.         local i = 1
  186.  
  187.         for byte in file.read do
  188.           local char = string.char(byte)
  189.  
  190.           if escape[char] then
  191.             result = result .. escape[char]
  192.             prevEsc = false
  193.           elseif bet(byte, 32, 47) or bet(byte, 58, 95) or bet(byte, 97, 126) then
  194.             result = result .. char
  195.             prevEsc = false
  196.           elseif bet(byte, 48, 57) then
  197.             if prevEsc then
  198.               result = result .. toByte(byte)
  199.             else
  200.               result = result .. char
  201.               prevEsc = false
  202.             end
  203.           else
  204.             result = result .. toByte(byte)
  205.             prevEsc = true
  206.           end
  207.  
  208.           if i > 10000 then
  209.             i = 1
  210.           else
  211.             i = i + 1
  212.           end
  213.         end
  214.       else
  215.         printError("Could not open file '" .. path .. "'. Excluding it from the package.")
  216.       end
  217.  
  218.       file.close()
  219.     else
  220.       printError("File or folder '" .. path .. "' does not exist.")
  221.     end
  222.   end
  223.  
  224.   return result
  225. end
  226.  
  227. local function serialize (fh, file, i)
  228.   i = i or 0
  229.  
  230.   if type(file) == "string" then
  231.     fh.writeLine("'" .. file .. "';")
  232.   else
  233.     fh.writeLine("{")
  234.  
  235.     for name, subFile in pairs(file) do
  236.       fh.write(string.rep(" ", i + 2) .. "['" .. name .. "']=")
  237.       serialize(fh, subFile, i + 2)
  238.     end
  239.  
  240.     fh.writeLine(string.rep(" ", i) .. "};")
  241.   end
  242. end
  243.  
  244. local function serializePermissions (fh, perms)
  245.   fh.writeLine("{")
  246.  
  247.   for path, perm in pairs(perms) do
  248.     fh.writeLine("  ['" .. path .. "']='" .. perm .. "';")
  249.   end
  250.  
  251.   fh.writeLine("}")
  252. end
  253.  
  254. --// Main program \\--
  255.  
  256. local mainFile, destination = shell.resolve(args[1]), shell.resolve(args[2])
  257. local packAll = args[3] == "--pack-all"
  258.  
  259. local root = nil
  260. local include = {}
  261. local exclude = {
  262.   [shell.getRunningProgram()] = not packAll or nil;
  263. }
  264. local perms = {}
  265. local permsArg = {}
  266.  
  267. if not fs.exists(mainFile) then
  268.   error("Main program '" .. mainFile .. "' does not exist.", 0)
  269. elseif fs.isDir(mainFile) then
  270.   error("Main program '" .. mainFile .. "' cannot be a folder.", 0)
  271. elseif fs.isReadOnly(destination) then
  272.   error("The destination path '" .. destination .. "' is read-only.", 0)
  273. elseif fs.exists(destination) then
  274.   print("Destination " .. (fs.isDir(destination) and "folder" or "file") .. " '" .. destination .. "' already exists. Do you want to overwrite it? (y/N)")
  275.  
  276.   local overwrite = read()
  277.  
  278.   if overwrite:sub(1, 1):lower() ~= "y" then
  279.     error("Packing canceled.", 0)
  280.   end
  281.  
  282.   exclude[destination] = true
  283. end
  284.  
  285. do
  286.   local ok, err = loadfile(mainFile)
  287.  
  288.   if not ok then
  289.     error("'" .. mainFile .. "' is not a valid Lua program or has syntax errors:\n" .. err, 0)
  290.   end
  291. end
  292.  
  293. if packAll then
  294.   for i, path in ipairs(fs.list(shell.dir())) do
  295.     include[shell.resolve(path)] = true
  296.   end
  297. else
  298.   local switches = {
  299.     "-i", "-include";
  300.     "-e", "-exclude";
  301.     "-r", "-root";
  302.     "-p", "-perms", "-permissions";
  303.   }
  304.  
  305.   local switch = "i"
  306.   local prevSwitch = "i"
  307.  
  308.   for i = 3, #args do
  309.     local arg = args[i]:lower()
  310.  
  311.     if arg == "--include-self" then
  312.       exclude[shell.getRunningProgram()] = nil
  313.       include[shell.getRunningProgram()] = true
  314.     elseif isin(arg, switches) then
  315.       prevSwitch = switch
  316.       switch = arg:sub(2, 2)
  317.     elseif switch == "r" then
  318.       root = cleanPath(arg)
  319.       switch = prevSwitch
  320.     elseif switch == "p" then
  321.       local paths, perm = arg:match("^([^;]+);([rw]+)$")
  322.  
  323.       if not paths then
  324.         error("Invalid path '" .. arg .. "'.\nUse the format:\npath1[:path2[...]];(r|w|rw)", 0)
  325.       end
  326.  
  327.       for path in paths:gmatch("[^:]+") do
  328.         permsArg[cleanPath(path)] = perm
  329.       end
  330.     elseif switch == "e" then
  331.       for path in arg:gmatch("[^:]+") do
  332.         for i, file in ipairs(fs.find(shell.resolve(path))) do
  333.           exclude[file] = true
  334.         end
  335.       end
  336.     elseif switch == "i" then
  337.       local paths, perm = arg:match("^([^;]+);?([rw]*)$")
  338.  
  339.       if not paths then
  340.         error("Invalid path '" .. arg .. "'.\nUse the format:\npath1[:path2[...]][;(r|w|rw)]", 0)
  341.       end
  342.  
  343.       perm = #perm ~= 0 and perm or nil
  344.  
  345.       for path in paths:gmatch("[^:]+") do
  346.         if path == "%self%" then
  347.           exclude[shell.getRunningProgram()] = nil
  348.           include[shell.getRunningProgram()] = true
  349.           addPerm(fs.getName(shell.getRunningProgram()), perm, perms)
  350.         else
  351.           for i, file in ipairs(fs.find(shell.resolve(path))) do
  352.             include[file] = true
  353.             addPerm(fs.getName(file), perm, perms)
  354.           end
  355.         end
  356.       end
  357.     end
  358.   end
  359.  
  360.   if not next(include) then
  361.     local drive = fs.getDrive(shell.dir())
  362.  
  363.     for i, path in ipairs(fs.list(shell.dir())) do
  364.       path = shell.resolve(path)
  365.  
  366.       if fs.getDrive(path) == drive then
  367.         include[path] = true
  368.       end
  369.     end
  370.   end
  371. end
  372.  
  373. write("Building file list. ")
  374.  
  375. local files = {}
  376. local main = buildFileList(mainFile, {})
  377.  
  378. for path in pairs(include) do
  379.   files[fs.getName(path)] = buildFileList(path, exclude)
  380. end
  381.  
  382. print("Done")
  383.  
  384. if next(permsArg) then
  385.   write("Setting permissions. ")
  386.  
  387.   for paths, perm in pairs(permsArg) do
  388.     for path in paths:gmatch("[^:]+") do
  389.       for i, file in ipairs(find(path, files)) do
  390.         addPerm(file, perm, perms)
  391.       end
  392.     end
  393.   end
  394.  
  395.   print("Done")
  396. end
  397.  
  398. if fs.exists(destination) then
  399.   print("'" .. destination .. "' already exists, deleting.")
  400.  
  401.   local ok, err = pcall(fs.delete, destination)
  402.  
  403.   if not ok then
  404.     error("Could not delete '" .. destination .. "':\n" .. err, 0)
  405.   end
  406. end
  407.  
  408. print("Writing to file started.")
  409.  
  410. local file = fs.open(destination, "w")
  411.  
  412. if not file then
  413.   error("Could not open destination file '" .. destination .. "'.", 0)
  414. end
  415.  
  416. local ok, err = pcall(function ()
  417.  
  418. file.writeLine[[
  419. -- packed using Pack by MKlegoman357
  420. ]]
  421.  
  422. write("Serializing files. ")
  423.  
  424. file.writeLine("--# FILES #--")
  425. file.write("local files = ")
  426. serialize(file, files)
  427. file.writeLine("--# FILES END #--")
  428.  
  429. print("Done")
  430. write("Serializing main program. ")
  431.  
  432. file.writeLine("--# MAIN #--")
  433. file.write("local main = ")
  434. serialize(file, main)
  435. file.writeLine("--# MAIN END #--")
  436.  
  437. print("Done")
  438. write("Serializing file permissions. ")
  439.  
  440. file.writeLine("--# PERMISSIONS #--")
  441. file.write("local perms = ")
  442. serializePermissions(file, perms)
  443. file.writeLine("--# PERMISSIONS END #--")
  444.  
  445. print("Done")
  446. write("Appending the rest of the file. ")
  447.  
  448. file.writeLine("--# ROOT #--")
  449. file.writeLine("local root = " .. textutils.serialize(root))
  450. file.writeLine("--# ROOT END #--")
  451.  
  452. file.writeLine([[
  453. local env = getfenv and getfenv() or _ENV
  454. local _fs = {}
  455.  
  456. for k, v in pairs(fs) do
  457.   _fs[k] = v
  458. end
  459.  
  460. local function restore ()
  461.   for k, v in pairs(_fs) do
  462.     fs[k] = v
  463.   end
  464. end
  465.  
  466. local function isin (val, tab)
  467.   for k, v in pairs(tab) do
  468.     if v == val then
  469.       return true, k
  470.     end
  471.   end
  472.  
  473.   return false
  474. end
  475.  
  476. local function cleanPath (path)
  477.   return ("/" .. path .. "/"):gsub("[/\\]+", "/"):gsub("/([^/]*)/%.%./", "/"):match("^/?(.-)/?$")
  478. end
  479.  
  480. local function resolvePerms (path)
  481.   path = cleanPath(path):sub(#root == 0 and 0 or #root + 2)
  482.   local lastCount, perm = math.huge, "rw"
  483.  
  484.   for f, p in pairs(perms) do
  485.     if path:sub(1, #f) == f and (path .. "/"):sub(#f + 1, #f + 1) == "/" then
  486.       local dirCount = 0; for _ in f:gmatch("[^/]+") do dirCount = dirCount + 1 end
  487.       if dirCount < lastCount then
  488.         lastCount = dirCount
  489.         perm = p
  490.       end
  491.     end
  492.   end
  493.  
  494.   return perm
  495. end
  496.  
  497. local function resolveFile (path)
  498.   path = cleanPath(path):sub(#root == 0 and 0 or #root + 2)
  499.   local current = files
  500.  
  501.   for part in path:gmatch("[^/]+") do
  502.     current = current[part]
  503.  
  504.     if not current then
  505.       return nil, resolvePerms(path)
  506.     end
  507.   end
  508.  
  509.   return current, resolvePerms(path)
  510. end
  511.  
  512. local function createFile (path, file)
  513.   path = cleanPath(path)
  514.  
  515.   if type(file) == "string" then
  516.     local f = _fs.open(path, "wb")
  517.  
  518.     for char in file:gmatch(".") do
  519.       f.write(char:byte())
  520.     end
  521.  
  522.     f.close()
  523.   else
  524.     fs.makeDir(path)
  525.  
  526.     for name, file in pairs(file) do
  527.       createFile(path .. "/" .. name, file)
  528.     end
  529.   end
  530. end
  531.  
  532. if not root then
  533.   root = cleanPath(fs.getDir(shell.getRunningProgram()))
  534. end
  535.  
  536. fs.open = function (path, mode)
  537.   local file, perms = resolveFile(path)
  538.  
  539.   if type(file) == "string" then
  540.     if mode == "r" then
  541.       if perms:match("r") then
  542.         if _fs.exists(path) and not _fs.isDir(path) and perms:match("w") then
  543.           return _fs.open(path, mode)
  544.         end
  545.  
  546.         local cur = 1
  547.         local closed = false
  548.  
  549.         return {
  550.           readLine = function ()
  551.             if not closed and cur <= #file then
  552.               local line, ending = file:match("^([^\r\n]-)([\r]?[\n])", cur)
  553.               cur = cur + #line + #ending
  554.               return line
  555.             end
  556.           end;
  557.           readAll = function ()
  558.             if not closed then
  559.               local _cur = cur
  560.               cur = #file + 1
  561.               return file:sub(_cur)
  562.             end
  563.           end;
  564.           close = function ()
  565.             closed = true
  566.           end;
  567.         }
  568.       end
  569.     elseif mode == "rb" then
  570.       if perms:match("r") then
  571.         if _fs.exists(path) and not _fs.isDir(path) and perms:match("w") then
  572.           return _fs.open(path, mode)
  573.         end
  574.  
  575.         local cur = 1
  576.         local closed = false
  577.  
  578.         return {
  579.           read = function ()
  580.             if not closed and cur <= #file then
  581.               local _cur = cur
  582.               cur = cur + 1
  583.               return file:sub(_cur, _cur):byte()
  584.             end
  585.           end;
  586.           close = function ()
  587.             closed = true
  588.           end;
  589.         }
  590.       end
  591.     elseif mode == "a" then
  592.       if perms:match("w") then
  593.         if not _fs.exists(path) then createFile(path, file) end
  594.         return _fs.open(path, "a")
  595.       end
  596.     elseif mode == "ab" then
  597.       if perms:match("w") then
  598.         if not _fs.exists(path) then createFile(path, file) end
  599.         return _fs.open(path, "ab")
  600.       end
  601.     elseif mode == "w" then
  602.       if perms:match("w") then
  603.         return _fs.open(path, "w")
  604.       end
  605.     elseif mode == "wb" then
  606.       if perms:match("w") then
  607.         return _fs.open(path, "wb")
  608.       end
  609.     end
  610.   elseif isin(mode, { "r", "rb" }) and perms:match("r") or isin(mode, { "a", "ab", "w", "wb" }) and perms:match("w") then
  611.     return _fs.open(path, mode)
  612.   end
  613. end
  614.  
  615. fs.list = function (path)
  616.   path = cleanPath(path)
  617.   local folder, perms = resolveFile(path)
  618.  
  619.   if _fs.exists(path) and (not folder or (not not (type(folder) == "table")) == _fs.isDir(path) and perms:match("w")) then
  620.     local list = _fs.list(path)
  621.  
  622.     if folder then
  623.       for name in pairs(folder) do
  624.         if not isin(name, list) then
  625.           list[#list + 1] = name
  626.         end
  627.       end
  628.     end
  629.  
  630.     return list
  631.   else
  632.     folder = resolveFile(path)
  633.   end
  634.  
  635.   if type(folder) == "table" then
  636.     local list = {}
  637.  
  638.     for name in pairs(folder) do
  639.       list[#list + 1] = name
  640.     end
  641.  
  642.     return list
  643.   end
  644. end
  645.  
  646. fs.move = function (from, to)
  647.   if _fs.exists(from) then
  648.     return _fs.move(from, to)
  649.   else
  650.     local file = resolveFile(from)
  651.  
  652.     if file then
  653.       createFile(to, file)
  654.     end
  655.   end
  656. end
  657.  
  658. fs.copy = function (from, to)
  659.   if _fs.exists(from) then
  660.     return _fs.copy(from, to)
  661.   else
  662.     local file = resolveFile(from)
  663.  
  664.     if file then
  665.       createFile(from, file)
  666.       return _fs.copy(from, to)
  667.     end
  668.   end
  669. end
  670.  
  671. fs.getDrive = function (path)
  672.   return _fs.getDrive(path) or resolveFile(path) and _fs.getDrive(root) or nil
  673. end
  674.  
  675. fs.exists = function (path)
  676.   return _fs.exists(path) or not not resolveFile(path)
  677. end
  678.  
  679. fs.isDir = function (path)
  680.   return _fs.isDir(path) or type(resolveFile(path)) == "table"
  681. end
  682.  
  683. fs.getSize = function (path)
  684.   local file, perms = resolveFile(path)
  685.   return  _fs.exists(path) and (not file or (not not (type(file) == "table")) == _fs.isDir(path)) and _fs.getSize(path) or
  686.           type(file) == "string" and #file or
  687.           type(file) == "table" and 0 or
  688.           error("No such file", 2)
  689. end
  690.  
  691. fs.isReadOnly = function (path)
  692.   local file, perms = resolveFile(path)
  693.  
  694.   if file then
  695.     return _fs.isReadOnly(path) or perms == "r"
  696.   else
  697.     return _fs.isReadOnly(path)
  698.   end
  699. end
  700.  
  701. local func, err = (loadstring or load)(main, fs.getName(shell.getRunningProgram()), nil, env)
  702.  
  703. if not func then
  704.   restore()
  705.   error(err, 0)
  706. end
  707.  
  708. if setfenv then
  709.   setfenv(func, env)
  710. end
  711.  
  712. local ret = (function (...)
  713.   return { n = select("#", ...), ... }
  714. end)(pcall(func, ...))
  715.  
  716. restore()
  717.  
  718. if not ret[1] then
  719.   error(ret[2], 0)
  720. end
  721.  
  722. return (table.unpack or unpack)(ret, 2, ret.n)]])
  723. end)
  724.  
  725. file.close()
  726.  
  727. if not ok then
  728.   error("Failed to write to the file:\n" .. err, 0)
  729. end
  730.  
  731. print("Done")
  732. print("Files successfully packed into the package '" .. destination .. "'.")
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement