Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- --[[
- Copyright (c) 2015 Mantas KlimaĊĦauskas (MKlegoman357)
- 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 args = {...}
- local helpArgs = (function ()
- local help = {"help", "?", "-help", "-h"}
- for i, arg in ipairs(help) do help[arg] = true; help[i] = nil; end
- return help
- end)()
- local x, y = term.getCursorPos()
- local CLAMSHELL = type(shell.version) == "function" and type(shell.version()) == "string" and not not shell.version():match("ClamShell")
- local function getUsage (str)
- str = str or ""
- local l = function (t) str = str .. t .. "\n" end
- l("Usage:")
- 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]]")
- return str
- end
- local function getHelp (str)
- str = str or ""
- local l = function (t) str = str .. (t or "") .. "\n" end
- 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.")
- l()
- l("Required arguments:")
- 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>.")
- l("* <destination file> - the path of the resulting runnable file package.")
- l()
- l("Optional arguments:")
- 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.")
- 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.")
- l("* -e folder(s)/file(s) - the 'exclude' list of paths separated by spaces ( ) or colons (:).")
- l("* -p path;(r|w|rw) - list of resulting paths separated by spaces ( ) or colons (:) indicating their permissions.")
- l("* -r path - the root directory in which to virtually place packed files.")
- l("* --include-self - will include this program in the resulting package.")
- l()
- l("Notes:")
- l("* This program works with binary files.")
- l("* include, exclude and permissions paths allow the use of wildcards (*). ex.: -i /foo/*.lua -e /bar/*/foo")
- l("* You can use -include, -exclude, -perms (or -permissions) and -root instead of -i, -e, -p and -r.")
- return str
- end
- if helpArgs[args[1]] then
- if CLAMSHELL then
- print(getUsage() .. "\n" .. getHelp())
- else
- textutils.pagedPrint(getUsage() .. "\n" .. getHelp(), y - 3)
- end
- return
- elseif #args < 2 then
- if term.isColor() then
- term.setTextColor(colors.red)
- end
- if CLAMSHELL then
- print(getUsage())
- else
- textutils.pagedPrint(getUsage(), y - 3)
- end
- error()
- end
- --// Helpers \\--
- local function yield ()
- os.queueEvent("PACK_YIELD"); os.pullEvent()
- end
- local function cleanPath (path)
- return ("/" .. path .. "/"):gsub("[/\\]+", "/"):gsub("/([^/]*)/%.%./", "/"):match("^/?(.-)/?$")
- end
- local function isin (val, tab)
- for k, v in pairs(tab) do
- if v == val then
- return true, k
- end
- end
- return false
- end
- local function bet (n, n1, n2)
- return n >= n1 and n <= n2
- end
- local special = {
- "[", "]", ".", "+", "-", "?", "%", "*", "(", ")"
- }
- local function find (search, files)
- search = cleanPath(search)
- local list = {}
- local path = search:match("[^/]*")
- local pattern = "^" .. path:gsub(".", function (c) return c == "*" and "[^/]*" or isin(c, special) and "%" .. c or c end) .. "$"
- local isLast = path == search
- for name, file in pairs(files) do
- if name:find(pattern) then
- if isLast then
- list[#list + 1] = name
- elseif type(file) == "table" then
- for i, found in ipairs(find(search:sub(#path + 2), file)) do
- list[#list + 1] = name .. "/" .. found
- end
- end
- end
- end
- return list
- end
- local function addPerm (path, perm, perms)
- path = cleanPath(path)
- for file in pairs(perms) do
- if path:sub(1, #file) == file and (path .. "/"):sub(#file + 1, #file + 1) == "/" then
- return
- end
- end
- perms[path] = perm
- end
- local function toByte (byte)
- return "\\" .. byte
- end
- local escape = {
- ["\n"] = "\\n";
- ["\r"] = "\\r";
- ["\t"] = "\\t";
- ["\b"] = "\\b";
- ["'"] = "\\'";
- ["\\"] = "\\\\";
- }
- local function buildFileList (path, exclude)
- local result
- if not exclude[path] then
- yield()
- if fs.isDir(path) then
- result = {}
- for i, file in ipairs(fs.list(path)) do
- result[file] = buildFileList(fs.combine(path, file), exclude)
- end
- elseif fs.exists(path) then
- local file = fs.open(path, "rb")
- local prevEsc = false
- result = ""
- if file then
- local i = 1
- for byte in file.read do
- local char = string.char(byte)
- if escape[char] then
- result = result .. escape[char]
- prevEsc = false
- elseif bet(byte, 32, 47) or bet(byte, 58, 95) or bet(byte, 97, 126) then
- result = result .. char
- prevEsc = false
- elseif bet(byte, 48, 57) then
- if prevEsc then
- result = result .. toByte(byte)
- else
- result = result .. char
- prevEsc = false
- end
- else
- result = result .. toByte(byte)
- prevEsc = true
- end
- if i > 10000 then
- i = 1
- else
- i = i + 1
- end
- end
- else
- printError("Could not open file '" .. path .. "'. Excluding it from the package.")
- end
- file.close()
- else
- printError("File or folder '" .. path .. "' does not exist.")
- end
- end
- return result
- end
- local function serialize (fh, file, i)
- i = i or 0
- if type(file) == "string" then
- fh.writeLine("'" .. file .. "';")
- else
- fh.writeLine("{")
- for name, subFile in pairs(file) do
- fh.write(string.rep(" ", i + 2) .. "['" .. name .. "']=")
- serialize(fh, subFile, i + 2)
- end
- fh.writeLine(string.rep(" ", i) .. "};")
- end
- end
- local function serializePermissions (fh, perms)
- fh.writeLine("{")
- for path, perm in pairs(perms) do
- fh.writeLine(" ['" .. path .. "']='" .. perm .. "';")
- end
- fh.writeLine("}")
- end
- --// Main program \\--
- local mainFile, destination = shell.resolve(args[1]), shell.resolve(args[2])
- local packAll = args[3] == "--pack-all"
- local root = nil
- local include = {}
- local exclude = {
- [shell.getRunningProgram()] = not packAll or nil;
- }
- local perms = {}
- local permsArg = {}
- if not fs.exists(mainFile) then
- error("Main program '" .. mainFile .. "' does not exist.", 0)
- elseif fs.isDir(mainFile) then
- error("Main program '" .. mainFile .. "' cannot be a folder.", 0)
- elseif fs.isReadOnly(destination) then
- error("The destination path '" .. destination .. "' is read-only.", 0)
- elseif fs.exists(destination) then
- print("Destination " .. (fs.isDir(destination) and "folder" or "file") .. " '" .. destination .. "' already exists. Do you want to overwrite it? (y/N)")
- local overwrite = read()
- if overwrite:sub(1, 1):lower() ~= "y" then
- error("Packing canceled.", 0)
- end
- exclude[destination] = true
- end
- do
- local ok, err = loadfile(mainFile)
- if not ok then
- error("'" .. mainFile .. "' is not a valid Lua program or has syntax errors:\n" .. err, 0)
- end
- end
- if packAll then
- for i, path in ipairs(fs.list(shell.dir())) do
- include[shell.resolve(path)] = true
- end
- else
- local switches = {
- "-i", "-include";
- "-e", "-exclude";
- "-r", "-root";
- "-p", "-perms", "-permissions";
- }
- local switch = "i"
- local prevSwitch = "i"
- for i = 3, #args do
- local arg = args[i]:lower()
- if arg == "--include-self" then
- exclude[shell.getRunningProgram()] = nil
- include[shell.getRunningProgram()] = true
- elseif isin(arg, switches) then
- prevSwitch = switch
- switch = arg:sub(2, 2)
- elseif switch == "r" then
- root = cleanPath(arg)
- switch = prevSwitch
- elseif switch == "p" then
- local paths, perm = arg:match("^([^;]+);([rw]+)$")
- if not paths then
- error("Invalid path '" .. arg .. "'.\nUse the format:\npath1[:path2[...]];(r|w|rw)", 0)
- end
- for path in paths:gmatch("[^:]+") do
- permsArg[cleanPath(path)] = perm
- end
- elseif switch == "e" then
- for path in arg:gmatch("[^:]+") do
- for i, file in ipairs(fs.find(shell.resolve(path))) do
- exclude[file] = true
- end
- end
- elseif switch == "i" then
- local paths, perm = arg:match("^([^;]+);?([rw]*)$")
- if not paths then
- error("Invalid path '" .. arg .. "'.\nUse the format:\npath1[:path2[...]][;(r|w|rw)]", 0)
- end
- perm = #perm ~= 0 and perm or nil
- for path in paths:gmatch("[^:]+") do
- if path == "%self%" then
- exclude[shell.getRunningProgram()] = nil
- include[shell.getRunningProgram()] = true
- addPerm(fs.getName(shell.getRunningProgram()), perm, perms)
- else
- for i, file in ipairs(fs.find(shell.resolve(path))) do
- include[file] = true
- addPerm(fs.getName(file), perm, perms)
- end
- end
- end
- end
- end
- if not next(include) then
- local drive = fs.getDrive(shell.dir())
- for i, path in ipairs(fs.list(shell.dir())) do
- path = shell.resolve(path)
- if fs.getDrive(path) == drive then
- include[path] = true
- end
- end
- end
- end
- write("Building file list. ")
- local files = {}
- local main = buildFileList(mainFile, {})
- for path in pairs(include) do
- files[fs.getName(path)] = buildFileList(path, exclude)
- end
- print("Done")
- if next(permsArg) then
- write("Setting permissions. ")
- for paths, perm in pairs(permsArg) do
- for path in paths:gmatch("[^:]+") do
- for i, file in ipairs(find(path, files)) do
- addPerm(file, perm, perms)
- end
- end
- end
- print("Done")
- end
- if fs.exists(destination) then
- print("'" .. destination .. "' already exists, deleting.")
- local ok, err = pcall(fs.delete, destination)
- if not ok then
- error("Could not delete '" .. destination .. "':\n" .. err, 0)
- end
- end
- print("Writing to file started.")
- local file = fs.open(destination, "w")
- if not file then
- error("Could not open destination file '" .. destination .. "'.", 0)
- end
- local ok, err = pcall(function ()
- file.writeLine[[
- -- packed using Pack by MKlegoman357
- ]]
- write("Serializing files. ")
- file.writeLine("--# FILES #--")
- file.write("local files = ")
- serialize(file, files)
- file.writeLine("--# FILES END #--")
- print("Done")
- write("Serializing main program. ")
- file.writeLine("--# MAIN #--")
- file.write("local main = ")
- serialize(file, main)
- file.writeLine("--# MAIN END #--")
- print("Done")
- write("Serializing file permissions. ")
- file.writeLine("--# PERMISSIONS #--")
- file.write("local perms = ")
- serializePermissions(file, perms)
- file.writeLine("--# PERMISSIONS END #--")
- print("Done")
- write("Appending the rest of the file. ")
- file.writeLine("--# ROOT #--")
- file.writeLine("local root = " .. textutils.serialize(root))
- file.writeLine("--# ROOT END #--")
- file.writeLine([[
- local env = getfenv and getfenv() or _ENV
- local _fs = {}
- for k, v in pairs(fs) do
- _fs[k] = v
- end
- local function restore ()
- for k, v in pairs(_fs) do
- fs[k] = v
- end
- end
- local function isin (val, tab)
- for k, v in pairs(tab) do
- if v == val then
- return true, k
- end
- end
- return false
- end
- local function cleanPath (path)
- return ("/" .. path .. "/"):gsub("[/\\]+", "/"):gsub("/([^/]*)/%.%./", "/"):match("^/?(.-)/?$")
- end
- local function resolvePerms (path)
- path = cleanPath(path):sub(#root == 0 and 0 or #root + 2)
- local lastCount, perm = math.huge, "rw"
- for f, p in pairs(perms) do
- if path:sub(1, #f) == f and (path .. "/"):sub(#f + 1, #f + 1) == "/" then
- local dirCount = 0; for _ in f:gmatch("[^/]+") do dirCount = dirCount + 1 end
- if dirCount < lastCount then
- lastCount = dirCount
- perm = p
- end
- end
- end
- return perm
- end
- local function resolveFile (path)
- path = cleanPath(path):sub(#root == 0 and 0 or #root + 2)
- local current = files
- for part in path:gmatch("[^/]+") do
- current = current[part]
- if not current then
- return nil, resolvePerms(path)
- end
- end
- return current, resolvePerms(path)
- end
- local function createFile (path, file)
- path = cleanPath(path)
- if type(file) == "string" then
- local f = _fs.open(path, "wb")
- for char in file:gmatch(".") do
- f.write(char:byte())
- end
- f.close()
- else
- fs.makeDir(path)
- for name, file in pairs(file) do
- createFile(path .. "/" .. name, file)
- end
- end
- end
- if not root then
- root = cleanPath(fs.getDir(shell.getRunningProgram()))
- end
- fs.open = function (path, mode)
- local file, perms = resolveFile(path)
- if type(file) == "string" then
- if mode == "r" then
- if perms:match("r") then
- if _fs.exists(path) and not _fs.isDir(path) and perms:match("w") then
- return _fs.open(path, mode)
- end
- local cur = 1
- local closed = false
- return {
- readLine = function ()
- if not closed and cur <= #file then
- local line, ending = file:match("^([^\r\n]-)([\r]?[\n])", cur)
- cur = cur + #line + #ending
- return line
- end
- end;
- readAll = function ()
- if not closed then
- local _cur = cur
- cur = #file + 1
- return file:sub(_cur)
- end
- end;
- close = function ()
- closed = true
- end;
- }
- end
- elseif mode == "rb" then
- if perms:match("r") then
- if _fs.exists(path) and not _fs.isDir(path) and perms:match("w") then
- return _fs.open(path, mode)
- end
- local cur = 1
- local closed = false
- return {
- read = function ()
- if not closed and cur <= #file then
- local _cur = cur
- cur = cur + 1
- return file:sub(_cur, _cur):byte()
- end
- end;
- close = function ()
- closed = true
- end;
- }
- end
- elseif mode == "a" then
- if perms:match("w") then
- if not _fs.exists(path) then createFile(path, file) end
- return _fs.open(path, "a")
- end
- elseif mode == "ab" then
- if perms:match("w") then
- if not _fs.exists(path) then createFile(path, file) end
- return _fs.open(path, "ab")
- end
- elseif mode == "w" then
- if perms:match("w") then
- return _fs.open(path, "w")
- end
- elseif mode == "wb" then
- if perms:match("w") then
- return _fs.open(path, "wb")
- end
- end
- elseif isin(mode, { "r", "rb" }) and perms:match("r") or isin(mode, { "a", "ab", "w", "wb" }) and perms:match("w") then
- return _fs.open(path, mode)
- end
- end
- fs.list = function (path)
- path = cleanPath(path)
- local folder, perms = resolveFile(path)
- if _fs.exists(path) and (not folder or (not not (type(folder) == "table")) == _fs.isDir(path) and perms:match("w")) then
- local list = _fs.list(path)
- if folder then
- for name in pairs(folder) do
- if not isin(name, list) then
- list[#list + 1] = name
- end
- end
- end
- return list
- else
- folder = resolveFile(path)
- end
- if type(folder) == "table" then
- local list = {}
- for name in pairs(folder) do
- list[#list + 1] = name
- end
- return list
- end
- end
- fs.move = function (from, to)
- if _fs.exists(from) then
- return _fs.move(from, to)
- else
- local file = resolveFile(from)
- if file then
- createFile(to, file)
- end
- end
- end
- fs.copy = function (from, to)
- if _fs.exists(from) then
- return _fs.copy(from, to)
- else
- local file = resolveFile(from)
- if file then
- createFile(from, file)
- return _fs.copy(from, to)
- end
- end
- end
- fs.getDrive = function (path)
- return _fs.getDrive(path) or resolveFile(path) and _fs.getDrive(root) or nil
- end
- fs.exists = function (path)
- return _fs.exists(path) or not not resolveFile(path)
- end
- fs.isDir = function (path)
- return _fs.isDir(path) or type(resolveFile(path)) == "table"
- end
- fs.getSize = function (path)
- local file, perms = resolveFile(path)
- return _fs.exists(path) and (not file or (not not (type(file) == "table")) == _fs.isDir(path)) and _fs.getSize(path) or
- type(file) == "string" and #file or
- type(file) == "table" and 0 or
- error("No such file", 2)
- end
- fs.isReadOnly = function (path)
- local file, perms = resolveFile(path)
- if file then
- return _fs.isReadOnly(path) or perms == "r"
- else
- return _fs.isReadOnly(path)
- end
- end
- local func, err = (loadstring or load)(main, fs.getName(shell.getRunningProgram()), nil, env)
- if not func then
- restore()
- error(err, 0)
- end
- if setfenv then
- setfenv(func, env)
- end
- local ret = (function (...)
- return { n = select("#", ...), ... }
- end)(pcall(func, ...))
- restore()
- if not ret[1] then
- error(ret[2], 0)
- end
- return (table.unpack or unpack)(ret, 2, ret.n)]])
- end)
- file.close()
- if not ok then
- error("Failed to write to the file:\n" .. err, 0)
- end
- print("Done")
- print("Files successfully packed into the package '" .. destination .. "'.")
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement